diff --git a/backend/app.ts b/backend/app.ts
index 44b67e0..20088ec 100644
--- a/backend/app.ts
+++ b/backend/app.ts
@@ -1,30 +1,29 @@
+import { toNodeHandler } from "better-auth/node";
import express from "express";
import morgan from "morgan";
import { createLogger } from "./src/logger/logger.controller.js";
-import { connectProdSql } from "./src/prodSql/prodSqlConnection.controller.js";
import { setupRoutes } from "./src/routeHandler.routes.js";
+import { auth } from "./src/utils/auth.utils.js";
+import { lstCors } from "./src/utils/cors.utils.js";
-const port = Number(process.env.PORT);
-
-const startApp = async () => {
+const createApp = async () => {
const log = createLogger({ module: "system", subModule: "main start" });
const app = express();
let baseUrl = "/";
- // global env that run only in dev
if (process.env.NODE_ENV?.trim() !== "production") {
app.use(morgan("tiny"));
baseUrl = "/lst";
}
- // start the connection to the prod sql server
- connectProdSql();
-
+ app.set("trust proxy", true);
+ app.all(`${baseUrl}api/auth/*splat`, toNodeHandler(auth));
+ app.use(express.json());
+ app.use(lstCors());
setupRoutes(baseUrl, app);
- app.listen(port, () => {
- log.info(`Listening on port ${port}`);
- });
+ log.info("Express app created");
+ return { app, baseUrl };
};
-startApp();
+export default createApp;
diff --git a/backend/server.ts b/backend/server.ts
new file mode 100644
index 0000000..b21d1da
--- /dev/null
+++ b/backend/server.ts
@@ -0,0 +1,22 @@
+import os from "node:os";
+import createApp from "./app.js";
+import { createLogger } from "./src/logger/logger.controller.js";
+import { connectProdSql } from "./src/prodSql/prodSqlConnection.controller.js";
+
+const port = Number(process.env.PORT);
+
+const start = async () => {
+ const log = createLogger({ module: "system", subModule: "main start" });
+
+ connectProdSql();
+
+ const { app, baseUrl } = await createApp();
+
+ app.listen(port, async () => {
+ log.info(
+ `Listening on http://${os.hostname()}:${port}${baseUrl}, logging in ${process.env.LOG_LEVEL}`,
+ );
+ });
+};
+
+start();
diff --git a/backend/src/db/schema/auth.schema.ts b/backend/src/db/schema/auth.schema.ts
new file mode 100644
index 0000000..4c67171
--- /dev/null
+++ b/backend/src/db/schema/auth.schema.ts
@@ -0,0 +1,157 @@
+import { relations } from "drizzle-orm";
+import {
+ boolean,
+ index,
+ integer,
+ pgTable,
+ text,
+ timestamp,
+} from "drizzle-orm/pg-core";
+
+export const user = pgTable("user", {
+ id: text("id").primaryKey(),
+ name: text("name").notNull(),
+ email: text("email").notNull().unique(),
+ emailVerified: boolean("email_verified").default(false).notNull(),
+ image: text("image"),
+ createdAt: timestamp("created_at").defaultNow().notNull(),
+ updatedAt: timestamp("updated_at")
+ .defaultNow()
+ .$onUpdate(() => /* @__PURE__ */ new Date())
+ .notNull(),
+ role: text("role"),
+ banned: boolean("banned").default(false),
+ banReason: text("ban_reason"),
+ banExpires: timestamp("ban_expires"),
+ username: text("username").unique(),
+ displayUsername: text("display_username"),
+ lastLogin: timestamp("last_login").defaultNow(),
+});
+
+export const session = pgTable(
+ "session",
+ {
+ id: text("id").primaryKey(),
+ expiresAt: timestamp("expires_at").notNull(),
+ token: text("token").notNull().unique(),
+ createdAt: timestamp("created_at").defaultNow().notNull(),
+ updatedAt: timestamp("updated_at")
+ .$onUpdate(() => /* @__PURE__ */ new Date())
+ .notNull(),
+ ipAddress: text("ip_address"),
+ userAgent: text("user_agent"),
+ userId: text("user_id")
+ .notNull()
+ .references(() => user.id, { onDelete: "cascade" }),
+ impersonatedBy: text("impersonated_by"),
+ },
+ (table) => [index("session_userId_idx").on(table.userId)],
+);
+
+export const account = pgTable(
+ "account",
+ {
+ id: text("id").primaryKey(),
+ accountId: text("account_id").notNull(),
+ providerId: text("provider_id").notNull(),
+ userId: text("user_id")
+ .notNull()
+ .references(() => user.id, { onDelete: "cascade" }),
+ accessToken: text("access_token"),
+ refreshToken: text("refresh_token"),
+ idToken: text("id_token"),
+ accessTokenExpiresAt: timestamp("access_token_expires_at"),
+ refreshTokenExpiresAt: timestamp("refresh_token_expires_at"),
+ scope: text("scope"),
+ password: text("password"),
+ createdAt: timestamp("created_at").defaultNow().notNull(),
+ updatedAt: timestamp("updated_at")
+ .$onUpdate(() => /* @__PURE__ */ new Date())
+ .notNull(),
+ },
+ (table) => [index("account_userId_idx").on(table.userId)],
+);
+
+export const verification = pgTable(
+ "verification",
+ {
+ id: text("id").primaryKey(),
+ identifier: text("identifier").notNull(),
+ value: text("value").notNull(),
+ expiresAt: timestamp("expires_at").notNull(),
+ createdAt: timestamp("created_at").defaultNow().notNull(),
+ updatedAt: timestamp("updated_at")
+ .defaultNow()
+ .$onUpdate(() => /* @__PURE__ */ new Date())
+ .notNull(),
+ },
+ (table) => [index("verification_identifier_idx").on(table.identifier)],
+);
+
+export const jwks = pgTable("jwks", {
+ id: text("id").primaryKey(),
+ publicKey: text("public_key").notNull(),
+ privateKey: text("private_key").notNull(),
+ createdAt: timestamp("created_at").notNull(),
+ expiresAt: timestamp("expires_at"),
+});
+
+export const apikey = pgTable(
+ "apikey",
+ {
+ id: text("id").primaryKey(),
+ name: text("name"),
+ start: text("start"),
+ prefix: text("prefix"),
+ key: text("key").notNull(),
+ userId: text("user_id")
+ .notNull()
+ .references(() => user.id, { onDelete: "cascade" }),
+ refillInterval: integer("refill_interval"),
+ refillAmount: integer("refill_amount"),
+ lastRefillAt: timestamp("last_refill_at"),
+ enabled: boolean("enabled").default(true),
+ rateLimitEnabled: boolean("rate_limit_enabled").default(true),
+ rateLimitTimeWindow: integer("rate_limit_time_window").default(86400000),
+ rateLimitMax: integer("rate_limit_max").default(10),
+ requestCount: integer("request_count").default(0),
+ remaining: integer("remaining"),
+ lastRequest: timestamp("last_request"),
+ expiresAt: timestamp("expires_at"),
+ createdAt: timestamp("created_at").notNull(),
+ updatedAt: timestamp("updated_at").notNull(),
+ permissions: text("permissions"),
+ metadata: text("metadata"),
+ },
+ (table) => [
+ index("apikey_key_idx").on(table.key),
+ index("apikey_userId_idx").on(table.userId),
+ ],
+);
+
+export const userRelations = relations(user, ({ many }) => ({
+ sessions: many(session),
+ accounts: many(account),
+ apikeys: many(apikey),
+}));
+
+export const sessionRelations = relations(session, ({ one }) => ({
+ user: one(user, {
+ fields: [session.userId],
+ references: [user.id],
+ }),
+}));
+
+export const accountRelations = relations(account, ({ one }) => ({
+ user: one(user, {
+ fields: [account.userId],
+ references: [user.id],
+ }),
+}));
+
+export const apikeyRelations = relations(apikey, ({ one }) => ({
+ user: one(user, {
+ fields: [apikey.userId],
+ references: [user.id],
+ }),
+}));
diff --git a/backend/src/db/schema/logs.schema.ts b/backend/src/db/schema/logs.schema.ts
index 0f1b0dd..beaefa6 100644
--- a/backend/src/db/schema/logs.schema.ts
+++ b/backend/src/db/schema/logs.schema.ts
@@ -18,7 +18,7 @@ export const logs = pgTable("logs", {
stack: jsonb("stack").default([]),
checked: boolean("checked").default(false),
hostname: text("hostname"),
- createdAt: timestamp("createdAt").defaultNow(),
+ createdAt: timestamp("created_at").defaultNow(),
});
export const logSchema = createSelectSchema(logs);
diff --git a/backend/src/utils/auth.utils.ts b/backend/src/utils/auth.utils.ts
new file mode 100644
index 0000000..f5f0a0e
--- /dev/null
+++ b/backend/src/utils/auth.utils.ts
@@ -0,0 +1,87 @@
+import { betterAuth, type User } from "better-auth";
+import { drizzleAdapter } from "better-auth/adapters/drizzle";
+import { admin, apiKey, jwt, username } from "better-auth/plugins";
+import { eq } from "drizzle-orm";
+import { db } from "../db/db.controller.js";
+import * as rawSchema from "../db/schema/auth.schema.js";
+import { allowedOrigins } from "./cors.utils.js";
+import { sendEmail } from "./sendEmail.utils.js";
+
+export const schema = {
+ user: rawSchema.user,
+ session: rawSchema.session,
+ account: rawSchema.account,
+ verification: rawSchema.verification,
+ jwks: rawSchema.jwks,
+ apiKey: rawSchema.apikey, // 🔑 rename to apiKey
+};
+
+export const auth = betterAuth({
+ appName: "lst",
+ baseURL: process.env.URL,
+ database: drizzleAdapter(db, {
+ provider: "pg",
+ }),
+ plugins: [
+ jwt({ jwt: { expirationTime: "1h" } }),
+ apiKey(),
+ admin(),
+ username(),
+ ],
+ trustedOrigins: allowedOrigins,
+ // email or username and password.
+ emailAndPassword: {
+ enabled: true,
+ minPasswordLength: 8, // optional config
+ resetPasswordTokenExpirySeconds: process.env.RESET_EXPIRY_SECONDS, // time in seconds
+ sendResetPassword: async ({ user, token }) => {
+ const frontendUrl = `${process.env.BETTER_AUTH_URL}/lst/app/user/resetpassword?token=${token}`;
+ const expiryMinutes = Math.floor(
+ parseInt(process.env.RESET_EXPIRY_SECONDS ?? "3600", 10) / 60,
+ );
+ const expiryText =
+ expiryMinutes >= 60
+ ? `${expiryMinutes / 60} hour${expiryMinutes === 60 ? "" : "s"}`
+ : `${expiryMinutes} minutes`;
+ const emailData = {
+ email: user.email,
+ subject: "LST- Forgot password request",
+ template: "forgotPassword",
+ context: {
+ username: user.name,
+ email: user.email,
+ url: frontendUrl,
+ expiry: expiryText,
+ },
+ };
+ await sendEmail(emailData);
+ },
+ // onPasswordReset: async ({ user }, request) => {
+ // // your logic here
+ // console.log(`Password for user ${user.email} has been reset.`);
+ // },
+ },
+ session: {
+ expiresIn: 60 * 60,
+ updateAge: 60 * 5,
+ freshAge: 60 * 2,
+ cookieCache: {
+ enabled: true,
+ maxAge: 5 * 60,
+ },
+ },
+ cookie: {
+ path: "/lst/app",
+ sameSite: "lax",
+ secure: false,
+ httpOnly: true,
+ },
+ events: {
+ async onSignInSuccess({ user }: { user: User }) {
+ await db
+ .update(rawSchema.user)
+ .set({ lastLogin: new Date() })
+ .where(eq(schema.user.id, user.id));
+ },
+ },
+});
diff --git a/backend/src/utils/cors.utils.ts b/backend/src/utils/cors.utils.ts
new file mode 100644
index 0000000..f2d8467
--- /dev/null
+++ b/backend/src/utils/cors.utils.ts
@@ -0,0 +1,53 @@
+import cors from "cors";
+
+export const allowedOrigins = [
+ "*.alpla.net",
+ "http://localhost:4173",
+ "http://localhost:4200",
+ "http://localhost:3000",
+ "http://localhost:3001",
+ "http://localhost:4000",
+ "http://localhost:4001",
+ "http://localhost:5500",
+ `${process.env.URL}`,
+];
+export const lstCors = () => {
+ return cors({
+ origin: (origin, callback) => {
+ //console.log("CORS request from origin:", origin);
+
+ if (!origin) return callback(null, true); // allow same-site or direct calls
+
+ try {
+ const hostname = new URL(origin).hostname; // strips protocol/port
+ //console.log("Parsed hostname:", hostname);
+
+ if (allowedOrigins.includes(origin)) {
+ return callback(null, true);
+ }
+
+ // Now this works for *.alpla.net
+ if (hostname.endsWith(".alpla.net") || hostname === "alpla.net") {
+ return callback(null, true);
+ }
+ } catch (_) {
+ //console.error("Invalid Origin header:", origin);
+ }
+
+ return callback(new Error(`Not allowed by CORS: ${origin}`));
+ },
+ methods: ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"],
+ credentials: true,
+ exposedHeaders: ["set-cookie", "expo-protocol-version", "expo-sfv-version"],
+ allowedHeaders: [
+ "Content-Type",
+ "Authorization",
+ "X-Requested-With",
+ "XMLHttpRequest",
+ "expo-runtime-version",
+ "expo-platform",
+ "expo-channel-name",
+ "*",
+ ],
+ });
+};
diff --git a/backend/src/utils/mailViews/forgotPassword.hbs b/backend/src/utils/mailViews/forgotPassword.hbs
new file mode 100644
index 0000000..922e306
--- /dev/null
+++ b/backend/src/utils/mailViews/forgotPassword.hbs
@@ -0,0 +1,69 @@
+
+
+
+
+ Password Reset
+ {{> styles}}
+
+
+
+
+
Password Reset Request
+
Hi {{username}},
+
+
We received a request to reset the password for your account ({{email}}). If this was you, you can create a new password by clicking the button below:
+
+
+ Reset Your Password
+
+
+
If the button above doesn’t work, copy and paste the following link into your web browser:
+
{{url}}
+
+
Note: This link will expire in {{expiry}} for your security.
+
+
If you did not request a password reset, you can safely ignore this email — your account will remain secure.
+
+
Thanks,
The LST Team
+
+
+
+
+
\ No newline at end of file
diff --git a/backend/src/utils/mailViews/styles.hbs b/backend/src/utils/mailViews/styles.hbs
new file mode 100644
index 0000000..09e1061
--- /dev/null
+++ b/backend/src/utils/mailViews/styles.hbs
@@ -0,0 +1,6 @@
+
\ No newline at end of file
diff --git a/backend/src/utils/mailViews/testEmail.hbs b/backend/src/utils/mailViews/testEmail.hbs
new file mode 100644
index 0000000..8c697b3
--- /dev/null
+++ b/backend/src/utils/mailViews/testEmail.hbs
@@ -0,0 +1,33 @@
+
+
+
+
+ Order Summary
+ {{> styles}}
+ {{!-- --}}
+
+
+ {{name}}, This is a test that the emailing actually works as intended.
+ All,
+ This is an example of the test email
+
+ {{!--
+
+
+ | Item Name |
+ Quantity |
+ Price |
+
+
+
+ {{#each items}}
+
+ | {{name}} |
+ {{quantity}} |
+ {{price}} |
+
+ {{/each}}
+
+
--}}
+
+
\ No newline at end of file
diff --git a/backend/src/utils/returnHelper.utils.ts b/backend/src/utils/returnHelper.utils.ts
index bc49833..726f303 100644
--- a/backend/src/utils/returnHelper.utils.ts
+++ b/backend/src/utils/returnHelper.utils.ts
@@ -3,8 +3,8 @@ import { createLogger } from "../logger/logger.controller.js";
interface Data {
success: boolean;
- module: "system" | "ocp" | "routes" | "datamart";
- subModule: "db" | "labeling" | "printer" | "prodSql" | "query";
+ module: "system" | "ocp" | "routes" | "datamart" | "utils";
+ subModule: "db" | "labeling" | "printer" | "prodSql" | "query" | "sendmail";
level: "info" | "error" | "debug" | "fatal";
message: string;
data?: unknown[];
@@ -47,6 +47,9 @@ export const returnFunc = (data: Data) => {
return {
success: data.success,
message: data.message,
+ level: data.level,
+ module: data.module,
+ subModule: data.subModule,
data: data.data || [],
};
};
diff --git a/backend/src/utils/sendEmail.utils.ts b/backend/src/utils/sendEmail.utils.ts
new file mode 100644
index 0000000..45393e7
--- /dev/null
+++ b/backend/src/utils/sendEmail.utils.ts
@@ -0,0 +1,125 @@
+import type { Transporter } from "nodemailer";
+import nodemailer from "nodemailer";
+import type Mail from "nodemailer/lib/mailer/index.js";
+import type { Address } from "nodemailer/lib/mailer/index.js";
+import type SMTPTransport from "nodemailer/lib/smtp-transport/index.js";
+import hbs from "nodemailer-express-handlebars";
+import os from "os";
+import path from "path";
+import { fileURLToPath } from "url";
+import { promisify } from "util";
+import { returnFunc } from "./returnHelper.utils.js";
+import { tryCatch } from "./trycatch.utils.js";
+
+interface HandlebarsMailOptions extends Mail.Options {
+ template: string;
+ context: Record;
+}
+
+interface EmailData {
+ email: string;
+ subject: string;
+ template: string;
+ context: Record;
+}
+
+export const sendEmail = async (data: EmailData) => {
+ let transporter: Transporter;
+ let fromEmail: string | Address;
+
+ if (os.hostname().includes("OLP")) {
+ transporter = nodemailer.createTransport({
+ service: "gmail",
+ auth: {
+ user: process.env.EMAIL_USER,
+ pass: process.env.EMAIL_PASSWORD, // The 16-character App Password
+ },
+ //debug: true,
+ });
+
+ // update the from email
+ fromEmail = process.env.EMAIL_USER as string;
+
+ // update the from email
+ fromEmail = `noreply@alpla.com`;
+ } else {
+ //create the servers smtp config
+ let host = `${os.hostname().replace("VMS006", "")}-smtp.alpla.net`;
+
+ if (os.hostname().includes("VMS036")) {
+ host = "USMCD1-smtp.alpla.net";
+ }
+
+ transporter = nodemailer.createTransport({
+ host: host,
+ port: 25,
+ rejectUnauthorized: false,
+ //secure: false,
+ // auth: {
+ // user: "alplaprod",
+ // pass: "obelix",
+ // },
+ debug: true,
+ } as SMTPTransport.Options);
+
+ // update the from email
+ fromEmail = `noreply@alpla.com`;
+ }
+
+ // create the handlebars view
+ const viewPath = path.resolve(
+ path.dirname(fileURLToPath(import.meta.url)),
+ "./mailViews",
+ );
+
+ const handlebarOptions = {
+ viewEngine: {
+ extname: ".hbs",
+ defaultLayout: "",
+ partialsDir: viewPath,
+ },
+ viewPath: viewPath,
+ extName: ".hbs",
+ };
+
+ transporter.use("compile", hbs(handlebarOptions));
+
+ const mailOptions: HandlebarsMailOptions = {
+ from: fromEmail,
+ to: data.email,
+ subject: data.subject,
+ //text: "You will have a reset token here and only have 30min to click the link before it expires.",
+ //html: emailTemplate("Blake's Test", "This is an example with css"),
+ template: data.template, // Name of the Handlebars template (e.g., 'welcome.hbs')
+ context: data.context,
+ };
+
+ // now verify and send the email
+ const sendMailPromise = promisify(transporter.sendMail).bind(transporter);
+
+ const { data: mail, error } = await tryCatch(sendMailPromise(mailOptions));
+
+ if (mail) {
+ return returnFunc({
+ success: true,
+ level: "info",
+ module: "utils",
+ subModule: "sendmail",
+ message: `Email was sent to: ${data.email}`,
+ data: [],
+ notify: false,
+ });
+ }
+
+ if (error) {
+ return returnFunc({
+ success: false,
+ level: "error",
+ module: "utils",
+ subModule: "sendmail",
+ message: `Error sending Email to : ${data.email}`,
+ data: [{ error: error }],
+ notify: false,
+ });
+ }
+};
diff --git a/migrations/0001_puzzling_enchantress.sql b/migrations/0001_puzzling_enchantress.sql
new file mode 100644
index 0000000..734cb61
--- /dev/null
+++ b/migrations/0001_puzzling_enchantress.sql
@@ -0,0 +1,15 @@
+CREATE TABLE "datamart" (
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
+ "name" text,
+ "description" text NOT NULL,
+ "query" text,
+ "version" integer NOT NULL,
+ "active" boolean DEFAULT true,
+ "checked" text DEFAULT '',
+ "add_date" timestamp DEFAULT now(),
+ "add_user" text DEFAULT 'lst-system',
+ "upd_date" text DEFAULT 'lst-system'
+);
+--> statement-breakpoint
+ALTER TABLE "logs" ADD COLUMN "created_at" timestamp DEFAULT now();--> statement-breakpoint
+ALTER TABLE "logs" DROP COLUMN "createdAt";
\ No newline at end of file
diff --git a/migrations/0002_pink_nightshade.sql b/migrations/0002_pink_nightshade.sql
new file mode 100644
index 0000000..682e8eb
--- /dev/null
+++ b/migrations/0002_pink_nightshade.sql
@@ -0,0 +1,53 @@
+CREATE TABLE "account" (
+ "id" text PRIMARY KEY NOT NULL,
+ "account_id" text NOT NULL,
+ "provider_id" text NOT NULL,
+ "user_id" text NOT NULL,
+ "access_token" text,
+ "refresh_token" text,
+ "id_token" text,
+ "access_token_expires_at" timestamp,
+ "refresh_token_expires_at" timestamp,
+ "scope" text,
+ "password" text,
+ "created_at" timestamp DEFAULT now() NOT NULL,
+ "updated_at" timestamp NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE "session" (
+ "id" text PRIMARY KEY NOT NULL,
+ "expires_at" timestamp NOT NULL,
+ "token" text NOT NULL,
+ "created_at" timestamp DEFAULT now() NOT NULL,
+ "updated_at" timestamp NOT NULL,
+ "ip_address" text,
+ "user_agent" text,
+ "user_id" text NOT NULL,
+ CONSTRAINT "session_token_unique" UNIQUE("token")
+);
+--> statement-breakpoint
+CREATE TABLE "user" (
+ "id" text PRIMARY KEY NOT NULL,
+ "name" text NOT NULL,
+ "email" text NOT NULL,
+ "email_verified" boolean DEFAULT false NOT NULL,
+ "image" text,
+ "created_at" timestamp DEFAULT now() NOT NULL,
+ "updated_at" timestamp DEFAULT now() NOT NULL,
+ CONSTRAINT "user_email_unique" UNIQUE("email")
+);
+--> statement-breakpoint
+CREATE TABLE "verification" (
+ "id" text PRIMARY KEY NOT NULL,
+ "identifier" text NOT NULL,
+ "value" text NOT NULL,
+ "expires_at" timestamp NOT NULL,
+ "created_at" timestamp DEFAULT now() NOT NULL,
+ "updated_at" timestamp DEFAULT now() NOT NULL
+);
+--> statement-breakpoint
+ALTER TABLE "account" ADD CONSTRAINT "account_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "session" ADD CONSTRAINT "session_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+CREATE INDEX "account_userId_idx" ON "account" USING btree ("user_id");--> statement-breakpoint
+CREATE INDEX "session_userId_idx" ON "session" USING btree ("user_id");--> statement-breakpoint
+CREATE INDEX "verification_identifier_idx" ON "verification" USING btree ("identifier");
\ No newline at end of file
diff --git a/migrations/0003_orange_vision.sql b/migrations/0003_orange_vision.sql
new file mode 100644
index 0000000..6747b17
--- /dev/null
+++ b/migrations/0003_orange_vision.sql
@@ -0,0 +1,44 @@
+CREATE TABLE "apikey" (
+ "id" text PRIMARY KEY NOT NULL,
+ "name" text,
+ "start" text,
+ "prefix" text,
+ "key" text NOT NULL,
+ "user_id" text NOT NULL,
+ "refill_interval" integer,
+ "refill_amount" integer,
+ "last_refill_at" timestamp,
+ "enabled" boolean DEFAULT true,
+ "rate_limit_enabled" boolean DEFAULT true,
+ "rate_limit_time_window" integer DEFAULT 86400000,
+ "rate_limit_max" integer DEFAULT 10,
+ "request_count" integer DEFAULT 0,
+ "remaining" integer,
+ "last_request" timestamp,
+ "expires_at" timestamp,
+ "created_at" timestamp NOT NULL,
+ "updated_at" timestamp NOT NULL,
+ "permissions" text,
+ "metadata" text
+);
+--> statement-breakpoint
+CREATE TABLE "jwks" (
+ "id" text PRIMARY KEY NOT NULL,
+ "public_key" text NOT NULL,
+ "private_key" text NOT NULL,
+ "created_at" timestamp NOT NULL,
+ "expires_at" timestamp
+);
+--> statement-breakpoint
+ALTER TABLE "session" ADD COLUMN "impersonated_by" text;--> statement-breakpoint
+ALTER TABLE "user" ADD COLUMN "role" text;--> statement-breakpoint
+ALTER TABLE "user" ADD COLUMN "banned" boolean DEFAULT false;--> statement-breakpoint
+ALTER TABLE "user" ADD COLUMN "ban_reason" text;--> statement-breakpoint
+ALTER TABLE "user" ADD COLUMN "ban_expires" timestamp;--> statement-breakpoint
+ALTER TABLE "user" ADD COLUMN "username" text;--> statement-breakpoint
+ALTER TABLE "user" ADD COLUMN "display_username" text;--> statement-breakpoint
+ALTER TABLE "user" ADD COLUMN "last_login" timestamp DEFAULT now();--> statement-breakpoint
+ALTER TABLE "apikey" ADD CONSTRAINT "apikey_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+CREATE INDEX "apikey_key_idx" ON "apikey" USING btree ("key");--> statement-breakpoint
+CREATE INDEX "apikey_userId_idx" ON "apikey" USING btree ("user_id");--> statement-breakpoint
+ALTER TABLE "user" ADD CONSTRAINT "user_username_unique" UNIQUE("username");
\ No newline at end of file
diff --git a/migrations/meta/0001_snapshot.json b/migrations/meta/0001_snapshot.json
new file mode 100644
index 0000000..3a77feb
--- /dev/null
+++ b/migrations/meta/0001_snapshot.json
@@ -0,0 +1,169 @@
+{
+ "id": "dd230ac4-0bd9-4df4-b1da-4d97ac4cf7c4",
+ "prevId": "a42ab1d2-29ee-43ca-bcad-7b14c545172e",
+ "version": "7",
+ "dialect": "postgresql",
+ "tables": {
+ "public.datamart": {
+ "name": "datamart",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "query": {
+ "name": "query",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "version": {
+ "name": "version",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "active": {
+ "name": "active",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": true
+ },
+ "checked": {
+ "name": "checked",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "''"
+ },
+ "add_date": {
+ "name": "add_date",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "add_user": {
+ "name": "add_user",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'lst-system'"
+ },
+ "upd_date": {
+ "name": "upd_date",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'lst-system'"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.logs": {
+ "name": "logs",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "level": {
+ "name": "level",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "module": {
+ "name": "module",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "subModule": {
+ "name": "subModule",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "message": {
+ "name": "message",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "stack": {
+ "name": "stack",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'[]'::jsonb"
+ },
+ "checked": {
+ "name": "checked",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "hostname": {
+ "name": "hostname",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ }
+ },
+ "enums": {},
+ "schemas": {},
+ "sequences": {},
+ "roles": {},
+ "policies": {},
+ "views": {},
+ "_meta": {
+ "columns": {},
+ "schemas": {},
+ "tables": {}
+ }
+}
\ No newline at end of file
diff --git a/migrations/meta/0002_snapshot.json b/migrations/meta/0002_snapshot.json
new file mode 100644
index 0000000..03d18d6
--- /dev/null
+++ b/migrations/meta/0002_snapshot.json
@@ -0,0 +1,524 @@
+{
+ "id": "27d37c0d-a6f0-4acc-9a9b-3d50096e56dd",
+ "prevId": "dd230ac4-0bd9-4df4-b1da-4d97ac4cf7c4",
+ "version": "7",
+ "dialect": "postgresql",
+ "tables": {
+ "public.account": {
+ "name": "account",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "account_id": {
+ "name": "account_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provider_id": {
+ "name": "provider_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "access_token": {
+ "name": "access_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_token": {
+ "name": "refresh_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "id_token": {
+ "name": "id_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "access_token_expires_at": {
+ "name": "access_token_expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_token_expires_at": {
+ "name": "refresh_token_expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "account_userId_idx": {
+ "name": "account_userId_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "account_user_id_user_id_fk": {
+ "name": "account_user_id_user_id_fk",
+ "tableFrom": "account",
+ "tableTo": "user",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.session": {
+ "name": "session",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "ip_address": {
+ "name": "ip_address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_agent": {
+ "name": "user_agent",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "session_userId_idx": {
+ "name": "session_userId_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "session_user_id_user_id_fk": {
+ "name": "session_user_id_user_id_fk",
+ "tableFrom": "session",
+ "tableTo": "user",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "session_token_unique": {
+ "name": "session_token_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "token"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.user": {
+ "name": "user",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "email_verified": {
+ "name": "email_verified",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "image": {
+ "name": "image",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "user_email_unique": {
+ "name": "user_email_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "email"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.verification": {
+ "name": "verification",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "identifier": {
+ "name": "identifier",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "verification_identifier_idx": {
+ "name": "verification_identifier_idx",
+ "columns": [
+ {
+ "expression": "identifier",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.datamart": {
+ "name": "datamart",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "query": {
+ "name": "query",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "version": {
+ "name": "version",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "active": {
+ "name": "active",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": true
+ },
+ "checked": {
+ "name": "checked",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "''"
+ },
+ "add_date": {
+ "name": "add_date",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "add_user": {
+ "name": "add_user",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'lst-system'"
+ },
+ "upd_date": {
+ "name": "upd_date",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'lst-system'"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.logs": {
+ "name": "logs",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "level": {
+ "name": "level",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "module": {
+ "name": "module",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "subModule": {
+ "name": "subModule",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "message": {
+ "name": "message",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "stack": {
+ "name": "stack",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'[]'::jsonb"
+ },
+ "checked": {
+ "name": "checked",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "hostname": {
+ "name": "hostname",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ }
+ },
+ "enums": {},
+ "schemas": {},
+ "sequences": {},
+ "roles": {},
+ "policies": {},
+ "views": {},
+ "_meta": {
+ "columns": {},
+ "schemas": {},
+ "tables": {}
+ }
+}
\ No newline at end of file
diff --git a/migrations/meta/0003_snapshot.json b/migrations/meta/0003_snapshot.json
new file mode 100644
index 0000000..449038f
--- /dev/null
+++ b/migrations/meta/0003_snapshot.json
@@ -0,0 +1,813 @@
+{
+ "id": "f82bd918-f5f0-4c05-ba25-8a0e97891f5f",
+ "prevId": "27d37c0d-a6f0-4acc-9a9b-3d50096e56dd",
+ "version": "7",
+ "dialect": "postgresql",
+ "tables": {
+ "public.account": {
+ "name": "account",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "account_id": {
+ "name": "account_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provider_id": {
+ "name": "provider_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "access_token": {
+ "name": "access_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_token": {
+ "name": "refresh_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "id_token": {
+ "name": "id_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "access_token_expires_at": {
+ "name": "access_token_expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_token_expires_at": {
+ "name": "refresh_token_expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "account_userId_idx": {
+ "name": "account_userId_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "account_user_id_user_id_fk": {
+ "name": "account_user_id_user_id_fk",
+ "tableFrom": "account",
+ "tableTo": "user",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.apikey": {
+ "name": "apikey",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "start": {
+ "name": "start",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "prefix": {
+ "name": "prefix",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "refill_interval": {
+ "name": "refill_interval",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refill_amount": {
+ "name": "refill_amount",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_refill_at": {
+ "name": "last_refill_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": true
+ },
+ "rate_limit_enabled": {
+ "name": "rate_limit_enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": true
+ },
+ "rate_limit_time_window": {
+ "name": "rate_limit_time_window",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 86400000
+ },
+ "rate_limit_max": {
+ "name": "rate_limit_max",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 10
+ },
+ "request_count": {
+ "name": "request_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 0
+ },
+ "remaining": {
+ "name": "remaining",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_request": {
+ "name": "last_request",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "permissions": {
+ "name": "permissions",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "apikey_key_idx": {
+ "name": "apikey_key_idx",
+ "columns": [
+ {
+ "expression": "key",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "apikey_userId_idx": {
+ "name": "apikey_userId_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "apikey_user_id_user_id_fk": {
+ "name": "apikey_user_id_user_id_fk",
+ "tableFrom": "apikey",
+ "tableTo": "user",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.jwks": {
+ "name": "jwks",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "public_key": {
+ "name": "public_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "private_key": {
+ "name": "private_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.session": {
+ "name": "session",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "ip_address": {
+ "name": "ip_address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_agent": {
+ "name": "user_agent",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "impersonated_by": {
+ "name": "impersonated_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "session_userId_idx": {
+ "name": "session_userId_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "session_user_id_user_id_fk": {
+ "name": "session_user_id_user_id_fk",
+ "tableFrom": "session",
+ "tableTo": "user",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "session_token_unique": {
+ "name": "session_token_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "token"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.user": {
+ "name": "user",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "email_verified": {
+ "name": "email_verified",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "image": {
+ "name": "image",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "role": {
+ "name": "role",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "banned": {
+ "name": "banned",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "ban_reason": {
+ "name": "ban_reason",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ban_expires": {
+ "name": "ban_expires",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "display_username": {
+ "name": "display_username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_login": {
+ "name": "last_login",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "user_email_unique": {
+ "name": "user_email_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "email"
+ ]
+ },
+ "user_username_unique": {
+ "name": "user_username_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "username"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.verification": {
+ "name": "verification",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "identifier": {
+ "name": "identifier",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "verification_identifier_idx": {
+ "name": "verification_identifier_idx",
+ "columns": [
+ {
+ "expression": "identifier",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.datamart": {
+ "name": "datamart",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "query": {
+ "name": "query",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "version": {
+ "name": "version",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "active": {
+ "name": "active",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": true
+ },
+ "checked": {
+ "name": "checked",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "''"
+ },
+ "add_date": {
+ "name": "add_date",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "add_user": {
+ "name": "add_user",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'lst-system'"
+ },
+ "upd_date": {
+ "name": "upd_date",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'lst-system'"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.logs": {
+ "name": "logs",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "level": {
+ "name": "level",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "module": {
+ "name": "module",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "subModule": {
+ "name": "subModule",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "message": {
+ "name": "message",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "stack": {
+ "name": "stack",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'[]'::jsonb"
+ },
+ "checked": {
+ "name": "checked",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "hostname": {
+ "name": "hostname",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ }
+ },
+ "enums": {},
+ "schemas": {},
+ "sequences": {},
+ "roles": {},
+ "policies": {},
+ "views": {},
+ "_meta": {
+ "columns": {},
+ "schemas": {},
+ "tables": {}
+ }
+}
\ No newline at end of file
diff --git a/migrations/meta/_journal.json b/migrations/meta/_journal.json
index 8cad11c..5e7f1df 100644
--- a/migrations/meta/_journal.json
+++ b/migrations/meta/_journal.json
@@ -8,6 +8,27 @@
"when": 1766759965948,
"tag": "0000_ordinary_justice",
"breakpoints": true
+ },
+ {
+ "idx": 1,
+ "version": "7",
+ "when": 1767015305152,
+ "tag": "0001_puzzling_enchantress",
+ "breakpoints": true
+ },
+ {
+ "idx": 2,
+ "version": "7",
+ "when": 1767016655572,
+ "tag": "0002_pink_nightshade",
+ "breakpoints": true
+ },
+ {
+ "idx": 3,
+ "version": "7",
+ "when": 1767020576353,
+ "tag": "0003_orange_vision",
+ "breakpoints": true
}
]
}
\ No newline at end of file