Files
pcrt-legal-return/app/prisma/schema.prisma
tommaso d331e609a5 R6 L1+L3: motore di stile del form (token + CSS custom)
Il form era gia' tokenizzato (~30 var CSS in :root): non riscriviamo nulla,
iniettiamo un blocco di override DOPO il CSS di base, cosi' un errore di
configurazione del merchant non puo' rompere il default.

- theme.ts (modulo PURO, condiviso con la futura anteprima admin): ThemeTokens,
  FONT_PRESETS, themeStyle(). Sanificazione a monte: colori solo esadecimali,
  raggio e larghezza clampati, font da whitelist, CSS custom con rimozione di
  </style, @import, expression(), javascript: e cap a 4000 char.
  Derivati: --focus-ring da accent (rgba), --primary-bg-hover per shading.
- recesso.server: tokenizzati anche font (--font) e larghezza (--card-max), che
  erano hardcoded; renderShell(inner, theme) inietta l'override; renderStep1..4
  accettano e propagano il tema.
- Settings: themeAccent/ButtonBg/ButtonText/Radius/Font/Width/CustomCss + migrazione.
- proxy: loadTheme(shop) in loader e action, propagato ai 22 punti di render.
  Nell'early-return senza sessione il tema e' null (non conosciamo lo shop).

fly.toml: min_machines_running resta 0 per scelta (TODO go-live, vedi
PROTECTED-CUSTOMER-DATA.md §5.2).
2026-07-10 13:07:43 +02:00

177 lines
5.1 KiB
Plaintext

// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
// ---------------------------------------------------------------------------
// Session — used by @shopify/shopify-app-session-storage-prisma.
// DO NOT change the shape of this model (the session storage depends on it).
// ---------------------------------------------------------------------------
model Session {
id String @id
shop String
state String
isOnline Boolean @default(false)
scope String?
expires DateTime?
accessToken String
userId BigInt?
firstName String?
lastName String?
email String?
accountOwner Boolean @default(false)
locale String?
collaborator Boolean? @default(false)
emailVerified Boolean? @default(false)
refreshToken String?
refreshTokenExpires DateTime?
}
// ---------------------------------------------------------------------------
// Multi-tenant app models. Every tenant-scoped row carries `shop` + an index
// on it (public-grade isolation from day 1, even with only 2-3 custom stores).
// ---------------------------------------------------------------------------
// Per-shop merchant configuration for the withdrawal button/flow.
model Settings {
id String @id @default(cuid())
shop String @unique
buttonLabel String @default("Recedere dal contratto qui")
confirmLabel String @default("Conferma recesso")
brandPrimaryColor String?
returnAddress String?
defaultWindowDays Int @default(14)
withdrawalInfoText String?
emailSubject String?
emailIntro String?
emailNote String?
notifyEnabled Boolean @default(true)
notifyEmail String?
tagEnabled Boolean @default(true)
enforceWindow Boolean @default(false)
enforceExclusions Boolean @default(false)
stateAwareEmail Boolean @default(true)
autoCancelUnfulfilled Boolean @default(false)
returnAtCustomerExpense Boolean @default(true)
returnInstructions String? // deprecato: sostituito da opTextShipped
opTextUnfulfilled String?
opTextShipped String?
smtpHost String?
smtpPort Int?
smtpUser String?
smtpPass String? // cifrato AES-256-GCM (mai in chiaro)
smtpSecure Boolean @default(false)
smtpFrom String?
// Motore di stile del form (livello 1 = token, livello 3 = CSS custom).
themeAccent String?
themeButtonBg String?
themeButtonText String?
themeRadius Int?
themeFont String?
themeWidth Int?
themeCustomCss String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([shop])
}
// Art. 59 exclusions (made-to-order, perishable, sealed-for-hygiene, etc.).
model ExclusionRule {
id String @id @default(cuid())
shop String
scope ExclusionScope
targetId String?
reason ExclusionReason
active Boolean @default(true)
createdAt DateTime @default(now())
@@index([shop])
}
// A withdrawal (recesso) statement submitted by a consumer.
model WithdrawalRequest {
id String @id @default(cuid())
shop String
orderId String
orderName String?
customerName String
email String
statementText String
transmittedAt DateTime // legal timestamp of transmission (Art. 54-bis)
locale String?
channel WithdrawalChannel
productType String?
status WithdrawalStatus @default(RECEIVED)
receiptSentAt DateTime?
computedDeadline DateTime?
shopifyReturnId String? // GID del Reso Shopify creato (se ordine evaso)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([shop])
@@index([shop, orderId])
}
// Append-only audit trail (burden of proof — Art. 54-bis / R6). No updatedAt.
model AuditLog {
id String @id @default(cuid())
shop String
event String
payloadHash String?
detail String?
createdAt DateTime @default(now())
@@index([shop])
}
// Webhook idempotency ledger.
model WebhookEvent {
id String @id @default(cuid())
shop String
topic String
webhookId String? @unique
receivedAt DateTime @default(now())
processed Boolean @default(false)
@@index([shop])
}
// ---------------------------------------------------------------------------
// Enums
// ---------------------------------------------------------------------------
enum ExclusionScope {
PRODUCT
COLLECTION
TAG
ALL
}
enum ExclusionReason {
CUSTOM
PERISHABLE
HYGIENE
OTHER
}
enum WithdrawalChannel {
GUEST
ACCOUNT
}
enum WithdrawalStatus {
RECEIVED
ACKNOWLEDGED
GOODS_PENDING
CLOSED
REJECTED
}