666 lines
26 KiB
JavaScript
666 lines
26 KiB
JavaScript
/// <reference path="../pb_data/types.d.ts" />
|
|||
|
|
//
|
||
|
|
// Schema iniziale PocketBase per CrAPP — solo ambiente locale (Docker).
|
||
|
|
//
|
||
|
|
// Non è la riproduzione 1:1 delle 27 migration Supabase in supabase/migrations/: rappresenta
|
||
|
|
// lo STATO FINALE attuale dello schema (fonte: docs/DATABASE.md + lettura diretta delle
|
||
|
|
// migration Supabase più recenti), riscritto nel modello PocketBase (collection + campi +
|
||
|
|
// API rules). Le tabelle Supabase non più usate dal codice (`giocatori`, `eventi`/`presenze`
|
||
|
|
// "nuovo modello" mai adottato, `promemoria_push` deprecata) non vengono portate.
|
||
|
|
//
|
||
|
|
// `user_roles` non diventa una collection: il ruolo admin/user è un campo diretto sulla
|
||
|
|
// collection auth nativa `users` (decisione 3 del piano di migrazione).
|
||
|
|
//
|
||
|
|
// La logica procedurale che in Postgres viveva in trigger/funzioni (claim-slot su
|
||
|
|
// giocatori_squadra, blocco autovoto, convocati/pagelle_chiuse, cascata su cancellazione
|
||
|
|
// evento, risposto_il immutabile) NON sta qui: è in pb_hooks/*.pb.js, perché le API rules di
|
||
|
|
// PocketBase sono espressioni, non codice procedurale.
|
||
|
|
|
||
|
|
migrate(
|
||
|
|
(app) => {
|
||
|
|
// ---------------------------------------------------------------------------------------
|
||
|
|
// users (collection auth nativa): aggiunge il campo ruolo che sostituisce user_roles.
|
||
|
|
// ---------------------------------------------------------------------------------------
|
||
|
|
const users = app.findCollectionByNameOrId("users");
|
||
|
|
users.fields.add(
|
||
|
|
new Field({
|
||
|
|
name: "role",
|
||
|
|
type: "select",
|
||
|
|
required: true,
|
||
|
|
values: ["user", "admin"],
|
||
|
|
maxSelect: 1,
|
||
|
|
}),
|
||
|
|
);
|
||
|
|
// Ogni utente autenticato legge il proprio record; l'admin (verificato lato hook al primo
|
||
|
|
// login, poi qui) può leggere tutti. Il ruolo lo scrive solo chi è già admin.
|
||
|
|
users.listRule = "id = @request.auth.id || @request.auth.role = 'admin'";
|
||
|
|
users.viewRule = "id = @request.auth.id || @request.auth.role = 'admin'";
|
||
|
|
users.updateRule = "id = @request.auth.id";
|
||
|
|
app.save(users);
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------------------------------
|
||
|
|
// giocatori_squadra — anagrafica operativa della squadra (DD-015, DD-016, DD-018)
|
||
|
|
// ---------------------------------------------------------------------------------------
|
||
|
|
const giocatori = new Collection({
|
||
|
|
name: "giocatori_squadra",
|
||
|
|
type: "base",
|
||
|
|
fields: [
|
||
|
|
// Id testuali corti (g1..gN, come in Supabase): il vincolo di default di PocketBase
|
||
|
|
// (minimo 15 caratteri) va rilassato esplicitamente.
|
||
|
|
{ name: "id", type: "text", min: 1, max: 40, pattern: "^g[0-9]+$" },
|
||
|
|
{ name: "nome", type: "text", required: true },
|
||
|
|
{ name: "cognome", type: "text", required: true },
|
||
|
|
{ name: "numero", type: "number", required: true, min: 1 },
|
||
|
|
{ name: "ruolo", type: "text", required: true },
|
||
|
|
{
|
||
|
|
name: "auth_user_id",
|
||
|
|
type: "relation",
|
||
|
|
required: false,
|
||
|
|
collectionId: users.id,
|
||
|
|
maxSelect: 1,
|
||
|
|
},
|
||
|
|
{ name: "email", type: "email", required: false },
|
||
|
|
{ name: "attivo", type: "bool", required: false },
|
||
|
|
{ name: "numero_tessera", type: "text", required: false },
|
||
|
|
{ name: "data_tessera", type: "date", required: false },
|
||
|
|
{ name: "created", type: "autodate", onCreate: true },
|
||
|
|
{ name: "updated", type: "autodate", onCreate: true, onUpdate: true },
|
||
|
|
],
|
||
|
|
indexes: [
|
||
|
|
"CREATE UNIQUE INDEX idx_giocatori_squadra_auth_user ON giocatori_squadra (auth_user_id) WHERE auth_user_id != ''",
|
||
|
|
"CREATE UNIQUE INDEX idx_giocatori_squadra_email ON giocatori_squadra (email) WHERE email != ''",
|
||
|
|
],
|
||
|
|
// Lettura: rosa attiva a tutti gli autenticati, tutta la rosa agli admin (DD-015/M1).
|
||
|
|
listRule: "@request.auth.id != '' && (attivo = true || @request.auth.role = 'admin')",
|
||
|
|
viewRule: "@request.auth.id != '' && (attivo = true || @request.auth.role = 'admin')",
|
||
|
|
// Creazione slot: solo admin.
|
||
|
|
createRule: "@request.auth.role = 'admin'",
|
||
|
|
// Aggiornamento: admin senza vincoli, oppure claim di uno slot libero — il resto
|
||
|
|
// (che il claim tocchi solo auth_user_id e rispetti l'email) lo valida l'hook
|
||
|
|
// giocatori_squadra_claim.pb.js, la rule qui apre solo la porta.
|
||
|
|
updateRule: "@request.auth.role = 'admin' || (@request.auth.id != '' && auth_user_id = '')",
|
||
|
|
deleteRule: "@request.auth.role = 'admin'",
|
||
|
|
});
|
||
|
|
app.save(giocatori);
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------------------------------
|
||
|
|
// profili_giocatore — dati personali/documento/certificato, 1:1 con giocatori_squadra
|
||
|
|
// ---------------------------------------------------------------------------------------
|
||
|
|
const profili = new Collection({
|
||
|
|
name: "profili_giocatore",
|
||
|
|
type: "base",
|
||
|
|
fields: [
|
||
|
|
{
|
||
|
|
name: "giocatore",
|
||
|
|
type: "relation",
|
||
|
|
required: true,
|
||
|
|
collectionId: giocatori.id,
|
||
|
|
maxSelect: 1,
|
||
|
|
cascadeDelete: true,
|
||
|
|
},
|
||
|
|
{ name: "data_nascita", type: "date", required: false },
|
||
|
|
{ name: "luogo_nascita", type: "text", required: false },
|
||
|
|
{ name: "indirizzo", type: "text", required: false },
|
||
|
|
{ name: "telefono", type: "text", required: false },
|
||
|
|
{ name: "email", type: "email", required: false },
|
||
|
|
{
|
||
|
|
name: "documento_tipo",
|
||
|
|
type: "select",
|
||
|
|
required: false,
|
||
|
|
maxSelect: 1,
|
||
|
|
values: ["Carta d'identità", "Passaporto", "Patente"],
|
||
|
|
},
|
||
|
|
{ name: "documento_numero", type: "text", required: false },
|
||
|
|
{ name: "documento_rilasciato_da", type: "text", required: false },
|
||
|
|
{ name: "documento_emissione", type: "date", required: false },
|
||
|
|
{ name: "documento_scadenza", type: "date", required: false },
|
||
|
|
{
|
||
|
|
name: "documento_fronte",
|
||
|
|
type: "file",
|
||
|
|
required: false,
|
||
|
|
protected: true,
|
||
|
|
maxSelect: 1,
|
||
|
|
maxSize: 10485760,
|
||
|
|
mimeTypes: ["image/jpeg", "image/png", "image/webp", "application/pdf"],
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "documento_retro",
|
||
|
|
type: "file",
|
||
|
|
required: false,
|
||
|
|
protected: true,
|
||
|
|
maxSelect: 1,
|
||
|
|
maxSize: 10485760,
|
||
|
|
mimeTypes: ["image/jpeg", "image/png", "image/webp", "application/pdf"],
|
||
|
|
},
|
||
|
|
{ name: "certificato_scadenza", type: "date", required: false },
|
||
|
|
{
|
||
|
|
name: "certificato",
|
||
|
|
type: "file",
|
||
|
|
required: false,
|
||
|
|
protected: true,
|
||
|
|
maxSelect: 1,
|
||
|
|
maxSize: 10485760,
|
||
|
|
mimeTypes: ["image/jpeg", "image/png", "image/webp", "application/pdf"],
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "foto",
|
||
|
|
type: "file",
|
||
|
|
required: false,
|
||
|
|
protected: true,
|
||
|
|
maxSelect: 1,
|
||
|
|
maxSize: 10485760,
|
||
|
|
mimeTypes: ["image/jpeg", "image/png", "image/webp"],
|
||
|
|
},
|
||
|
|
{ name: "created", type: "autodate", onCreate: true },
|
||
|
|
{ name: "updated", type: "autodate", onCreate: true, onUpdate: true },
|
||
|
|
],
|
||
|
|
indexes: [
|
||
|
|
"CREATE UNIQUE INDEX idx_profili_giocatore_giocatore ON profili_giocatore (giocatore)",
|
||
|
|
],
|
||
|
|
// Documenti sanitari/identità: mai pubblici (DD-016 regola 4). Solo il giocatore
|
||
|
|
// proprietario o un admin, sia per i campi sia per i file allegati (le API rules
|
||
|
|
// governano anche il download dei file in PocketBase, non serve un bucket a parte).
|
||
|
|
listRule:
|
||
|
|
"@request.auth.id != '' && (giocatore.auth_user_id = @request.auth.id || @request.auth.role = 'admin')",
|
||
|
|
viewRule:
|
||
|
|
"@request.auth.id != '' && (giocatore.auth_user_id = @request.auth.id || @request.auth.role = 'admin')",
|
||
|
|
createRule:
|
||
|
|
"@request.auth.id != '' && (giocatore.auth_user_id = @request.auth.id || @request.auth.role = 'admin')",
|
||
|
|
updateRule:
|
||
|
|
"@request.auth.id != '' && (giocatore.auth_user_id = @request.auth.id || @request.auth.role = 'admin')",
|
||
|
|
deleteRule: "@request.auth.role = 'admin'",
|
||
|
|
});
|
||
|
|
app.save(profili);
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------------------------------
|
||
|
|
// avatar_giocatori — foto profilo pubbliche (M6 avatar-giocatori). Collection separata
|
||
|
|
// da giocatori_squadra: la policy è "chiunque autenticato carica/sostituisce/elimina
|
||
|
|
// qualsiasi avatar" (nessun controllo per-proprietario, DD in docs/DATABASE.md), molto
|
||
|
|
// più permissiva delle regole di giocatori_squadra — mescolarle avrebbe o indebolito
|
||
|
|
// quelle o impedito il caricamento a chi non è admin.
|
||
|
|
// ---------------------------------------------------------------------------------------
|
||
|
|
const avatar = new Collection({
|
||
|
|
name: "avatar_giocatori",
|
||
|
|
type: "base",
|
||
|
|
fields: [
|
||
|
|
{
|
||
|
|
name: "giocatore",
|
||
|
|
type: "relation",
|
||
|
|
required: true,
|
||
|
|
collectionId: giocatori.id,
|
||
|
|
maxSelect: 1,
|
||
|
|
cascadeDelete: true,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "foto",
|
||
|
|
type: "file",
|
||
|
|
required: false,
|
||
|
|
maxSelect: 1,
|
||
|
|
maxSize: 5242880,
|
||
|
|
mimeTypes: ["image/jpeg"],
|
||
|
|
},
|
||
|
|
{ name: "created", type: "autodate", onCreate: true },
|
||
|
|
{ name: "updated", type: "autodate", onCreate: true, onUpdate: true },
|
||
|
|
],
|
||
|
|
indexes: [
|
||
|
|
"CREATE UNIQUE INDEX idx_avatar_giocatori_giocatore ON avatar_giocatori (giocatore)",
|
||
|
|
],
|
||
|
|
// Bucket pubblico in Supabase: leggibile da chiunque, anche senza login.
|
||
|
|
listRule: "",
|
||
|
|
viewRule: "",
|
||
|
|
createRule: "@request.auth.id != ''",
|
||
|
|
updateRule: "@request.auth.id != ''",
|
||
|
|
deleteRule: "@request.auth.id != ''",
|
||
|
|
});
|
||
|
|
app.save(avatar);
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------------------------------
|
||
|
|
// eventi_app — eventi gestionali (partite/allenamenti/eventi squadra)
|
||
|
|
// ---------------------------------------------------------------------------------------
|
||
|
|
const eventi = new Collection({
|
||
|
|
name: "eventi_app",
|
||
|
|
type: "base",
|
||
|
|
fields: [
|
||
|
|
// Id testuali corti (e1..eN, come in Supabase).
|
||
|
|
{ name: "id", type: "text", min: 1, max: 40, pattern: "^e[0-9]+$" },
|
||
|
|
{
|
||
|
|
name: "tipo",
|
||
|
|
type: "select",
|
||
|
|
required: true,
|
||
|
|
maxSelect: 1,
|
||
|
|
values: ["partita", "allenamento", "evento"],
|
||
|
|
},
|
||
|
|
{ name: "titolo", type: "text", required: true },
|
||
|
|
{ name: "luogo", type: "text", required: false },
|
||
|
|
{ name: "data", type: "date", required: true },
|
||
|
|
{ name: "ora", type: "text", required: false },
|
||
|
|
{ name: "note", type: "text", required: false },
|
||
|
|
{
|
||
|
|
name: "convocati",
|
||
|
|
type: "relation",
|
||
|
|
required: false,
|
||
|
|
collectionId: giocatori.id,
|
||
|
|
maxSelect: 999,
|
||
|
|
},
|
||
|
|
{ name: "campionato", type: "bool", required: false },
|
||
|
|
{ name: "casa", type: "bool", required: false },
|
||
|
|
{ name: "pagelle_chiuse", type: "bool", required: false },
|
||
|
|
{ name: "created", type: "autodate", onCreate: true },
|
||
|
|
{ name: "updated", type: "autodate", onCreate: true, onUpdate: true },
|
||
|
|
],
|
||
|
|
// Lettura aperta a tutti gli autenticati; scrittura solo admin (rotta /eventi già
|
||
|
|
// riservata — M11/DD-023).
|
||
|
|
listRule: "@request.auth.id != ''",
|
||
|
|
viewRule: "@request.auth.id != ''",
|
||
|
|
createRule: "@request.auth.role = 'admin'",
|
||
|
|
updateRule: "@request.auth.role = 'admin'",
|
||
|
|
deleteRule: "@request.auth.role = 'admin'",
|
||
|
|
});
|
||
|
|
app.save(eventi);
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------------------------------
|
||
|
|
// risposte_presenze — risposte dei giocatori agli eventi
|
||
|
|
// ---------------------------------------------------------------------------------------
|
||
|
|
const presenze = new Collection({
|
||
|
|
name: "risposte_presenze",
|
||
|
|
type: "base",
|
||
|
|
fields: [
|
||
|
|
{
|
||
|
|
name: "evento",
|
||
|
|
type: "relation",
|
||
|
|
required: true,
|
||
|
|
collectionId: eventi.id,
|
||
|
|
maxSelect: 1,
|
||
|
|
cascadeDelete: true,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "giocatore",
|
||
|
|
type: "relation",
|
||
|
|
required: true,
|
||
|
|
collectionId: giocatori.id,
|
||
|
|
maxSelect: 1,
|
||
|
|
},
|
||
|
|
{ name: "stato", type: "text", required: true },
|
||
|
|
// Istante della PRIMA risposta (M9): valorizzato e reso immutabile dall'hook
|
||
|
|
// risposte_presenze_immutabili.pb.js, non scrivibile direttamente dal client.
|
||
|
|
{ name: "risposto_il", type: "date", required: false },
|
||
|
|
{ name: "created", type: "autodate", onCreate: true },
|
||
|
|
{ name: "updated", type: "autodate", onCreate: true, onUpdate: true },
|
||
|
|
],
|
||
|
|
indexes: [
|
||
|
|
"CREATE UNIQUE INDEX idx_risposte_presenze_evento_giocatore ON risposte_presenze (evento, giocatore)",
|
||
|
|
],
|
||
|
|
listRule: "@request.auth.id != ''",
|
||
|
|
viewRule: "@request.auth.id != ''",
|
||
|
|
createRule:
|
||
|
|
"@request.auth.role = 'admin' || (@request.auth.id != '' && giocatore.auth_user_id = @request.auth.id)",
|
||
|
|
updateRule:
|
||
|
|
"@request.auth.role = 'admin' || (@request.auth.id != '' && giocatore.auth_user_id = @request.auth.id)",
|
||
|
|
deleteRule:
|
||
|
|
"@request.auth.role = 'admin' || (@request.auth.id != '' && giocatore.auth_user_id = @request.auth.id)",
|
||
|
|
});
|
||
|
|
app.save(presenze);
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------------------------------
|
||
|
|
// cacche_partita — sondaggio pre-partita (statistiche/badge segreti)
|
||
|
|
// ---------------------------------------------------------------------------------------
|
||
|
|
const cacche = new Collection({
|
||
|
|
name: "cacche_partita",
|
||
|
|
type: "base",
|
||
|
|
fields: [
|
||
|
|
{
|
||
|
|
name: "evento",
|
||
|
|
type: "relation",
|
||
|
|
required: true,
|
||
|
|
collectionId: eventi.id,
|
||
|
|
maxSelect: 1,
|
||
|
|
cascadeDelete: true,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "giocatore",
|
||
|
|
type: "relation",
|
||
|
|
required: true,
|
||
|
|
collectionId: giocatori.id,
|
||
|
|
maxSelect: 1,
|
||
|
|
},
|
||
|
|
{ name: "quantita", type: "number", required: true, min: 0, max: 10 },
|
||
|
|
{ name: "created", type: "autodate", onCreate: true },
|
||
|
|
{ name: "updated", type: "autodate", onCreate: true, onUpdate: true },
|
||
|
|
],
|
||
|
|
indexes: [
|
||
|
|
"CREATE UNIQUE INDEX idx_cacche_partita_evento_giocatore ON cacche_partita (evento, giocatore)",
|
||
|
|
],
|
||
|
|
listRule: "@request.auth.id != ''",
|
||
|
|
viewRule: "@request.auth.id != ''",
|
||
|
|
createRule:
|
||
|
|
"@request.auth.role = 'admin' || (@request.auth.id != '' && giocatore.auth_user_id = @request.auth.id)",
|
||
|
|
updateRule:
|
||
|
|
"@request.auth.role = 'admin' || (@request.auth.id != '' && giocatore.auth_user_id = @request.auth.id)",
|
||
|
|
deleteRule:
|
||
|
|
"@request.auth.role = 'admin' || (@request.auth.id != '' && giocatore.auth_user_id = @request.auth.id)",
|
||
|
|
});
|
||
|
|
app.save(cacche);
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------------------------------
|
||
|
|
// mvp_voti / pagelle_voti / badge_social_voti — votazioni tra compagni
|
||
|
|
//
|
||
|
|
// Ownership (votante = chi scrive) è espressa qui via API rule. Autovoto e vincoli
|
||
|
|
// "convocato all'evento" / "pagelle non chiuse" (M12/M13) NON sono esprimibili in modo
|
||
|
|
// sicuro come semplice espressione di rule su relazioni multiple: li applica l'hook
|
||
|
|
// pb_hooks/voti_convocati.pb.js su create/update, per tutte e tre le collection.
|
||
|
|
// ---------------------------------------------------------------------------------------
|
||
|
|
const mvpVoti = new Collection({
|
||
|
|
name: "mvp_voti",
|
||
|
|
type: "base",
|
||
|
|
fields: [
|
||
|
|
{
|
||
|
|
name: "evento",
|
||
|
|
type: "relation",
|
||
|
|
required: true,
|
||
|
|
collectionId: eventi.id,
|
||
|
|
maxSelect: 1,
|
||
|
|
cascadeDelete: true,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "votante",
|
||
|
|
type: "relation",
|
||
|
|
required: true,
|
||
|
|
collectionId: giocatori.id,
|
||
|
|
maxSelect: 1,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "votato",
|
||
|
|
type: "relation",
|
||
|
|
required: true,
|
||
|
|
collectionId: giocatori.id,
|
||
|
|
maxSelect: 1,
|
||
|
|
},
|
||
|
|
{ name: "votato_nome", type: "text", required: true },
|
||
|
|
{ name: "created", type: "autodate", onCreate: true },
|
||
|
|
{ name: "updated", type: "autodate", onCreate: true, onUpdate: true },
|
||
|
|
],
|
||
|
|
indexes: ["CREATE UNIQUE INDEX idx_mvp_voti_evento_votante ON mvp_voti (evento, votante)"],
|
||
|
|
listRule: "@request.auth.id != ''",
|
||
|
|
viewRule: "@request.auth.id != ''",
|
||
|
|
createRule:
|
||
|
|
"@request.auth.role = 'admin' || (@request.auth.id != '' && votante.auth_user_id = @request.auth.id)",
|
||
|
|
updateRule:
|
||
|
|
"@request.auth.role = 'admin' || (@request.auth.id != '' && votante.auth_user_id = @request.auth.id)",
|
||
|
|
deleteRule:
|
||
|
|
"@request.auth.role = 'admin' || (@request.auth.id != '' && votante.auth_user_id = @request.auth.id)",
|
||
|
|
});
|
||
|
|
app.save(mvpVoti);
|
||
|
|
|
||
|
|
const pagelleVoti = new Collection({
|
||
|
|
name: "pagelle_voti",
|
||
|
|
type: "base",
|
||
|
|
fields: [
|
||
|
|
{
|
||
|
|
name: "evento",
|
||
|
|
type: "relation",
|
||
|
|
required: true,
|
||
|
|
collectionId: eventi.id,
|
||
|
|
maxSelect: 1,
|
||
|
|
cascadeDelete: true,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "votante",
|
||
|
|
type: "relation",
|
||
|
|
required: true,
|
||
|
|
collectionId: giocatori.id,
|
||
|
|
maxSelect: 1,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "votato",
|
||
|
|
type: "relation",
|
||
|
|
required: true,
|
||
|
|
collectionId: giocatori.id,
|
||
|
|
maxSelect: 1,
|
||
|
|
},
|
||
|
|
{ name: "voto", type: "number", required: true, min: 1, max: 10 },
|
||
|
|
{ name: "created", type: "autodate", onCreate: true },
|
||
|
|
{ name: "updated", type: "autodate", onCreate: true, onUpdate: true },
|
||
|
|
],
|
||
|
|
indexes: [
|
||
|
|
"CREATE UNIQUE INDEX idx_pagelle_voti_evento_votante_votato ON pagelle_voti (evento, votante, votato)",
|
||
|
|
],
|
||
|
|
listRule: "@request.auth.id != ''",
|
||
|
|
viewRule: "@request.auth.id != ''",
|
||
|
|
createRule:
|
||
|
|
"@request.auth.role = 'admin' || (@request.auth.id != '' && votante.auth_user_id = @request.auth.id)",
|
||
|
|
updateRule:
|
||
|
|
"@request.auth.role = 'admin' || (@request.auth.id != '' && votante.auth_user_id = @request.auth.id)",
|
||
|
|
deleteRule:
|
||
|
|
"@request.auth.role = 'admin' || (@request.auth.id != '' && votante.auth_user_id = @request.auth.id)",
|
||
|
|
});
|
||
|
|
app.save(pagelleVoti);
|
||
|
|
|
||
|
|
const badgeSocialVoti = new Collection({
|
||
|
|
name: "badge_social_voti",
|
||
|
|
type: "base",
|
||
|
|
fields: [
|
||
|
|
{
|
||
|
|
name: "evento",
|
||
|
|
type: "relation",
|
||
|
|
required: true,
|
||
|
|
collectionId: eventi.id,
|
||
|
|
maxSelect: 1,
|
||
|
|
cascadeDelete: true,
|
||
|
|
},
|
||
|
|
{ name: "categoria", type: "text", required: true },
|
||
|
|
{
|
||
|
|
name: "votante",
|
||
|
|
type: "relation",
|
||
|
|
required: true,
|
||
|
|
collectionId: giocatori.id,
|
||
|
|
maxSelect: 1,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "votato",
|
||
|
|
type: "relation",
|
||
|
|
required: true,
|
||
|
|
collectionId: giocatori.id,
|
||
|
|
maxSelect: 1,
|
||
|
|
},
|
||
|
|
{ name: "votato_nome", type: "text", required: true },
|
||
|
|
{ name: "created", type: "autodate", onCreate: true },
|
||
|
|
{ name: "updated", type: "autodate", onCreate: true, onUpdate: true },
|
||
|
|
],
|
||
|
|
indexes: [
|
||
|
|
"CREATE UNIQUE INDEX idx_badge_social_voti_evento_categoria_votante ON badge_social_voti (evento, categoria, votante)",
|
||
|
|
],
|
||
|
|
listRule: "@request.auth.id != ''",
|
||
|
|
viewRule: "@request.auth.id != ''",
|
||
|
|
createRule:
|
||
|
|
"@request.auth.role = 'admin' || (@request.auth.id != '' && votante.auth_user_id = @request.auth.id)",
|
||
|
|
updateRule:
|
||
|
|
"@request.auth.role = 'admin' || (@request.auth.id != '' && votante.auth_user_id = @request.auth.id)",
|
||
|
|
deleteRule:
|
||
|
|
"@request.auth.role = 'admin' || (@request.auth.id != '' && votante.auth_user_id = @request.auth.id)",
|
||
|
|
});
|
||
|
|
app.save(badgeSocialVoti);
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------------------------------
|
||
|
|
// turni_palloni / scout_sessioni / scout_live / scout_partite — nessun gate in UI,
|
||
|
|
// aperte a qualsiasi autenticato (invariato da Supabase, vedi docs/DATABASE.md).
|
||
|
|
// ---------------------------------------------------------------------------------------
|
||
|
|
const turniPalloni = new Collection({
|
||
|
|
name: "turni_palloni",
|
||
|
|
type: "base",
|
||
|
|
fields: [
|
||
|
|
{
|
||
|
|
name: "evento",
|
||
|
|
type: "relation",
|
||
|
|
required: true,
|
||
|
|
collectionId: eventi.id,
|
||
|
|
maxSelect: 1,
|
||
|
|
cascadeDelete: true,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "giocatore",
|
||
|
|
type: "relation",
|
||
|
|
required: true,
|
||
|
|
collectionId: giocatori.id,
|
||
|
|
maxSelect: 1,
|
||
|
|
},
|
||
|
|
{ name: "aggiornato_da", type: "text", required: false },
|
||
|
|
{ name: "created", type: "autodate", onCreate: true },
|
||
|
|
{ name: "updated", type: "autodate", onCreate: true, onUpdate: true },
|
||
|
|
],
|
||
|
|
indexes: ["CREATE UNIQUE INDEX idx_turni_palloni_evento ON turni_palloni (evento)"],
|
||
|
|
listRule: "@request.auth.id != ''",
|
||
|
|
viewRule: "@request.auth.id != ''",
|
||
|
|
createRule: "@request.auth.id != ''",
|
||
|
|
updateRule: "@request.auth.id != ''",
|
||
|
|
deleteRule: "@request.auth.id != ''",
|
||
|
|
});
|
||
|
|
app.save(turniPalloni);
|
||
|
|
|
||
|
|
const scoutSessioni = new Collection({
|
||
|
|
name: "scout_sessioni",
|
||
|
|
type: "base",
|
||
|
|
fields: [
|
||
|
|
{
|
||
|
|
name: "evento",
|
||
|
|
type: "relation",
|
||
|
|
required: true,
|
||
|
|
collectionId: eventi.id,
|
||
|
|
maxSelect: 1,
|
||
|
|
cascadeDelete: true,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "giocatore",
|
||
|
|
type: "relation",
|
||
|
|
required: true,
|
||
|
|
collectionId: giocatori.id,
|
||
|
|
maxSelect: 1,
|
||
|
|
},
|
||
|
|
{ name: "giocatore_nome", type: "text", required: true },
|
||
|
|
{ name: "created", type: "autodate", onCreate: true },
|
||
|
|
{ name: "updated", type: "autodate", onCreate: true, onUpdate: true },
|
||
|
|
],
|
||
|
|
indexes: ["CREATE UNIQUE INDEX idx_scout_sessioni_evento ON scout_sessioni (evento)"],
|
||
|
|
listRule: "@request.auth.id != ''",
|
||
|
|
viewRule: "@request.auth.id != ''",
|
||
|
|
createRule: "@request.auth.id != ''",
|
||
|
|
updateRule: "@request.auth.id != ''",
|
||
|
|
deleteRule: "@request.auth.id != ''",
|
||
|
|
});
|
||
|
|
app.save(scoutSessioni);
|
||
|
|
|
||
|
|
const scoutLive = new Collection({
|
||
|
|
name: "scout_live",
|
||
|
|
type: "base",
|
||
|
|
fields: [
|
||
|
|
{
|
||
|
|
name: "evento",
|
||
|
|
type: "relation",
|
||
|
|
required: true,
|
||
|
|
collectionId: eventi.id,
|
||
|
|
maxSelect: 1,
|
||
|
|
cascadeDelete: true,
|
||
|
|
},
|
||
|
|
{ name: "stato", type: "json", required: false },
|
||
|
|
{ name: "created", type: "autodate", onCreate: true },
|
||
|
|
{ name: "updated", type: "autodate", onCreate: true, onUpdate: true },
|
||
|
|
],
|
||
|
|
indexes: ["CREATE UNIQUE INDEX idx_scout_live_evento ON scout_live (evento)"],
|
||
|
|
listRule: "@request.auth.id != ''",
|
||
|
|
viewRule: "@request.auth.id != ''",
|
||
|
|
createRule: "@request.auth.id != ''",
|
||
|
|
updateRule: "@request.auth.id != ''",
|
||
|
|
deleteRule: "@request.auth.id != ''",
|
||
|
|
});
|
||
|
|
app.save(scoutLive);
|
||
|
|
|
||
|
|
const scoutPartite = new Collection({
|
||
|
|
name: "scout_partite",
|
||
|
|
type: "base",
|
||
|
|
fields: [
|
||
|
|
// Id libero (come in Supabase, "id text PRIMARY KEY", generato dal client).
|
||
|
|
{ name: "id", type: "text", min: 1, max: 60 },
|
||
|
|
{
|
||
|
|
name: "evento",
|
||
|
|
type: "relation",
|
||
|
|
required: false,
|
||
|
|
collectionId: eventi.id,
|
||
|
|
maxSelect: 1,
|
||
|
|
cascadeDelete: true,
|
||
|
|
},
|
||
|
|
{ name: "data", type: "date", required: true },
|
||
|
|
{ name: "avversario", type: "text", required: true },
|
||
|
|
{ name: "casa", type: "bool", required: false },
|
||
|
|
{ name: "set_nostri", type: "number", required: true, min: 0 },
|
||
|
|
{ name: "set_loro", type: "number", required: true, min: 0 },
|
||
|
|
{ name: "parziali", type: "json", required: false },
|
||
|
|
{ name: "azioni", type: "json", required: false },
|
||
|
|
{ name: "created", type: "autodate", onCreate: true },
|
||
|
|
{ name: "updated", type: "autodate", onCreate: true, onUpdate: true },
|
||
|
|
],
|
||
|
|
listRule: "@request.auth.id != ''",
|
||
|
|
viewRule: "@request.auth.id != ''",
|
||
|
|
createRule: "@request.auth.id != ''",
|
||
|
|
updateRule: "@request.auth.id != ''",
|
||
|
|
deleteRule: "@request.auth.id != ''",
|
||
|
|
});
|
||
|
|
app.save(scoutPartite);
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------------------------------
|
||
|
|
// push_subscriptions — dispositivi registrati per le notifiche push
|
||
|
|
// ---------------------------------------------------------------------------------------
|
||
|
|
const pushSubscriptions = new Collection({
|
||
|
|
name: "push_subscriptions",
|
||
|
|
type: "base",
|
||
|
|
fields: [
|
||
|
|
{
|
||
|
|
name: "giocatore",
|
||
|
|
type: "relation",
|
||
|
|
required: false,
|
||
|
|
collectionId: giocatori.id,
|
||
|
|
maxSelect: 1,
|
||
|
|
},
|
||
|
|
{ name: "endpoint", type: "text", required: true },
|
||
|
|
{ name: "p256dh", type: "text", required: true },
|
||
|
|
{ name: "auth", type: "text", required: true },
|
||
|
|
{ name: "created", type: "autodate", onCreate: true },
|
||
|
|
{ name: "updated", type: "autodate", onCreate: true, onUpdate: true },
|
||
|
|
],
|
||
|
|
indexes: [
|
||
|
|
"CREATE UNIQUE INDEX idx_push_subscriptions_endpoint ON push_subscriptions (endpoint)",
|
||
|
|
],
|
||
|
|
listRule: "@request.auth.id != ''",
|
||
|
|
viewRule: "@request.auth.id != ''",
|
||
|
|
createRule: "@request.auth.id != ''",
|
||
|
|
updateRule: "@request.auth.id != ''",
|
||
|
|
deleteRule: "@request.auth.id != ''",
|
||
|
|
});
|
||
|
|
app.save(pushSubscriptions);
|
||
|
|
},
|
||
|
|
(app) => {
|
||
|
|
const names = [
|
||
|
|
"push_subscriptions",
|
||
|
|
"scout_partite",
|
||
|
|
"scout_live",
|
||
|
|
"scout_sessioni",
|
||
|
|
"turni_palloni",
|
||
|
|
"badge_social_voti",
|
||
|
|
"pagelle_voti",
|
||
|
|
"mvp_voti",
|
||
|
|
"cacche_partita",
|
||
|
|
"risposte_presenze",
|
||
|
|
"eventi_app",
|
||
|
|
"avatar_giocatori",
|
||
|
|
"profili_giocatore",
|
||
|
|
"giocatori_squadra",
|
||
|
|
];
|
||
|
|
for (const name of names) {
|
||
|
|
const collection = app.findCollectionByNameOrId(name);
|
||
|
|
if (collection) app.delete(collection);
|
||
|
|
}
|
||
|
|
|
||
|
|
const users = app.findCollectionByNameOrId("users");
|
||
|
|
users.fields.removeByName("role");
|
||
|
|
app.save(users);
|
||
|
|
},
|
||
|
|
);
|