4 Commits

37 changed files with 10999 additions and 88 deletions

View File

@@ -54,6 +54,8 @@
"alplaprod",
"Datamart",
"intiallally",
"OCME",
"onnotice",
"ppoo",
"prodlabels"
],

View File

@@ -1,3 +1,46 @@
# lst_v3
# Logistics support tool
The tool that supports us in our everyday alplaprod adventures
> The support tool for ALPLA Prod
## Overview
Quick summary of current rewrite/migration goal.
- **Phase:** Backend rewrite
- **Last updated:** 2024-05-01
---
## Feature Status
| Feature | Description | Status |
|----------|--------------|--------|
| User Authentication | Login, Signup, JWT refresh, API Key | 🟨 In Progress |
| User Profile | Edit profile, upload avatar | ⏳ Not Started |
| Notifications | Subscribe, Create, Update | ⏳ Not Started |
| Datamart | Create, Update, Run | 🔧 In Progress |
| Frontend | Analytics and charts | ⏳ Not Started |
| One Click Print | Printing system | ⏳ Not Started |
| Silo Adjustments | Adjustments | ⏳ Not Started |
| Demand Management | Orders, Forecast | ⏳ Not Started |
| Open Docks | Integrations | ⏳ Not Started |
| Transport Insight | Integrations | ⏳ Not Started |
| Quality | Request Tool | ⏳ Not Started |
| OCME | Custom integration | ⏳ Not Started |
| API Migration | Moving to new REST endpoints | 🔧 In Progress |
| System | Tests, Updates, Remote Logging | ⏳ Not Started |
_Status legend:_
✅ Complete🟨 In Progress ⏳ Not Started
---
## Setup / Installation
How to run the current version of the app.
```bash
git clone https://github.com/youruser/yourrepo.git
cd yourrepo
npm install
npm run dev

View File

@@ -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;

22
backend/server.ts Normal file
View File

@@ -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();

View File

@@ -0,0 +1,18 @@
import { Router } from "express";
import { apiReturn } from "../utils/returnHelper.utils.js";
const r = Router();
r.post("/add", async (_, res) => {
apiReturn(res, {
success: true,
level: "info",
module: "routes",
subModule: "prodSql",
message: "connect.message",
data: [{ connect: "" }],
status: 200,
});
});
export default r;

View File

@@ -0,0 +1,16 @@
import { drizzle } from "drizzle-orm/postgres-js";
import postgres from "postgres";
const dbURL = `postgres://${process.env.DATABASE_USER}:${process.env.DATABASE_PASSWORD}@${process.env.DATABASE_HOST}:${process.env.DATABASE_PORT}/${process.env.DATABASE_DB}`;
const queryClient = postgres(dbURL, {
max: 10,
idle_timeout: 60,
connect_timeout: 30,
max_lifetime: 1000 * 6 * 5,
onnotice: (n) => {
console.info("PG notice: ", n.message);
},
});
export const db = drizzle({ client: queryClient });

View File

@@ -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],
}),
}));

View File

@@ -0,0 +1,30 @@
import {
boolean,
integer,
pgTable,
text,
timestamp,
uuid,
} from "drizzle-orm/pg-core";
import { createInsertSchema, createSelectSchema } from "drizzle-zod";
import type { z } from "zod";
export const datamart = pgTable("datamart", {
id: uuid("id").defaultRandom().primaryKey(),
name: text("name"),
description: text("description").notNull(),
query: text("query"),
version: integer("version").notNull(),
active: boolean("active").default(true),
options: text("checked").default(""),
add_date: timestamp("add_date").defaultNow(),
add_user: text("add_user").default("lst-system"),
upd_date: timestamp("upd_date").defaultNow(),
upd_user: text("upd_date").default("lst-system"),
});
export const datamartSchema = createSelectSchema(datamart);
export const newDataMartSchema = createInsertSchema(datamart);
export type Datamart = z.infer<typeof datamartSchema>;
export type NewDatamart = z.infer<typeof newDataMartSchema>;

View File

@@ -0,0 +1,28 @@
import {
boolean,
jsonb,
pgTable,
text,
timestamp,
uuid,
} from "drizzle-orm/pg-core";
import { createInsertSchema, createSelectSchema } from "drizzle-zod";
import type { z } from "zod";
export const logs = pgTable("logs", {
id: uuid("id").defaultRandom().primaryKey(),
level: text("level"),
module: text("module").notNull(),
subModule: text("subModule"),
message: text("message").notNull(),
stack: jsonb("stack").default([]),
checked: boolean("checked").default(false),
hostname: text("hostname"),
createdAt: timestamp("created_at").defaultNow(),
});
export const logSchema = createSelectSchema(logs);
export const newLogSchema = createInsertSchema(logs);
export type Log = z.infer<typeof logSchema>;
export type NewLog = z.infer<typeof newLogSchema>;

View File

@@ -0,0 +1,43 @@
import build from "pino-abstract-transport";
import { db } from "../db/db.controller.js";
import { logs } from "../db/schema/logs.schema.js";
import { tryCatch } from "../utils/trycatch.utils.js";
const pinoLogLevels: Record<number, string> = {
10: "trace",
20: "debug",
30: "info",
40: "warn",
50: "error",
60: "fatal",
};
// Create a custom transport function
export default async function () {
//const {username, service, level, msg, ...extra} = log;
try {
return build(async (source) => {
for await (const obj of source) {
// convert to the name to make it more easy to find later :P
const levelName = pinoLogLevels[obj.level] || "unknown";
const res = await tryCatch(
db.insert(logs).values({
level: levelName,
module: obj?.module?.toLowerCase(),
subModule: obj?.subModule?.toLowerCase(),
hostname: obj?.hostname?.toLowerCase(),
message: obj.msg,
stack: obj?.stack,
}),
);
if (res.error) {
console.error(res.error);
}
}
});
} catch (err) {
console.error("Error inserting log into database:", err);
}
}

View File

@@ -12,6 +12,9 @@ const transport = pino.transport({
destination: process.stdout.fd,
},
},
{
target: "./db.transport.ts",
},
],
});

View File

@@ -1,16 +1,15 @@
import type { Express } from "express";
// import the routes and route setups
import { setupApiDocsRoutes } from "./configs/scaler.config.js";
import { setupDatamartRoutes } from "./datamart/datamart.routes.js";
import { setupProdSqlRoutes } from "./prodSql/prodSql.routes.js";
// import { setupApiDocsRoutes } from "./configs/scaler.config.js";
// import { setupDatamartRoutes } from "./datamart/datamart.routes.js";
// import { setupProdSqlRoutes } from "./prodSql/prodSql.routes.js";
import stats from "./system/stats.route.js";
export const setupRoutes = (baseUrl: string, app: Express) => {
//setup all the routes
setupApiDocsRoutes(baseUrl, app);
setupProdSqlRoutes(baseUrl, app);
setupDatamartRoutes(baseUrl, app);
app.use(`${baseUrl}/api/stats`, stats);
//setup all the routes
// setupApiDocsRoutes(baseUrl, app);
// setupProdSqlRoutes(baseUrl, app);
// setupDatamartRoutes(baseUrl, app);
};

View File

@@ -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));
},
},
});

View File

@@ -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",
"*",
],
});
};

View File

@@ -0,0 +1,69 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Password Reset</title>
{{> styles}}
<style>
body {
font-family: Arial, sans-serif;
color: #333333;
line-height: 1.6;
margin: 0;
padding: 0;
background-color: #f9fafb;
}
.email-container {
max-width: 600px;
margin: 40px auto;
padding: 20px;
background-color: #ffffff;
border-radius: 8px;
box-shadow: 0 2px 6px rgba(0,0,0,0.08);
}
h2 {
color: #1a1a1a;
}
.btn {
display: inline-block;
padding: 12px 24px;
margin: 20px 0;
font-size: 16px;
color: #ffffff;
background-color: #4f46e5;
text-decoration: none;
border-radius: 6px;
}
.footer {
font-size: 13px;
color: #666666;
margin-top: 30px;
}
</style>
</head>
<body>
<div class="email-container">
<h2>Password Reset Request</h2>
<p>Hi {{username}},</p>
<p>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:</p>
<p style="text-align:center;">
<a href="{{url}}" class="btn">Reset Your Password</a>
</p>
<p>If the button above doesnt work, copy and paste the following link into your web browser:</p>
<p style="word-break: break-all; color:#4f46e5;">{{url}}</p>
<p><strong>Note:</strong> This link will expire in <b>{{expiry}}</b> for your security.</p>
<p>If you did not request a password reset, you can safely ignore this email — your account will remain secure.</p>
<p>Thanks,<br/>The LST Team</p>
<div class="footer">
<p>You are receiving this email because a password reset was requested for your account.</p>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,6 @@
<style>
table { width: 100%; background-color: #ffffff; border-collapse: collapse;
border-width: 2px; border-color: #14BDEA; border-style: solid; color:
#000000; } th, td { border: 1px solid #ddd; padding: 8px; } th {
background-color: #f4f4f4; }
</style>

View File

@@ -0,0 +1,33 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Order Summary</title>
{{> styles}}
{{!-- <link rel="stylesheet" href="styles/styles.css" /> --}}
</head>
<body>
<h1>{{name}}, This is a test that the emailing actually works as intended.</h1>
<p>All,
This is an example of the test email
</p>
{{!-- <table>
<thead>
<tr>
<th>Item Name</th>
<th>Quantity</th>
<th>Price</th>
</tr>
</thead>
<tbody>
{{#each items}}
<tr>
<td>{{name}}</td>
<td>{{quantity}}</td>
<td>{{price}}</td>
</tr>
{{/each}}
</tbody>
</table> --}}
</body>
</html>

View File

@@ -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 || [],
};
};

View File

@@ -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<string, unknown>;
}
interface EmailData {
email: string;
subject: string;
template: string;
context: Record<string, unknown>;
}
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,
});
}
};

10
drizzle.config.ts Normal file
View File

@@ -0,0 +1,10 @@
import { defineConfig } from "drizzle-kit";
const dbURL = `postgres://${process.env.DATABASE_USER}:${process.env.DATABASE_PASSWORD}@${process.env.DATABASE_HOST}:${process.env.DATABASE_PORT}/${process.env.DATABASE_DB}`;
export default defineConfig({
dialect: "postgresql",
schema: "./dist/backend/src/db/schema",
out: "./migrations",
dbCredentials: { url: dbURL },
});

View File

@@ -0,0 +1,11 @@
CREATE TABLE "logs" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"level" text,
"module" text NOT NULL,
"subModule" text,
"message" text NOT NULL,
"stack" jsonb DEFAULT '[]'::jsonb,
"checked" boolean DEFAULT false,
"hostname" text,
"createdAt" timestamp DEFAULT now()
);

View File

@@ -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";

View File

@@ -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");

View File

@@ -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");

View File

@@ -0,0 +1,90 @@
{
"id": "a42ab1d2-29ee-43ca-bcad-7b14c545172e",
"prevId": "00000000-0000-0000-0000-000000000000",
"version": "7",
"dialect": "postgresql",
"tables": {
"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
},
"createdAt": {
"name": "createdAt",
"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": {}
}
}

View File

@@ -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": {}
}
}

View File

@@ -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": {}
}
}

View File

@@ -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": {}
}
}

View File

@@ -0,0 +1,34 @@
{
"version": "7",
"dialect": "postgresql",
"entries": [
{
"idx": 0,
"version": "7",
"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
}
]
}

8416
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -4,10 +4,12 @@
"description": "The tool that supports us in our everyday alplaprod",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"test": "dotenvx run -f .env -- vitest",
"dev": "dotenvx run -f .env -- npm run dev:app",
"dev:app": "cd backend && tsx watch app.ts",
"build": "npm run specCheck && npm run lint && npm run build:app",
"dev:app": "cd backend && tsx watch server.ts",
"dev:db:migrate": "npx drizzle-kit push",
"dev:db:generate": "tsc && npx drizzle-kit generate --config=drizzle.config.ts",
"build": "npm run specCheck && npm run lint && npm run dev:db:generate && npm run dev:db:migrate && npm run build:app && rimraf dist/backend",
"build:app": "ncc build backend/app.ts -o dist -m -s",
"lint": "tsc && biome lint",
"start": "dotenvx run -f .env -- node dist/index.js",
@@ -16,7 +18,6 @@
"version": "changeset version",
"release": "dotenvx run -f .env -- npm run version && git push --follow-tags && node scripts/create-release.js",
"specCheck": "node scripts/check-route-specs.mjs"
},
"repository": {
"type": "git",
@@ -32,28 +33,47 @@
"@commitlint/cli": "^18.4.0",
"@commitlint/config-conventional": "^18.4.0",
"@scalar/express-api-reference": "^0.8.28",
"@swc/core": "^1.15.7",
"@swc/jest": "^0.2.39",
"@types/cors": "^2.8.19",
"@types/express": "^5.0.6",
"@types/morgan": "^1.9.10",
"@types/mssql": "^9.1.8",
"@types/node": "^24.10.1",
"@types/nodemailer": "^7.0.4",
"@types/nodemailer-express-handlebars": "^4.0.6",
"@types/pg": "^8.16.0",
"@types/supertest": "^6.0.3",
"@types/swagger-jsdoc": "^6.0.4",
"@types/swagger-ui-express": "^4.1.8",
"@vercel/ncc": "^0.38.4",
"axios": "^1.13.2",
"better-auth": "^1.4.6",
"better-auth": "^1.4.9",
"commitizen": "^4.3.0",
"cors": "^2.8.5",
"cz-conventional-changelog": "^3.3.0",
"drizzle-kit": "^0.31.8",
"drizzle-orm": "^0.45.1",
"drizzle-zod": "^0.8.3",
"express": "^5.2.1",
"husky": "^8.0.3",
"morgan": "^1.10.1",
"mssql": "^12.2.0",
"nodemailer": "^7.0.12",
"nodemailer-express-handlebars": "^7.0.0",
"npm-check-updates": "^19.1.2",
"openapi-types": "^12.1.3",
"pg": "^8.16.3",
"pino": "^10.1.0",
"pino-pretty": "^13.1.3",
"postgres": "^3.4.7",
"supertest": "^7.1.4",
"ts-jest": "^29.4.6",
"ts-node-dev": "^2.0.0",
"tsx": "^4.21.0",
"typescript": "^5.9.3"
"typescript": "^5.9.3",
"vite-tsconfig-paths": "^6.0.3",
"vitest": "^4.0.16"
},
"dependencies": {
"@dotenvx/dotenvx": "^1.51.2"

View File

@@ -0,0 +1,29 @@
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
import { connectProdSql } from "../backend/src/prodSql/prodSqlConnection.controller.js";
let pool: any;
describe("Prod SQL connection", () => {
// This may take seconds, so give plenty of time
vi.setTimeout(30000);
beforeAll(async () => {
pool = await connectProdSql();
});
afterAll(async () => {
if (pool && pool.close) await pool.close();
});
it("should connect and return expected data", async () => {
// Example query — use something safe and consistent
const result = await pool
.request()
.query("SELECT TOP 1 id, name FROM Users ORDER BY id ASC");
expect(result.recordset).toBeDefined();
expect(Array.isArray(result.recordset)).toBe(true);
expect(result.recordset.length).toBeGreaterThan(0);
expect(result.recordset[0]).toHaveProperty("id");
});
});

42
tests/sendEmail.test.ts Normal file
View File

@@ -0,0 +1,42 @@
import { describe, expect, test, vi } from "vitest";
import { sendEmail } from "../backend/src/utils/sendEmail.utils";
// Mock the logger before imports
vi.mock("../backend/src/logger/logger.controller", () => ({
createLogger: () => ({
info: vi.fn(),
error: vi.fn(),
debug: vi.fn(),
fatal: vi.fn(),
}),
}));
describe("Mail sending", () => {
test("should send an email successfully", async () => {
const result = await sendEmail({
email: "blake.matthes@alpla.com",
subject: "LST - Testing system",
template: "testEmail",
context: { name: "blake" },
});
expect(result).toMatchObject({
success: true,
});
expect(result?.message).toContain("blake.matthes@alpla.com");
});
test("should handle email send failure gracefully", async () => {
const badResult = await sendEmail({
email: "invalid-address",
subject: "LST - Testing system",
template: "",
context: {},
});
expect(badResult).toMatchObject({
success: false,
});
});
});

View File

@@ -5,7 +5,7 @@
"moduleResolution": "nodenext",
"strict": true,
"verbatimModuleSyntax": true,
"types": ["node", "better-auth"],
"types": ["node", "better-auth", "jest"],
"jsx": "react-jsx",
"outDir": "./dist",
"removeComments": true,

11
tsconfig.test.json Normal file
View File

@@ -0,0 +1,11 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "ESNext",
"moduleResolution": "bundler",
"target": "ESNext",
"esModuleInterop": true,
"allowSyntheticDefaultImports": true
},
"include": ["tests/**/*", "backend/**/*"]
}

8
vitest.config.ts Normal file
View File

@@ -0,0 +1,8 @@
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
globals: true,
include: ["tests/**/*.test.ts"], // ← point to your tests folder
},
});