מבנה נתונים מפורט (Detailed Database Schema)
- summary:
מסמך זה מתאר בפירוט את כל האוספים, השדות, האילוצים והאינדקסים במסד הנתונים.
אוסף: code_snippets
תיאור: אחסון קטעי קוד של משתמשים
שדות:
שדה |
סוג |
חובה |
תיאור |
|---|---|---|---|
|
ObjectId |
כן |
מזהה ייחודי |
|
int |
כן |
מזהה משתמש Telegram |
|
string |
כן |
שם הקובץ (עד 255 תווים) |
|
string |
כן |
תוכן הקוד |
|
string |
כן |
שפת התכנות (auto-detected) |
|
bool |
לא |
האם הקובץ במועדפים (ברירת מחדל: false). סימון של הקובץ, לא של השורה: קובץ נחשב מועדף אם איזושהי גרסה פעילה שלו מסומנת, והרשימות מציגות אותו בגרסה האחרונה שלו. הוספה והסרה — בבוט ובווב, אחד-אחד ובבחירה מרובה — חלות על כל הגרסאות הפעילות של הקובץ, וגרסה חדשה יורשת את המצב של הקובץ לפי אותו כלל — גם כשרק גרסה ישנה מסומנת, וגם בעריכה ששינתה את שם הקובץ ( |
|
datetime |
לא |
תאריך הוספה למועדפים. גרסה חדשה יורשת אותו יחד עם הסימון — הסימון האחרון בקובץ — ולכן הוא הרגע שבו הקובץ סומן ולא רגע כתיבת הגרסה |
|
string |
לא |
תיאור/הערה על הקובץ |
|
array[string] |
לא |
תגיות (ברירת מחדל: []) |
|
int |
לא |
מספר גרסה (ברירת מחדל: 1). ייחודי לכל |
|
datetime |
לא |
תאריך יצירת הקובץ, לא של השורה. כל גרסה היא מסמך נפרד, וגרסה חדשה יורשת את הערך מהגרסה הקודמת |
|
datetime |
לא |
תאריך השינוי האחרון בקובץ עצמו — בתוכן, בתיאור או בשם. בקובץ שמעולם לא נערך הוא זהה ל- |
|
datetime |
לא |
מתי השורה הזאת נכתבה. נכתב פעם אחת ביצירת מסמך הגרסה ואינו מתעדכן לעולם — וזו ההבחנה מ- |
|
int |
לא |
מספר הגרסה שבה ה- |
|
bool |
לא |
האם הקובץ פעיל (ברירת מחדל: true). מחיקה רכה חלה על כל הגרסאות של הקובץ יחד, כי הקובץ הוא |
|
datetime |
לא |
תאריך מחיקה (soft delete) |
|
datetime |
לא |
תאריך המחיקה הסופית, שהעמוד |
אילוצים:
- user_id + file_name + version ייחודיים יחד
- file_name לא יכול להיות ריק
- code לא יכול להיות ריק
- tags חייב להיות array (אפילו ריק)
אינדקסים:
// אינדקס ראשי - חיפוש לפי משתמש ותאריך
db.code_snippets.createIndex(
{ "user_id": 1, "created_at": -1 },
{ name: "user_created_idx" }
)
// אינדקס שפה - סינון לפי שפת תכנות
db.code_snippets.createIndex(
{ "programming_language": 1 },
{ name: "language_idx" }
)
// אינדקס Full-Text - חיפוש בתוכן
db.code_snippets.createIndex(
{ "file_name": "text", "code": "text", "description": "text" },
{ name: "text_search_idx" }
)
// אינדקס תגיות - סינון לפי תגיות
db.code_snippets.createIndex(
{ "tags": 1 },
{ name: "tags_idx" }
)
// אינדקס מחיקה - סינון קבצים פעילים
db.code_snippets.createIndex(
{ "is_active": 1, "deleted_at": 1 },
{ name: "active_idx" }
)
// אינדקס מועדפים
db.code_snippets.createIndex(
{ "user_id": 1, "is_favorite": 1, "favorited_at": -1 },
{ name: "favorites_idx" }
)
אוסף: large_files
תיאור: אחסון קבצים גדולים (>4096 תווים)
שדות:
שדה |
סוג |
חובה |
תיאור |
|---|---|---|---|
|
ObjectId |
כן |
מזהה ייחודי |
|
int |
כן |
מזהה משתמש Telegram |
|
string |
כן |
שם הקובץ |
|
string |
כן |
תוכן הקובץ |
|
string |
כן |
שפת התכנות |
|
int |
כן |
גודל הקובץ בבייטים (auto-calculated) |
|
int |
כן |
מספר שורות (auto-calculated) |
|
string |
לא |
תיאור |
|
array[string] |
לא |
תגיות |
|
datetime |
לא |
תאריך יצירת הקובץ. שמירה חוזרת מוחקת את המסמך ומכניסה חדש, והערך עובר אליו |
|
datetime |
לא |
תאריך עדכון. בקובץ שמעולם לא נשמר מחדש הוא זהה ל- |
|
bool |
לא |
האם פעיל |
|
datetime |
לא |
תאריך מחיקה |
|
datetime |
לא |
תאריך המחיקה הסופית — אותו אינדקס TTL ואותו מפרט כמו ב- |
הבדלים מ-code_snippets:
- אין version (גרסאות נשמרות ב-file_versions)
- אין is_favorite (מועדפים נשמרים ב-favorites collection)
- file_size ו-lines_count מחושבים אוטומטית
אינדקסים:
db.large_files.createIndex(
{ "user_id": 1, "created_at": -1 },
{ name: "user_created_idx" }
)
db.large_files.createIndex(
{ "programming_language": 1 },
{ name: "language_idx" }
)
db.large_files.createIndex(
{ "file_size": -1 },
{ name: "size_idx" }
)
אוסף: users
תיאור: פרופילי משתמשים
שדות:
שדה |
סוג |
חובה |
תיאור |
|---|---|---|---|
|
ObjectId |
כן |
מזהה ייחודי |
|
int |
כן |
מזהה משתמש Telegram (unique) |
|
string |
לא |
שם משתמש Telegram |
|
string |
לא |
שם פרטי |
|
string |
לא |
שם משפחה |
|
datetime |
לא |
תאריך יצירת פרופיל |
|
datetime |
לא |
פעילות אחרונה |
|
object |
לא |
הגדרות משתמש |
|
object |
לא |
סטטיסטיקות שימוש |
מבנה settings:
{
"language": "he", // שפת ממשק
"notifications": true, // התראות
"github_backoff_enabled": false, // GitHub backoff
"github_backoff_until": null // תאריך סיום backoff
}
מבנה stats:
{
"total_files": 150, // סה"כ קבצים
"total_searches": 45, // סה"כ חיפושים
"total_backups": 5 // סה"כ גיבויים
}
אינדקסים:
db.users.createIndex(
{ "user_id": 1 },
{ unique: true, name: "user_id_unique_idx" }
)
db.users.createIndex(
{ "username": 1 },
{ name: "username_idx" }
)
אוסף: bookmarks
תיאור: סימניות משתמשים (WebApp)
שדות:
שדה |
סוג |
חובה |
תיאור |
|---|---|---|---|
|
ObjectId |
כן |
מזהה ייחודי |
|
int |
כן |
מזהה משתמש |
|
ObjectId |
כן |
מזהה קובץ |
|
int |
לא |
מספר שורה (אם לא anchor) |
|
string |
לא |
מזהה עוגן (Markdown/HTML) |
|
string |
לא |
צבע סימנייה (yellow/red/green/blue/purple) |
|
string |
לא |
הערה אישית |
|
datetime |
לא |
תאריך יצירה |
אילוצים:
- line_number או anchor_id חייב להיות מוגדר (לא שניהם)
- color חייב להיות אחד מהצבעים הנתמכים
- עד 50 סימניות לקובץ
- עד 500 סימניות למשתמש
אינדקסים:
db.bookmarks.createIndex(
{ "user_id": 1, "file_id": 1 },
{ name: "user_file_idx" }
)
db.bookmarks.createIndex(
{ "file_id": 1, "line_number": 1 },
{ name: "file_line_idx" }
)
אוסף: collections
תיאור: אוספי קבצים (WebApp)
שדות:
שדה |
סוג |
חובה |
תיאור |
|---|---|---|---|
|
ObjectId |
כן |
מזהה ייחודי |
|
int |
כן |
מזהה משתמש |
|
string |
כן |
שם האוסף |
|
string |
לא |
תיאור |
|
string |
לא |
אייקון |
|
string |
לא |
צבע |
|
bool |
לא |
האם מועדף |
|
array |
לא |
פריטי האוסף |
|
datetime |
לא |
תאריך יצירה |
|
datetime |
לא |
תאריך עדכון |
מבנה items:
[
{
"file_id": ObjectId("..."),
"order": 0, // סדר מותאם
"note": "הערה על הפריט"
}
]
אינדקסים:
db.collections.createIndex(
{ "user_id": 1, "created_at": -1 },
{ name: "user_created_idx" }
)
אוסף: backups
תיאור: metadata של גיבויים
שדות:
שדה |
סוג |
חובה |
תיאור |
|---|---|---|---|
|
ObjectId |
כן |
מזהה ייחודי |
|
int |
כן |
מזהה משתמש |
|
string |
כן |
סוג גיבוי (full_backup/github_repo_zip/google_drive_backup) |
|
datetime |
כן |
תאריך יצירה |
|
int |
כן |
מספר קבצים |
|
int |
כן |
גודל כולל בבייטים |
|
string |
לא |
גרסת metadata |
|
string |
לא |
דירוג (🏆 מצוין/👍 טוב/🤷 סביר) |
|
string |
לא |
הערה |
|
string |
לא |
repository (רק ל-github_repo_zip) |
אינדקסים:
db.backups.createIndex(
{ "user_id": 1, "created_at": -1 },
{ name: "user_created_idx" }
)
db.backups.createIndex(
{ "backup_type": 1 },
{ name: "type_idx" }
)
אוסף: sticky_notes
תיאור: פתקים דביקים. פתק שייך ל**יעד אחד בדיוק** — קובץ, לוח, או קובץ בריפו ממורר (הזוג repo_name + repo_path יחד).
שדה |
סוג |
חובה |
תיאור |
|---|---|---|---|
|
int |
כן |
בעל הפתק. נמצא בכל שאילתה. |
|
str |
מותנה |
הקובץ שאליו הפתק צמוד. ריק בפתקי לוח ובפתקי ריפו. |
|
str |
מותנה |
הלוח שעליו הפתק יושב. ריק בפתקי קובץ ובפתקי ריפו. |
|
str |
מותנה |
הריפו הממורר. קיים רק בפתקי ריפו, ותמיד יחד עם |
|
str |
מותנה |
נתיב הקובץ בעץ הריפו, מנורמל לצורת |
|
str |
לא |
מפתח פילוח לפי שם הקובץ. קיים רק בפתקי קובץ. |
|
str |
לא |
|
|
str |
כן |
עד 20,000 תווים. התקרה מוגדרת פעם אחת ב- |
|
int |
כן |
מיקום. בפתק לוח הקואורדינטות יחסיות למשטח, ולא למסמך. |
|
int |
כן |
גודל. |
חשוב
האילוץ ”בדיוק אחד“ נאכף ב-sticky_notes_target.build_note_target, שכל מסלולי הכתיבה עוברים דרכו. הכלל טרנרי (קובץ/לוח/ריפו) ותכונתי: TARGET_FIELDS ממפה כל סוג לשדות שמותר לו לשאת, וכל שדה זר נדחה. אין $jsonSchema validator ברמת מונגו, כי אין בריפו תשתית migrations שתחזיק אותו — במקומו scripts/migrate_note_boards.py מדפיס דוח הפרות, והרצה חוזרת שלו היא הבדיקה.
אינדקסים: (user_id, file_id), (user_id, file_id, created_at), (user_id, scope_id), (user_id, board_id), (user_id, repo_name, repo_path), (user_id, title), (user_id, updated_at), (updated_at). שני אינדקסי שם ייחודיים-חלקיים: one_title_per_board_v2 (מסונן על board_id קיים) ו-one_title_per_repo_file_v1 (מסונן על repo_path קיים).
הערה
``(user_id, updated_at)`` ו-(updated_at) נראים חופפים ואינם. חיפוש הפתקים ממיין ב-updated_at יורד וגם מסנן לפי משתמש, ו-explain על השאילתה הזו הראה שמונגו בוחרת דווקא ב-updated_at לבדו — כלומר סורקת את הפתקים של כל המשתמשים לפי סדר עדכון ומסננת את הבעלות כשארית. התוכניות שנשענות על תחילית user_id נדחות כולן, כי כל אחת מהן דורשת מיון חוסם. האינדקס המורכב הוא היחיד שנותן את שניהם יחד.
אין כאן אינדקס טקסט, ובכוונה. חיפוש הפתקים מתאים תת-מחרוזת ברג’קס ולא טוקנים שלמים. $text עם default_language: "none" מתעד במפורש שהוא מתעלם מגזירת סיומות, ולכן חיפוש ”פתק“ אינו מוצא ”בפתק“ — מדידה על פתקים אמיתיים בעברית הראתה שהוא מפספס כ-18% מההתאמות שהרג’קס מוצא, ובמונחים נפוצים הרבה יותר. ההסבר המלא ב-עקרונות להוספת פיצ’ר לסטיקי-נוטס.
אוסף: note_boards
תיאור: לוחות פתקים. משטח שעליו יושבים פתקים שאינם צמודים לקובץ.
שדה |
סוג |
חובה |
תיאור |
|---|---|---|---|
|
ObjectId |
כן |
מזהה הלוח. יציב — ולכן אין צורך ב- |
|
int |
כן |
בעל הלוח. |
|
str |
כן |
שם הלוח. ניתן לשינוי, כולל בלוח ברירת המחדל. |
|
bool |
כן |
לוח ברירת המחדל. הזיהוי הוא לפי השדה הזה ולא לפי השם, כי השם ניתן לשינוי. |
|
int |
כן |
סדר תצוגה. |
|
datetime |
כן |
חותמות זמן. |
אינדקסים: (user_id, order), ו-(user_id) ייחודי-חלקי על is_default: true.
הערה
האינדקס הייחודי-החלקי הוא מה שסוגר את המרוץ שבו שתי בקשות מקבילות מגלות שאין לוח ברירת מחדל ושתיהן יוצרות. הגנת קוד לבדה אינה מספיקה שם — המסד חייב לדחות, והקוד קורא שוב ומחזיר את הלוח שנוצר.
יחסים בין אוספים
דיאגרמת יחסים בסיסית:
erDiagram
users ||--o{ code_snippets : "has"
users ||--o{ large_files : "has"
users ||--o{ bookmarks : "has"
users ||--o{ collections : "has"
users ||--o{ backups : "has"
code_snippets ||--o{ bookmarks : "referenced_by"
code_snippets ||--o{ collections : "referenced_by"
large_files ||--o{ bookmarks : "referenced_by"
large_files ||--o{ collections : "referenced_by"
הערות:
bookmarks.file_idיכול להצביע עלcode_snippets._idאוlarge_files._idcollections.items[].file_idיכול להצביע עלcode_snippets._idאוlarge_files._idאין Foreign Key constraints ב-MongoDB - יש לבדוק תקינות בקוד
סכמת מערכת Observability - התראות ומטריקות
הדיאגרמה הבאה מציגה את מבנה הנתונים עבור מערכת המוניטור וההתראות:
erDiagram
METRICS ||--o{ ALERTS : triggers
METRICS {
string request_id PK
datetime timestamp
string metric_type
string operation
string status
float value
json metadata
string user_id FK
string error_code
}
ALERTS ||--o{ ALERT_ACTIONS : has
ALERTS {
string alert_id PK
datetime timestamp
string severity
string alert_type
string title
text description
array affected_services
json metrics_snapshot
datetime resolved_at
}
ALERT_ACTIONS {
int id PK
string alert_id FK
string action_type
text suggestion
string runbook_url
}
CODE_DUPLICATES ||--|| FILES : references
CODE_DUPLICATES {
string duplicate_id PK
datetime detection_time
string file1
int file1_start_line
int file1_end_line
string file2
int file2_start_line
int file2_end_line
float similarity_score
string detection_method
}
METRICS_AGGREGATES ||--|| METRICS : summarizes
METRICS_AGGREGATES {
int id PK
datetime period_start
datetime period_end
string operation
int total_requests
int success_count
int error_count
float avg_latency_ms
float p95_latency_ms
}
הסבר הטבלאות:
METRICS: מטריקות גולמיות לכל בקשה (latency, status, errors)
ALERTS: התראות שנוצרו על סמך ניתוח המטריקות
ALERT_ACTIONS: פעולות מומלצות לכל התראה (suggestions, runbooks)
CODE_DUPLICATES: זיהוי קוד כפול בין קבצים
METRICS_AGGREGATES: אגרגציות תקופתיות של המטריקות
Best Practices
תמיד לסנן לפי user_id - בידוד נתונים בין משתמשים
שימוש ב-is_active - לא למחוק פיזית, רק soft delete
אינדקסים - להוסיף אינדקסים לפי שאילתות נפוצות
Validation - לוודא תקינות נתונים לפני שמירה
Timestamps - תמיד לעדכן
updated_atבעדכון