@עידו300
זה בהחלט שיפור. עברת משלב "גן חובה" לכיתה א'.
הכנסת try/catch/finally כמו שצריך, יש לך טרנזקציה, ויש ניסיון לייצר סכימה. כל הכבוד.
אבל...
הקוד הזה עדיין יקרוס (Crash), הוא מכיל באג לוגי חמור מול הדרישות המקוריות, והוא חושף אותך לסיכון SQL Injection ברמת התכנון.
בוא ננתח למה ה"שיפור" הזה עדיין לא עובר Code Review אצלי:
1. קריסה מובטחת (Runtime Error) 
תסתכל על השורה הזו ב-createAdObject:
cities: validateResult.data.area || validateResult.data.city || ["all"]
אם המשתמש שלח area (שהוא מחרוזת, למשל "Tel Aviv"), אז createResult.cities יהיה שווה ל-"Tel Aviv".
ואז ב-addAdNEw אתה עושה:
citiesToInsert = createResult.cities.map(city => ...)
בום. לפונקציה map אין קיום על String (בצורה הזו). הקוד יזרוק TypeError והבקשה תיפול.
מתכנת רציני מוודא שתמיד חוזר מערך (Array), גם אם יש איבר אחד.
2. משתנה גלובלי דולף (Strict Mode Violation) 
citiesToInsert = createResult.cities.map(...)
איפה ה-const? איפה ה-let?
במודולים של JS (שבהם export קיים), הקוד רץ אוטומטית ב-Strict Mode.
שימוש במשתנה ללא הצהרה יזרוק ReferenceError: citiesToInsert is not defined. השרת שלך נפל שוב.
3. התעלמות מהדרישות העסקיות (Business Logic Regression) 
זוכר את הקוד המקורי?
- אם זה "מחפש" -> העמודה היא
max_payment.
- אם זה "מפרסם" -> העמודה היא
min_payment.
בקוד החדש החלטת על דעת עצמך:
...(validateResult.data.min_max_price && { 'min_max_price': ... })
אתה מנסה לדחוף לעמודה בשם min_max_price. העמודה הזו לא קיימת במסד הנתונים! ה-SQL יחזיר שגיאה: Unknown column 'min_max_price'.
שיפרת את המבנה, אבל הרסת את הלוגיקה. קוד יפה שלא עובד הוא חסר ערך.
4. פרצת אבטחה ב-insertRecord (SQL Injection via Design) 
אתה כותב פונקציה גנרית יפה:
const columns = Object.keys(records[0]);
const sql = `INSERT INTO ${tableName} (${columns.join(', ')}) ...`
כרגע, במקרה, האובייקט שאתה שולח נוצר ידנית ב-createAdObject.
אבל הפונקציה insertRecord היא גנרית. מחר מתכנת זוטר ישתמש בה וישלח אובייקט req.body ישירות מהמשתמש.
אם המשתמש ישלח מפתח בשם id) VALUES (1); DROP TABLE ads; --, והפונקציה שלך משרשרת את המפתחות ישר ל-SQL... הלך ה-DB.
כלל ברזל: לעולם אל תסמוך על מפתחות של אובייקטים (Object Keys) כשמות עמודות ב-SQL ללא בדיקה מול רשימה מותרת (Allowlist) בתוך הפונקציה הגנרית.
5. צימוד (Coupling) בין שם המשתנה לשם העמודה
בסכמה שלך:
min_max_price: {validate: checkNumber, required: false},
אבל ב-DB ובקלט המקורי זה נקרא price.
היצרת ערבוב: הפונקציה שלך מצפה לקלט בשם min_max_price מהלקוח? או שהלקוח שולח price ואתה ממפה את זה? הקוד שלך מניח שהלקוח שולח min_max_price. שינית את ה-API בלי לשים לב.
התיקון (כדי שזה באמת יעבוד)
הנה איך הקוד צריך להיראות כדי לפתור את כל הבעיות הנ"ל, תוך שמירה על המבנה הנקי שניסית ליצור:
// 1. מיפוי ברור בין שדות ה-API לעמודות ה-DB
const FIELD_MAPPING = {
phone: 'phone',
type: 'type',
profession: 'profession',
path: 'recording_path',
// לוגיקה דינמית תטופל בקוד
};
function createAdObject(params) {
const validateResult = validateAdParams(params); // נניח שזה תוקן ומחזיר price
if (validateResult.status === "error") return validateResult;
const data = validateResult.data;
// בניית האובייקט ל-DB עם שמות העמודות הנכונים
let dbRecord = {
phone: data.phone,
type: data.type,
profession: data.profession,
};
if (data.path) dbRecord.recording_path = data.path;
// 2. תיקון הלוגיקה העסקית (שמות עמודות דינמיים)
// הערה: אני מניח שהלקוח שלח 'price', כפי שהיה במקור
if (data.price) {
const priceCol = (data.type === "מחפש") ? "max_payment" : "min_payment";
dbRecord[priceCol] = data.price;
}
if (data.type === "מחפש" && data.ringMode) {
dbRecord.ring_mode = data.ringMode;
}
// 3. תיקון הבאג של המערך (Normalization)
let cities = ["all"];
if (data.area) {
cities = [data.area]; // עטיפה במערך!
} else if (Array.isArray(data.city)) {
cities = data.city;
}
return { status: "success", data: dbRecord, cities };
}
async function insertRecord(connection, tableName, data) {
const allowedTables = ['ads', 'ad_cities'];
if (!allowedTables.includes(tableName)) throw new Error("Invalid table name");
const records = Array.isArray(data) ? data : [data];
if (records.length === 0) return;
// 4. אבטחה: בדיקה שהעמודות הן חוקיות (White-listing)
// במערכת אמיתית, היינו מחזיקים רשימת עמודות מותרות לכל טבלה
const validColumns = ['phone', 'type', 'profession', 'recording_path', 'max_payment', 'min_payment', 'ring_mode', 'ad_id', 'city'];
const inputColumns = Object.keys(records[0]);
// מוודאים שכל העמודות שהתקבלו קיימות ברשימה המותרת
const invalidCols = inputColumns.filter(col => !validColumns.includes(col));
if (invalidCols.length > 0) throw new Error(`Invalid columns: ${invalidCols.join(',')}`);
// המשך הקוד הרגיל...
const placeholders = records.map(() => `(${inputColumns.map(() => '?').join(', ')})`).join(', ');
const values = records.flatMap(record => inputColumns.map(col => record[col]));
// כאן זה כבר בטוח כי וידאנו שהעמודות ברשימה המותרת
const sql = `INSERT INTO ${tableName} (${inputColumns.join(', ')}) VALUES ${placeholders}`;
return connection.execute(sql, values);
}
export async function addAdNEw(params) {
const createResult = createAdObject(params);
if (createResult.status === "error") return createResult;
let connection;
try {
connection = await pool.getConnection();
await connection.beginTransaction();
const [result] = await insertRecord(connection, 'ads', createResult.data);
const adId = result.insertId;
// 5. תיקון ה-ReferenceError
const citiesToInsert = createResult.cities.map(city => ({ad_id: adId, city}));
await insertRecord(connection, 'ad_cities', citiesToInsert);
await connection.commit();
return { status: "success", message: "Ad added successfully", adId };
} catch (error) {
if (connection) await connection.rollback(); // שים לב: רצוי לא לזרוק שגיאה החוצה בלי לוג או טיפול
console.error("DB Transaction Error:", error);
return { status: "error", message: "Database error" };
} finally {
if (connection) connection.release();
}
}
סיכום:
החבר שלך בכיוון הנכון מבחינת מבנה (Structure), אבל הוא נופל בפרטים הקטנים והקטלניים (Details). תגיד לו שאנקל בוב אומר: "God is in the details". קוד שלא מתחשב במקרי קצה (כמו מחרוזת במקום מערך) הוא לא קוד מקצועי.