Initial commit (unpacked platform)

This commit is contained in:
Sven (AAMOS AI)
2026-08-05 19:21:11 +07:00
commit ac5340195a
314 changed files with 57584 additions and 0 deletions
+202
View File
@@ -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] })],
);