Gå til innholdet
Digilist Dokumentasjon
Book demo

ARKITEKTUR · DATAMODELL

Datamodell

~80 kjernetabeller fordelt på 27 komponenter. Identity, infra, commerce, payments, ledger, GDPR, secrets — alt scopet til tenantId.

Convex-skjemaet inneholder ~80 kjernetabeller fordelt på 27 komponenter. Alle skriv-spørringer scopes til tenantId utledet fra sesjonen. Offentlige lese-spørringer (markedsplassen) er un-scoped.

Identity & Auth

TabellFelter (utdrag)Indekser
usersid, email, name, role, mfaEnabled, idPortenPidby_email, by_idPortenPid
sessionsuserId, token, expiresAt, device, ipHashby_token, by_user
mfaCredentialsuserId, kind (totp|webauthn), secretEncby_user
oauthAccountsuserId, provider (id-porten|bankid|feide), subjectby_provider_subject
passwordResetsuserId, token, expiresAt, usedAtby_token

Tenants & RBAC

TabellFelter (utdrag)Indekser
tenantsid, slug, name, plan, kind (kommune|forening|privat), orgnrby_slug, by_orgnr
tenantUserstenantId, userId, role, invitedAt, acceptedAtby_tenant, by_user
rolestenantId, name, permissions[], isSystemby_tenant_name
userRolesuserId, roleId, tenantIdby_user, by_role
capabilitieskind, scope, enabledByDefault(system)

Booking & Calendar

TabellFelter (utdrag)Indekser
resourcestenantId, slug, name, kind, state, openingHoursby_tenant_slug, by_state
bookingstenantId, resourceId, userId, startsAt, endsAt, state, priceCentsby_resource_time, by_user, by_tenant_state
bookingApprovalsbookingId, approverId, decidedAt, decision, noteby_booking
availabilityBlocksresourceId, startsAt, endsAt, reasonby_resource_time
seasonstenantId, name, startsAt, endsAt, allocationRulesby_tenant_active
recurringRulesbookingId, rrule, untilby_booking

Payments & Ledger

TabellFelter (utdrag)Indekser
paymentstenantId, bookingId, provider, intentId, state, amountCentsby_intent, by_booking
refundspaymentId, amountCents, reason, stateby_payment
payoutstenantId, provider, periodStart, periodEnd, amountCents, stateby_tenant_period
ledgerEntriestenantId, kind, bookingId, amountCents, account, tsby_tenant_ts, by_booking
commissionstenantId, bookingId, rateBps, amountCents, snapshotAtby_booking
invoicestenantId, customerId, kid, state, amountCents, ehfSentAtby_kid, by_tenant_state

Notifications & Messaging

TabellFelter (utdrag)Indekser
notificationsuserId, kind, channel, payload, sentAt, readAtby_user_sentAt
notificationPrefsuserId, kind, channels[]by_user_kind
messagestenantId, threadId, senderId, body, sentAtby_thread_sentAt
messageThreadstenantId, bookingId, participantIds[], lastMessageAtby_booking, by_tenant_lastMsg

Audit & GDPR

TabellFelter (utdrag)Indekser
auditEventstenantId, actorId, kind, targetType, targetId, payload, tsby_tenant_ts, by_target
dataExportsuserId, requestedAt, completedAt, urlEnc, expiresAtby_user
dataDeletionsuserId, requestedAt, scheduledAt, executedAt, gracePeriodEndby_user_state
consentsuserId, kind, givenAt, revokedAt, versionby_user_kind

Outbox (Hendelse-buss)

TabellFelter (utdrag)Indekser
outboxEventstopic, payload, producerComponentId, createdAt, stateby_topic_createdAt, by_state
outboxDeliverieseventId, subscriberId, attempt, nextAttemptAt, stateby_subscriber_state, by_event
eventTopicsname, schema, producerComponent, subscriberComponents[](system)

Tenant-første-prinsipp

Alle disse tabellene (med unntak av capabilities, eventTopics, og noen system-tabeller) har tenantId som første index-felt. Alle skriv-fasader verifiserer session.tenantId === input.tenantId før de muterer noe. Lese-spørringer på markedsplassen bypasser scope-sjekk siden de er public.

// Eksempel: en skriv-fasade med tenant-scope
export const update = mutation({
args: { id: v.id("bookings"), patch: v.object({ /* … */ }) },
handler: async (ctx, { id, patch }) => {
const session = await requireSession(ctx);
const booking = await ctx.db.get(id);
if (!booking || booking.tenantId !== session.tenantId) {
throw new ConvexError({ kind: "not_found", id });
}
await ctx.db.patch(id, patch);
await emit(ctx, "booking.updated", { id, by: session.userId });
},
});

Beslektet