/// /** * Fondations multi-agents : recherches sauvegardées, alertes, veille annonces, * sources de flux, transactions secteur (stub DVF), courriers prospection. */ migrate( (app) => { const usersCol = app.findCollectionByNameOrId("users"); let usersId = ""; if (usersCol) { const a = usersCol.id != null && String(usersCol.id) !== "" ? usersCol.id : null; const b = usersCol.Id != null && String(usersCol.Id) !== "" ? usersCol.Id : null; usersId = String(a != null ? a : b != null ? b : "").trim(); } if (!usersId) { throw new Error("migration 1760000000: collection users introuvable ou id vide"); } function findExistingCollection(name) { try { return app.findCollectionByNameOrId(name); } catch (_) {} try { const all = app.findAllCollections(); const want = String(name).toLowerCase(); for (let i = 0; i < all.length; i++) { const c = all[i]; if (c && c.name && String(c.name).toLowerCase() === want) { return c; } } } catch (_) {} return null; } function loadOrCreate(name, factory) { const existing = findExistingCollection(name); if (existing != null) { return existing; } try { const col = factory(); app.save(col); return col; } catch (err) { const msg = String(err && err.value ? err.value : err && err.message ? err.message : err); if (msg.includes("unique") || msg.includes("Unique")) { const again = findExistingCollection(name); if (again != null) { return again; } } throw err; } } const ownRecords = '@request.auth.id != "" && user.id = @request.auth.id'; const authOnly = '@request.auth.id != ""'; function addUserRules(col) { col.listRule = ownRecords; col.viewRule = ownRecords; col.createRule = authOnly; col.updateRule = ownRecords; col.deleteRule = ownRecords; } function userRel() { return new RelationField({ name: "user", required: true, collectionId: usersId, maxSelect: 1, cascadeDelete: true, }); } loadOrCreate("recherches_sauvegardees", () => { const col = new Collection({ name: "recherches_sauvegardees", type: "base" }); col.fields.add(userRel()); col.fields.add(new TextField({ name: "nom", required: true })); col.fields.add(new TextField({ name: "critere_json", required: false })); col.fields.add(new BoolField({ name: "actif", required: false })); addUserRules(col); return col; }); const rechRef = findExistingCollection("recherches_sauvegardees"); const rechId = rechRef ? String(rechRef.id || rechRef.Id || "").trim() : ""; if (!rechId) { throw new Error("migration 1760000000: recherches_sauvegardees introuvable après création"); } loadOrCreate("alertes_recherche", () => { const col = new Collection({ name: "alertes_recherche", type: "base" }); col.fields.add(userRel()); col.fields.add( new RelationField({ name: "recherche", required: false, collectionId: rechId, maxSelect: 1, cascadeDelete: false, }), ); col.fields.add(new TextField({ name: "nom", required: true })); col.fields.add( new SelectField({ name: "canal", required: true, maxSelect: 1, values: ["in_app", "email", "push"], }), ); col.fields.add(new BoolField({ name: "actif", required: false })); col.fields.add(new TextField({ name: "derniere_verification", required: false })); col.fields.add(new NumberField({ name: "dernier_nb_resultats", required: false })); addUserRules(col); return col; }); loadOrCreate("annonces_veille", () => { const col = new Collection({ name: "annonces_veille", type: "base" }); col.fields.add(userRel()); col.fields.add(new TextField({ name: "titre", required: true })); col.fields.add(new TextField({ name: "url", required: false })); col.fields.add(new TextField({ name: "source", required: false })); col.fields.add(new NumberField({ name: "prix", required: false })); col.fields.add(new NumberField({ name: "surface", required: false })); col.fields.add(new TextField({ name: "code_postal", required: false })); col.fields.add(new TextField({ name: "ville", required: false })); col.fields.add(new TextField({ name: "empreinte", required: false })); col.fields.add( new SelectField({ name: "statut", required: true, maxSelect: 1, values: ["nouveau", "vu", "ecarte", "raccroche"], }), ); addUserRules(col); return col; }); loadOrCreate("flux_sources", () => { const col = new Collection({ name: "flux_sources", type: "base" }); col.fields.add(userRel()); col.fields.add(new TextField({ name: "nom", required: true })); col.fields.add( new SelectField({ name: "type", required: true, maxSelect: 1, values: ["api", "manuel", "csv"], }), ); col.fields.add(new TextField({ name: "notes", required: false })); col.fields.add(new BoolField({ name: "actif", required: false })); addUserRules(col); return col; }); loadOrCreate("transactions_secteur", () => { const col = new Collection({ name: "transactions_secteur", type: "base" }); col.fields.add(userRel()); col.fields.add(new TextField({ name: "libelle", required: true })); col.fields.add(new TextField({ name: "code_insee", required: false })); col.fields.add(new NumberField({ name: "annee", required: false })); col.fields.add(new NumberField({ name: "prix_m2_median", required: false })); col.fields.add(new NumberField({ name: "nb_ventes", required: false })); col.fields.add( new SelectField({ name: "source", required: true, maxSelect: 1, values: ["manuel", "dvf_import", "api_tiers"], }), ); col.fields.add(new TextField({ name: "detail_json", required: false })); addUserRules(col); return col; }); loadOrCreate("courriers_immobilier", () => { const col = new Collection({ name: "courriers_immobilier", type: "base" }); col.fields.add(userRel()); col.fields.add(new TextField({ name: "titre", required: true })); col.fields.add(new TextField({ name: "corps", required: false })); col.fields.add( new SelectField({ name: "kind", required: true, maxSelect: 1, values: ["prospection", "annonce_agence", "relance"], }), ); col.fields.add( new SelectField({ name: "etat", required: true, maxSelect: 1, values: ["brouillon", "pret"], }), ); addUserRules(col); return col; }); }, (app) => { const names = [ "alertes_recherche", "annonces_veille", "flux_sources", "transactions_secteur", "courriers_immobilier", "recherches_sauvegardees", ]; for (const name of names) { try { app.delete(app.findCollectionByNameOrId(name)); } catch (_) {} } }, );