- Settings: smtpHost/Port/User/Pass(cifrata)/Secure/From. Vuoto -> provider default dell'app (env); compilato -> invio dal SMTP del merchant. - crypto.server: encrypt/decrypt AES-256-GCM (chiave APP_ENCRYPTION_KEY). - mailer: SmtpConfig + buildTransport(smtp) per-shop o env; mailFrom override. Sia ricevuta cliente sia notifica merchant usano lo SMTP del merchant. - proxy: costruisce SmtpConfig (decifra pass), passa ai due invii. - admin: nuovo tab 'Email (SMTP)'; password mai ri-mostrata (vuoto = invariata). - Migrazione smtp_settings. APP_ENCRYPTION_KEY: dev in .env, prod = fly secret. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Mv83a29B4eFv5ixoj6PoE1
169 lines
4.8 KiB
Plaintext
169 lines
4.8 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?
|
|
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
|
|
}
|