Aggiunge infrastruttura PocketBase locale (Docker, schema, hook)
Introduce PocketBase come sostituto di Supabase solo per l'ambiente di sviluppo locale (docs/PORTABILITA.md): docker-compose.pocketbase.yml, schema iniziale in pocketbase/pb_migrations/ (collection equivalenti alle tabelle Supabase, con le stesse API rule dove esprimibili) e la logica procedurale che in Postgres viveva in trigger/funzioni riscritta come hook JS in pocketbase/pb_hooks/ (claim slot giocatore, blocco autovoto, convocati/pagelle chiuse, immutabilità di risposto_il). Il superuser e il provider Google OAuth2 si ricreano da soli a ogni avvio da variabili d'ambiente (script scripts/pocketbase-reset.sh), così un reset non richiede passaggi manuali da dashboard. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,665 @@
|
||||
/// <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);
|
||||
},
|
||||
);
|
||||
Reference in New Issue
Block a user