Initial commit (unpacked platform)
This commit is contained in:
@@ -0,0 +1,202 @@
|
||||
import {
|
||||
boolean,
|
||||
doublePrecision,
|
||||
index,
|
||||
integer,
|
||||
jsonb,
|
||||
pgTable,
|
||||
primaryKey,
|
||||
text,
|
||||
timestamp,
|
||||
uuid,
|
||||
} from "drizzle-orm/pg-core";
|
||||
import {
|
||||
createdAt,
|
||||
creatorLevelEnum,
|
||||
eventTypeEnum,
|
||||
notificationTypeEnum,
|
||||
profileVisibilityEnum,
|
||||
updatedAt,
|
||||
} from "./_shared.js";
|
||||
import { households } from "./households.js";
|
||||
import { users } from "./users.js";
|
||||
|
||||
/**
|
||||
* Domänhändelser (spec §55) i outbox-mönster: skrivs transaktionellt med
|
||||
* affärsdata och publiceras asynkront till konsumenter (analytics, memory,
|
||||
* recommendations, training).
|
||||
*/
|
||||
export const domainEvents = pgTable(
|
||||
"domain_events",
|
||||
{
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
type: eventTypeEnum("type").notNull(),
|
||||
userId: uuid("user_id"),
|
||||
householdId: uuid("household_id"),
|
||||
payload: jsonb("payload").notNull(),
|
||||
correlationId: text("correlation_id"),
|
||||
occurredAt: timestamp("occurred_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
publishedAt: timestamp("published_at", { withTimezone: true }),
|
||||
},
|
||||
(t) => [
|
||||
index("domain_events_unpublished_idx").on(t.publishedAt, t.occurredAt),
|
||||
index("domain_events_type_idx").on(t.type, t.occurredAt),
|
||||
index("domain_events_household_idx").on(t.householdId, t.occurredAt),
|
||||
],
|
||||
);
|
||||
|
||||
export const featureFlags = pgTable("feature_flags", {
|
||||
key: text("key").primaryKey(),
|
||||
enabled: boolean("enabled").notNull().default(false),
|
||||
descriptionSv: text("description_sv"),
|
||||
rolloutPercent: integer("rollout_percent").notNull().default(100),
|
||||
updatedAt: updatedAt(),
|
||||
});
|
||||
|
||||
/** Audit log (spec §56): vem gjorde vad, när, mot vad. */
|
||||
export const auditLogs = pgTable(
|
||||
"audit_logs",
|
||||
{
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
actorUserId: uuid("actor_user_id"),
|
||||
actorType: text("actor_type").notNull().default("user"),
|
||||
action: text("action").notNull(),
|
||||
targetType: text("target_type"),
|
||||
targetId: text("target_id"),
|
||||
metadata: jsonb("metadata"),
|
||||
ip: text("ip"),
|
||||
correlationId: text("correlation_id"),
|
||||
createdAt: createdAt(),
|
||||
},
|
||||
(t) => [
|
||||
index("audit_logs_actor_idx").on(t.actorUserId, t.createdAt),
|
||||
index("audit_logs_action_idx").on(t.action, t.createdAt),
|
||||
],
|
||||
);
|
||||
|
||||
/** Idempotency-nycklar för säkra POST-retries (spec §56, §59). */
|
||||
export const idempotencyKeys = pgTable(
|
||||
"idempotency_keys",
|
||||
{
|
||||
userId: uuid("user_id")
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
key: text("key").notNull(),
|
||||
endpoint: text("endpoint").notNull(),
|
||||
responseStatus: integer("response_status"),
|
||||
responseBody: jsonb("response_body"),
|
||||
createdAt: createdAt(),
|
||||
},
|
||||
(t) => [primaryKey({ columns: [t.userId, t.key, t.endpoint] })],
|
||||
);
|
||||
|
||||
/** Notiser (spec §40). */
|
||||
export const notifications = pgTable(
|
||||
"notifications",
|
||||
{
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
userId: uuid("user_id")
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
type: notificationTypeEnum("type").notNull(),
|
||||
titleSv: text("title_sv").notNull(),
|
||||
bodySv: text("body_sv").notNull(),
|
||||
data: jsonb("data"),
|
||||
/** i18n-spec §26: mall + variabler; titleSv/bodySv är renderad cache. */
|
||||
templateKey: text("template_key"),
|
||||
variables: jsonb("variables"),
|
||||
locale: text("locale"),
|
||||
scheduledFor: timestamp("scheduled_for", { withTimezone: true }),
|
||||
sentAt: timestamp("sent_at", { withTimezone: true }),
|
||||
readAt: timestamp("read_at", { withTimezone: true }),
|
||||
createdAt: createdAt(),
|
||||
},
|
||||
(t) => [index("notifications_user_idx").on(t.userId, t.createdAt)],
|
||||
);
|
||||
|
||||
/** Push-tokens för Expo push. */
|
||||
export const pushTokens = pgTable(
|
||||
"push_tokens",
|
||||
{
|
||||
userId: uuid("user_id")
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
token: text("token").notNull(),
|
||||
platform: text("platform").notNull(),
|
||||
updatedAt: updatedAt(),
|
||||
},
|
||||
(t) => [primaryKey({ columns: [t.userId, t.token] })],
|
||||
);
|
||||
|
||||
/** Creator-statistik (spec §37). */
|
||||
export const creatorStats = pgTable("creator_stats", {
|
||||
userId: uuid("user_id")
|
||||
.primaryKey()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
visibility: profileVisibilityEnum("visibility").notNull().default("private"),
|
||||
level: creatorLevelEnum("level").notNull().default("beginner"),
|
||||
publishedRecipes: integer("published_recipes").notNull().default(0),
|
||||
followers: integer("followers").notNull().default(0),
|
||||
totalCooks: integer("total_cooks").notNull().default(0),
|
||||
totalFavorites: integer("total_favorites").notNull().default(0),
|
||||
averageRating: doublePrecision("average_rating"),
|
||||
verifiedRecipes: integer("verified_recipes").notNull().default(0),
|
||||
badges: text("badges").array().notNull().default([]),
|
||||
updatedAt: updatedAt(),
|
||||
});
|
||||
|
||||
export const creatorFollows = pgTable(
|
||||
"creator_follows",
|
||||
{
|
||||
followerUserId: uuid("follower_user_id")
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
creatorUserId: uuid("creator_user_id")
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
createdAt: createdAt(),
|
||||
},
|
||||
(t) => [primaryKey({ columns: [t.followerUserId, t.creatorUserId] })],
|
||||
);
|
||||
|
||||
/** AI-evals (spec §34): fast testbibliotek + körningar. Ingen modelländring utan eval. */
|
||||
export const aiEvalCases = pgTable("ai_eval_cases", {
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
taskType: text("task_type").notNull(),
|
||||
nameSv: text("name_sv").notNull(),
|
||||
inputRef: jsonb("input_ref").notNull(),
|
||||
expectedOutput: jsonb("expected_output").notNull(),
|
||||
category: text("category").notNull().default("standard"),
|
||||
active: boolean("active").notNull().default(true),
|
||||
createdAt: createdAt(),
|
||||
});
|
||||
|
||||
export const aiEvalRuns = pgTable(
|
||||
"ai_eval_runs",
|
||||
{
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
taskType: text("task_type").notNull(),
|
||||
modelVersion: text("model_version").notNull(),
|
||||
promptVersion: text("prompt_version").notNull(),
|
||||
metrics: jsonb("metrics").notNull(),
|
||||
passed: boolean("passed").notNull(),
|
||||
notes: text("notes"),
|
||||
createdAt: createdAt(),
|
||||
},
|
||||
(t) => [index("ai_eval_runs_task_idx").on(t.taskType, t.createdAt)],
|
||||
);
|
||||
|
||||
/** Householdsvinn per vecka – aggregat för budget/matsvinnsvyer (spec §26). */
|
||||
export const wasteSummaries = pgTable(
|
||||
"waste_summaries",
|
||||
{
|
||||
householdId: uuid("household_id")
|
||||
.notNull()
|
||||
.references(() => households.id, { onDelete: "cascade" }),
|
||||
weekStartDate: text("week_start_date").notNull(),
|
||||
discardedCount: integer("discarded_count").notNull().default(0),
|
||||
discardedValueMinor: integer("discarded_value_minor").notNull().default(0),
|
||||
updatedAt: updatedAt(),
|
||||
},
|
||||
(t) => [primaryKey({ columns: [t.householdId, t.weekStartDate] })],
|
||||
);
|
||||
Reference in New Issue
Block a user