feat: upload logs to sern bin, bye bye frontend, basically done

This commit is contained in:
2024-03-02 16:01:08 +00:00
committed by GitHub
parent 1878845a6b
commit d7000c0838
41 changed files with 294 additions and 5431 deletions

View File

@@ -1,14 +0,0 @@
FROM node:lts-alpine
WORKDIR /app
COPY . .
RUN yarn
ENV SKIP_ENV_VALIDATION=true
ENV API_URL="http://api.railway.internal:4000"
RUN yarn build:database
RUN yarn build:frontend
CMD yarn start:frontend

View File

@@ -8,10 +8,10 @@ import rateLimit from 'express-rate-limit';
import { Webhook } from 'simple-discord-webhooks';
import { codeBlock } from './util/discordCodeBlock.js';
import db, { schema } from 'database/dist/index.js';
import jobs, { LogGroup } from './jobs.js';
import jobs from './jobs.js';
import expressWs from 'express-ws';
import resolvePlugins from './util/resolvePlugins.js';
import { PassThrough } from 'node:stream';
import { stripIndents } from 'common-tags';
const devMode = process.argv[2] === '--dev';
if (devMode) console.log('You\'re a developer 😎 (sorry for that emoji jumpscare)')
@@ -33,7 +33,6 @@ app.get('/', (req, res) => {
})
for (const job of jobs) {
const jobLogs: Logs[] = []
switch (job.method) {
case "POST":
app.post(job.route, async (req, res) => {
@@ -53,23 +52,20 @@ for (const job of jobs) {
return res.status(418).send({ success: false, message: "Plugins didn't pass" });
res.send({ success: true, message: "Command is running" });
const stream_stdout = new PassThrough();
const stream_stderr = new PassThrough();
const parse_payload = (level: 'info' | 'error', payload: any) => ({
timestamp: new Date(),
message: String(payload),
level
})
stream_stdout.on('data', (chunk) => {
jobLogs.push(parse_payload('info', chunk))
});
stream_stderr.on('data', chunk => {
jobLogs.push(parse_payload('error', chunk))
message: payload.toString(),
level
})
const jobLogs = [] as { step: number, logs: Logs[] }[]
for (const steps of job.steps) {
console.log(`Running step ${steps.name}`);
const cmd = execa(
try {
for (let i = 0; i < job.steps.length; i++) {
const steps = job.steps[i]!;
const logsToPush = [] as Logs[];
console.log(`Running step ${steps.name}`);
const cmd = execa(
"bash",
[`${cwd}/scripts/${job.stepsMainDir}/${steps.script}`],
{
@@ -78,43 +74,63 @@ for (const job of jobs) {
env: { NT_ARGS: JSON.stringify(job.cmdArgs) },
},
)
cmd?.pipeStdout?.(stream_stdout)
cmd?.pipeStderr?.(stream_stderr)
const exitCode = await new Promise((resolve) => {
cmd.once("exit", (code) => {
if (code !== 0) {
console.log(
`Step ${steps.name} failed with code ${code}`,
);
} else {
cmd.stdout!.on('data', (data) => logsToPush.push(parse_payload('info', data.toString().replace(/\n$/, ""))));
cmd.stderr!.on('data', (data) => logsToPush.push(parse_payload('error', data.toString().replace(/\n$/, ""))));
await new Promise((resolve, reject) => {
cmd.once('exit', (code) => {
if (code === 0) {
console.log(`Step ${steps.name} finished successfully`);
logsToPush.push(parse_payload('info', 'Step finished successfully'));
jobLogs.push({ step: steps.id, logs: logsToPush });
resolve('nice');
} else {
console.log(`Step ${steps.name} failed with code ${code}`);
logsToPush.push(parse_payload('error', `Step failed with code ${code}`));
jobLogs.push({ step: steps.id, logs: logsToPush });
reject('stop it');
}
resolve(code);
});
});
if (exitCode !== 0) {
db.insert(schema.stepLogs).values({
pkey: crypto.randomUUID(),
id: steps.id.toString(),
logs: jobLogs,
// i gtg but we need to register the job run first
// tysm seren for helping me <3
// np
})
}
}
} catch {}
// const cmd = execa(
// "bash",
// [`${cwd}/scripts/${job.stepsMainDir}/${steps.script}`],
// {
// cwd: steps.cwd,
// shell: true,
// env: { NT_ARGS: JSON.stringify(job.cmdArgs) },
// },
// );
const markdownText = stripIndents`
# Job ${job.name} finished
## Steps
${jobLogs.map((step) => {
return `### Step ${step.step}\n\`\`\`\n${step.logs.map((log) => {
return `${log.timestamp.toISOString()} - ${log.level.toUpperCase()} | ${log.message}`;
}).join('\n')}\n\`\`\``;
}).join('\n')}
`;
const createSnippet = await fetch(`${process.env.SERN_BIN_ENDPOINT}/api/create`, {
method: 'POST',
headers: {
'Authorization': process.env.SERN_BIN_KEY!,
},
body: JSON.stringify({
fileName: `run-${job.name}-${new Date().toISOString()}.md`,
description: `Logs for ${job.name} job`,
authorId: process.env.SERN_BIN_USER,
lang: "markdown",
code: markdownText
})
}).then(async res => (await res.text()).replaceAll('"', ''))
const dbWrite = (await db.insert(schema.jobsList).values({
name: job.name,
steps: job.steps,
sernbinid: createSnippet
}).returning())[0]
const webhook = new Webhook(new URL(process.env.AUTOMATA_CHANNEL_WEBHOOK!), 'Job Logs (by automata)', 'https://avatars.githubusercontent.com/u/129876409?v=4')
webhook.send(`Job #${dbWrite?.id} ${job.name} finished`, [{
color: jobLogs.every((step) => step.logs.every((log) => log.level === 'info')) ? 0x00ff00 : 0xff0000,
description: `Job ${job.name} finished with ${jobLogs.every((step) => step.logs.every((log) => log.level === 'info')) ? 'no errors' : 'errors'}`,
fields: [
{ name: 'Snippet', value: `[Here](https://bin.sern.dev/s/${createSnippet})`, inline: true },
],
}])
}
};

View File

@@ -9,6 +9,7 @@
"type": "module",
"dependencies": {
"body-parser": "^1.20.2",
"common-tags": "^1.8.2",
"cors": "^2.8.5",
"database": "1.0.0",
"dotenv": "^16.0.3",
@@ -16,10 +17,12 @@
"express": "^4.18.2",
"express-rate-limit": "^6.11.1",
"express-ws": "^5.0.2",
"redis": "^4.6.13",
"simple-discord-webhooks": "^2.1.0",
"zod": "^3.22.4"
},
"devDependencies": {
"@types/common-tags": "^1.8.4",
"@types/cors": "^2.8.14",
"@types/express": "^4.17.17",
"@types/express-ws": "^3.0.4",

View File

@@ -13,6 +13,19 @@ CREATE TABLE IF NOT EXISTS "account" (
CONSTRAINT "account_provider_providerAccountId_pk" PRIMARY KEY("provider","providerAccountId")
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "guideFeedback" (
"id" text PRIMARY KEY NOT NULL,
"feedback" text NOT NULL,
"route" text NOT NULL,
"inputText" text
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "jobsList" (
"id" bigserial PRIMARY KEY NOT NULL,
"name" text NOT NULL,
"steps" json NOT NULL
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "session" (
"sessionToken" text PRIMARY KEY NOT NULL,
"userId" text NOT NULL,

View File

@@ -0,0 +1 @@
ALTER TABLE "jobsList" ADD COLUMN "sernbinid" text NOT NULL;

View File

@@ -1,6 +0,0 @@
CREATE TABLE IF NOT EXISTS "guideFeedback" (
"id" text PRIMARY KEY NOT NULL,
"feedback" text NOT NULL,
"route" text NOT NULL,
"inputText" text
);

View File

@@ -1,11 +0,0 @@
CREATE TABLE IF NOT EXISTS "jobsList" (
"id" "smallserial" PRIMARY KEY NOT NULL,
"name" text NOT NULL,
"steps" json NOT NULL
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "jobsLogs" (
"id" text PRIMARY KEY NOT NULL,
"stepId" text NOT NULL,
"log" text NOT NULL
);

View File

@@ -1,2 +0,0 @@
ALTER TABLE "jobsLogs" RENAME COLUMN "log" TO "logs";--> statement-breakpoint
ALTER TABLE "jobsLogs" ALTER COLUMN "logs" SET DATA TYPE json;

View File

@@ -1,5 +1,5 @@
{
"id": "41dbaad4-435f-4581-bffc-c26780e20d73",
"id": "5175ce72-68af-4e42-a300-0557071eb623",
"prevId": "00000000-0000-0000-0000-000000000000",
"version": "5",
"dialect": "pg",
@@ -102,6 +102,68 @@
},
"uniqueConstraints": {}
},
"guideFeedback": {
"name": "guideFeedback",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"feedback": {
"name": "feedback",
"type": "text",
"primaryKey": false,
"notNull": true
},
"route": {
"name": "route",
"type": "text",
"primaryKey": false,
"notNull": true
},
"inputText": {
"name": "inputText",
"type": "text",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"jobsList": {
"name": "jobsList",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "bigserial",
"primaryKey": true,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"steps": {
"name": "steps",
"type": "json",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"session": {
"name": "session",
"schema": "",
@@ -224,8 +286,8 @@
"enums": {},
"schemas": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {},
"columns": {}
"tables": {}
}
}

View File

@@ -1,6 +1,6 @@
{
"id": "3848e593-34c7-49c2-8f2c-d9558a25f99e",
"prevId": "41dbaad4-435f-4581-bffc-c26780e20d73",
"id": "63e42e23-78aa-4b67-96a2-9b1bc6bf15c8",
"prevId": "5175ce72-68af-4e42-a300-0557071eb623",
"version": "5",
"dialect": "pg",
"tables": {
@@ -136,6 +136,40 @@
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"jobsList": {
"name": "jobsList",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "bigserial",
"primaryKey": true,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"steps": {
"name": "steps",
"type": "json",
"primaryKey": false,
"notNull": true
},
"sernbinid": {
"name": "sernbinid",
"type": "text",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"session": {
"name": "session",
"schema": "",
@@ -258,8 +292,8 @@
"enums": {},
"schemas": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {},
"columns": {}
"tables": {}
}
}

View File

@@ -1,321 +0,0 @@
{
"id": "34e7f7f7-fb3d-492d-8247-a1f6b5dcfbe7",
"prevId": "3848e593-34c7-49c2-8f2c-d9558a25f99e",
"version": "5",
"dialect": "pg",
"tables": {
"account": {
"name": "account",
"schema": "",
"columns": {
"userId": {
"name": "userId",
"type": "text",
"primaryKey": false,
"notNull": true
},
"type": {
"name": "type",
"type": "text",
"primaryKey": false,
"notNull": true
},
"provider": {
"name": "provider",
"type": "text",
"primaryKey": false,
"notNull": true
},
"providerAccountId": {
"name": "providerAccountId",
"type": "text",
"primaryKey": false,
"notNull": true
},
"refresh_token": {
"name": "refresh_token",
"type": "text",
"primaryKey": false,
"notNull": false
},
"access_token": {
"name": "access_token",
"type": "text",
"primaryKey": false,
"notNull": false
},
"expires_at": {
"name": "expires_at",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"token_type": {
"name": "token_type",
"type": "text",
"primaryKey": false,
"notNull": false
},
"scope": {
"name": "scope",
"type": "text",
"primaryKey": false,
"notNull": false
},
"id_token": {
"name": "id_token",
"type": "text",
"primaryKey": false,
"notNull": false
},
"session_state": {
"name": "session_state",
"type": "text",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {
"account_userId_user_id_fk": {
"name": "account_userId_user_id_fk",
"tableFrom": "account",
"tableTo": "user",
"columnsFrom": [
"userId"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {
"account_provider_providerAccountId_pk": {
"name": "account_provider_providerAccountId_pk",
"columns": [
"provider",
"providerAccountId"
]
}
},
"uniqueConstraints": {}
},
"guideFeedback": {
"name": "guideFeedback",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"feedback": {
"name": "feedback",
"type": "text",
"primaryKey": false,
"notNull": true
},
"route": {
"name": "route",
"type": "text",
"primaryKey": false,
"notNull": true
},
"inputText": {
"name": "inputText",
"type": "text",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"jobsList": {
"name": "jobsList",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "smallserial",
"primaryKey": true,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"steps": {
"name": "steps",
"type": "json",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"jobsLogs": {
"name": "jobsLogs",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"stepId": {
"name": "stepId",
"type": "text",
"primaryKey": false,
"notNull": true
},
"log": {
"name": "log",
"type": "text",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"session": {
"name": "session",
"schema": "",
"columns": {
"sessionToken": {
"name": "sessionToken",
"type": "text",
"primaryKey": true,
"notNull": true
},
"userId": {
"name": "userId",
"type": "text",
"primaryKey": false,
"notNull": true
},
"expires": {
"name": "expires",
"type": "timestamp",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {
"session_userId_user_id_fk": {
"name": "session_userId_user_id_fk",
"tableFrom": "session",
"tableTo": "user",
"columnsFrom": [
"userId"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"user": {
"name": "user",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": false
},
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": true
},
"emailVerified": {
"name": "emailVerified",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"image": {
"name": "image",
"type": "text",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"verificationToken": {
"name": "verificationToken",
"schema": "",
"columns": {
"identifier": {
"name": "identifier",
"type": "text",
"primaryKey": false,
"notNull": true
},
"token": {
"name": "token",
"type": "text",
"primaryKey": false,
"notNull": true
},
"expires": {
"name": "expires",
"type": "timestamp",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {
"verificationToken_identifier_token_pk": {
"name": "verificationToken_identifier_token_pk",
"columns": [
"identifier",
"token"
]
}
},
"uniqueConstraints": {}
}
},
"enums": {},
"schemas": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View File

@@ -1,321 +0,0 @@
{
"id": "0de7bdb6-1399-4a95-80fe-567c0a49bfd7",
"prevId": "34e7f7f7-fb3d-492d-8247-a1f6b5dcfbe7",
"version": "5",
"dialect": "pg",
"tables": {
"account": {
"name": "account",
"schema": "",
"columns": {
"userId": {
"name": "userId",
"type": "text",
"primaryKey": false,
"notNull": true
},
"type": {
"name": "type",
"type": "text",
"primaryKey": false,
"notNull": true
},
"provider": {
"name": "provider",
"type": "text",
"primaryKey": false,
"notNull": true
},
"providerAccountId": {
"name": "providerAccountId",
"type": "text",
"primaryKey": false,
"notNull": true
},
"refresh_token": {
"name": "refresh_token",
"type": "text",
"primaryKey": false,
"notNull": false
},
"access_token": {
"name": "access_token",
"type": "text",
"primaryKey": false,
"notNull": false
},
"expires_at": {
"name": "expires_at",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"token_type": {
"name": "token_type",
"type": "text",
"primaryKey": false,
"notNull": false
},
"scope": {
"name": "scope",
"type": "text",
"primaryKey": false,
"notNull": false
},
"id_token": {
"name": "id_token",
"type": "text",
"primaryKey": false,
"notNull": false
},
"session_state": {
"name": "session_state",
"type": "text",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {
"account_userId_user_id_fk": {
"name": "account_userId_user_id_fk",
"tableFrom": "account",
"tableTo": "user",
"columnsFrom": [
"userId"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {
"account_provider_providerAccountId_pk": {
"name": "account_provider_providerAccountId_pk",
"columns": [
"provider",
"providerAccountId"
]
}
},
"uniqueConstraints": {}
},
"guideFeedback": {
"name": "guideFeedback",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"feedback": {
"name": "feedback",
"type": "text",
"primaryKey": false,
"notNull": true
},
"route": {
"name": "route",
"type": "text",
"primaryKey": false,
"notNull": true
},
"inputText": {
"name": "inputText",
"type": "text",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"jobsList": {
"name": "jobsList",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "smallserial",
"primaryKey": true,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"steps": {
"name": "steps",
"type": "json",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"jobsLogs": {
"name": "jobsLogs",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"stepId": {
"name": "stepId",
"type": "text",
"primaryKey": false,
"notNull": true
},
"logs": {
"name": "logs",
"type": "json",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"session": {
"name": "session",
"schema": "",
"columns": {
"sessionToken": {
"name": "sessionToken",
"type": "text",
"primaryKey": true,
"notNull": true
},
"userId": {
"name": "userId",
"type": "text",
"primaryKey": false,
"notNull": true
},
"expires": {
"name": "expires",
"type": "timestamp",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {
"session_userId_user_id_fk": {
"name": "session_userId_user_id_fk",
"tableFrom": "session",
"tableTo": "user",
"columnsFrom": [
"userId"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"user": {
"name": "user",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": false
},
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": true
},
"emailVerified": {
"name": "emailVerified",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"image": {
"name": "image",
"type": "text",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"verificationToken": {
"name": "verificationToken",
"schema": "",
"columns": {
"identifier": {
"name": "identifier",
"type": "text",
"primaryKey": false,
"notNull": true
},
"token": {
"name": "token",
"type": "text",
"primaryKey": false,
"notNull": true
},
"expires": {
"name": "expires",
"type": "timestamp",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {
"verificationToken_identifier_token_pk": {
"name": "verificationToken_identifier_token_pk",
"columns": [
"identifier",
"token"
]
}
},
"uniqueConstraints": {}
}
},
"enums": {},
"schemas": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View File

@@ -5,29 +5,15 @@
{
"idx": 0,
"version": "5",
"when": 1703347129897,
"tag": "0000_tearful_earthquake",
"when": 1709242740332,
"tag": "0000_greedy_sandman",
"breakpoints": true
},
{
"idx": 1,
"version": "5",
"when": 1703508532165,
"tag": "0001_chief_ricochet",
"breakpoints": true
},
{
"idx": 2,
"version": "5",
"when": 1704641056957,
"tag": "0002_rainy_catseye",
"breakpoints": true
},
{
"idx": 3,
"version": "5",
"when": 1704642053129,
"tag": "0003_nervous_songbird",
"when": 1709334685582,
"tag": "0001_burly_carnage",
"breakpoints": true
}
]

View File

@@ -4,8 +4,8 @@ import {
text,
primaryKey,
integer,
smallserial,
json
json,
bigserial
} from "drizzle-orm/pg-core";
import type { AdapterAccount } from "@auth/core/adapters";
@@ -18,22 +18,10 @@ export const guideFeedback = pgTable("guideFeedback", {
})
export const jobsList = pgTable("jobsList", {
// note to reviewers:
// id is a smallserial and smallserials
// is an autoincrementing 2-byte integer
// so the max value is 32767
// is this fine? or should I use a bigserial (8-byte int)?
// https://orm.drizzle.team/docs/column-types/pg#smallserial
id: smallserial('id').primaryKey().notNull(),
id: bigserial('id', { mode: 'number' }).primaryKey(),
name: text("name").notNull(),
steps: json("steps").notNull(),
})
export const stepLogs = pgTable("jobsLogs", {
pkey: text("pkey").notNull().primaryKey(),
id: text("id").notNull(),
jobId: text("stepId").notNull(),
logs: json('logs').$type<JobLog[]>().notNull(),
sernbinid: text("sernbinid").notNull(),
})
// next-auth schema

View File

@@ -1,38 +0,0 @@
/** @type {import("eslint").Linter.Config} */
const config = {
parser: "@typescript-eslint/parser",
parserOptions: {
project: true,
},
plugins: ["@typescript-eslint"],
extends: [
"plugin:@next/next/recommended",
"plugin:@typescript-eslint/recommended-type-checked",
"plugin:@typescript-eslint/stylistic-type-checked",
],
rules: {
// These opinionated rules are enabled in stylistic-type-checked above.
// Feel free to reconfigure them to your own preference.
"@typescript-eslint/array-type": "off",
"@typescript-eslint/ban-ts-comment": "warn",
"@typescript-eslint/consistent-type-definitions": "off",
"@typescript-eslint/consistent-type-imports": [
"warn",
{
prefer: "type-imports",
fixStyle: "inline-type-imports",
},
],
"@typescript-eslint/no-unused-vars": ["warn", { argsIgnorePattern: "^_" }],
"@typescript-eslint/require-await": "off",
"@typescript-eslint/no-misused-promises": [
"error",
{
checksVoidReturn: { attributes: false },
},
],
},
};
module.exports = config;

View File

@@ -1,42 +0,0 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# database
/prisma/db.sqlite
/prisma/db.sqlite-journal
# next.js
/.next/
/out/
next-env.d.ts
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# local env files
# do not commit any .env files to git, except for the .env.example file. https://create.t3.gg/en/usage/env-variables#using-environment-variables
.env
.env*.local
# vercel
.vercel
# typescript
*.tsbuildinfo

View File

@@ -1,28 +0,0 @@
# Create T3 App
This is a [T3 Stack](https://create.t3.gg/) project bootstrapped with `create-t3-app`.
## What's next? How do I make an app with this?
We try to keep this project as simple as possible, so you can start with just the scaffolding we set up for you, and add additional things later when they become necessary.
If you are not familiar with the different technologies used in this project, please refer to the respective docs. If you still are in the wind, please join our [Discord](https://t3.gg/discord) and ask for help.
- [Next.js](https://nextjs.org)
- [NextAuth.js](https://next-auth.js.org)
- [Prisma](https://prisma.io)
- [Tailwind CSS](https://tailwindcss.com)
- [tRPC](https://trpc.io)
## Learn More
To learn more about the [T3 Stack](https://create.t3.gg/), take a look at the following resources:
- [Documentation](https://create.t3.gg/)
- [Learn the T3 Stack](https://create.t3.gg/en/faq#what-learning-resources-are-currently-available) — Check out these awesome tutorials
You can check out the [create-t3-app GitHub repository](https://github.com/t3-oss/create-t3-app) — your feedback and contributions are welcome!
## How do I deploy this?
Follow our deployment guides for [Vercel](https://create.t3.gg/en/deployment/vercel), [Netlify](https://create.t3.gg/en/deployment/netlify) and [Docker](https://create.t3.gg/en/deployment/docker) for more information.

View File

@@ -1,19 +0,0 @@
/**
* Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially useful
* for Docker builds.
*/
await import("./src/env.js");
/** @type {import("next").NextConfig} */
const config = {
async rewrites() {
return [
{
source: '/api/:path*',
destination: `${process.env.API_URL}/:path*`,
},
]
},
};
export default config;

View File

@@ -1,56 +0,0 @@
{
"name": "frontend",
"version": "0.1.0",
"private": true,
"type": "module",
"scripts": {
"build": "next build",
"dev": "next dev",
"lint": "next lint",
"start": "next start"
},
"dependencies": {
"@auth/drizzle-adapter": "^0.3.6",
"@babel/runtime": "^7.23.7",
"@emotion/cache": "^11.11.0",
"@emotion/react": "^11.11.3",
"@emotion/styled": "^11.11.0",
"@mui/material": "^5.15.2",
"@mui/material-nextjs": "^5.15.0",
"@planetscale/database": "^1.11.0",
"@t3-oss/env-nextjs": "^0.7.1",
"database": "1.0.0",
"drizzle-orm": "^0.29.1",
"next": "14.0.0",
"next-auth": "^4.24.5",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-icons": "^4.12.0",
"sharp": "^0.33.1",
"swr": "^2.2.4",
"zod": "^3.22.4"
},
"devDependencies": {
"@next/eslint-plugin-next": "^14.0.3",
"@types/eslint": "^8.44.7",
"@types/node": "^18.17.0",
"@types/react": "^18.2.37",
"@types/react-dom": "^18.2.15",
"@typescript-eslint/eslint-plugin": "^6.11.0",
"@typescript-eslint/parser": "^6.11.0",
"autoprefixer": "^10.4.14",
"dotenv-cli": "^7.3.0",
"drizzle-kit": "^0.20.7",
"eslint": "^8.54.0",
"mysql2": "^3.6.1",
"postcss": "^8.4.31",
"prettier": "^3.1.0",
"prettier-plugin-tailwindcss": "^0.5.7",
"tailwindcss": "^3.3.5",
"typescript": "^5.1.6"
},
"ct3aMetadata": {
"initVersion": "7.25.0"
},
"packageManager": "yarn@4.0.2"
}

View File

@@ -1,8 +0,0 @@
const config = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};
module.exports = config;

View File

@@ -1,8 +0,0 @@
/** @type {import('prettier').Config & import('prettier-plugin-tailwindcss').PluginOptions} */
const config = {
plugins: ["prettier-plugin-tailwindcss"],
useTabs: true,
tabWidth: 2
};
export default config;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -1,7 +0,0 @@
import NextAuth from "next-auth";
import { authOptions } from "~/server/auth";
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };

View File

@@ -1,35 +0,0 @@
import "~/styles/globals.css";
import { AppRouterCacheProvider } from '@mui/material-nextjs/v14-appRouter';
import { Roboto } from "next/font/google";
import { StyledEngineProvider } from '@mui/material/styles';
import NextAuthProvider from "../components/NextAuthProvider";
const roboto = Roboto({
weight: ['400'],
variable: '--font-sans',
subsets: ['latin']
});
export const metadata = {
title: "sern Automata GUI",
description: "Generated by create-t3-app",
icons: [{ rel: "icon", url: "/favicon.png" }],
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body className={`font-sans ${roboto.variable}`}>
<NextAuthProvider>
<AppRouterCacheProvider options={{ enableCssLayer: true }}>
{children}
</AppRouterCacheProvider>
</NextAuthProvider>
</body>
</html>
);
}

View File

@@ -1,19 +0,0 @@
import { Container } from "@mui/material";
import Authenticated from "~/components/HomePage/Authenticated";
import Unauthenticated from "~/components/HomePage/Unauthenticated";
import NavBar from "~/components/Layout/AppBar";
import { getServerAuthSession } from "~/server/auth";
export default async function HomePage() {
const session = await getServerAuthSession();
return session ?
<div>
<NavBar />
{/*
// @ts-expect-error https://github.com/mui/material-ui/issues/40370 */}
<Container maxWidth="s">
<Authenticated />
</Container>
</div>
: <Unauthenticated />;
}

View File

@@ -1,7 +0,0 @@
export default async function Authenticated() {
return (
<div>
<p>hi</p>
</div>
);
}

View File

@@ -1,11 +0,0 @@
"use client";
import { Button } from "@mui/material";
import { signIn } from "next-auth/react";
export default function LoginButton() {
return (
<Button variant="outlined" onClick={() => signIn()}>
Login
</Button>
);
}

View File

@@ -1,22 +0,0 @@
import LoginButton from "./LoginButton";
export default async function Unauthenticated() {
return (
<div className="relative">
<div className="absolute top-0 right-0 pt-4 pr-4">
<LoginButton />
</div>
<div className="flex h-screen flex-col items-center justify-center gap-3">
<h1 className="text-6xl">Hey!</h1>
<p className="text-2xl">
You just stomped on the frontend of sern Automata.
</p>
<p className="text-2xl">
It's unfortunately WIP and for security purposes it's behind a login
wall that only the devteam can access.
</p>
<p className="text-3xl">Sorry!</p>
</div>
</div>
);
}

View File

@@ -1,164 +0,0 @@
'use client';
import * as React from 'react';
import AppBar from '@mui/material/AppBar';
import Box from '@mui/material/Box';
import Toolbar from '@mui/material/Toolbar';
import IconButton from '@mui/material/IconButton';
import Typography from '@mui/material/Typography';
import Menu from '@mui/material/Menu';
import { MdMenu } from 'react-icons/md';
import Container from '@mui/material/Container';
import Avatar from '@mui/material/Avatar';
import Button from '@mui/material/Button';
import Tooltip from '@mui/material/Tooltip';
import MenuItem from '@mui/material/MenuItem';
import Logo from '../../../public/croppedlogo.png';
import Image from 'next/image';
import { signOut } from 'next-auth/react';
import { useRouter } from 'next/navigation';
import { useSession } from "next-auth/react"
const pages = ['Home'];
const settings = ['Account', 'Logout'];
export default function NavBar() {
const { data: session } = useSession()
const router = useRouter();
const [anchorElNav, setAnchorElNav] = React.useState<null | HTMLElement>(null);
const [anchorElUser, setAnchorElUser] = React.useState<null | HTMLElement>(null);
const settingLinkHandler = (setting: string) => {
switch (setting) {
case 'Logout':
return void signOut();
case 'Account':
return void router.push('/account');
}
}
const handleOpenNavMenu = (event: React.MouseEvent<HTMLElement>) => {
setAnchorElNav(event.currentTarget);
};
const handleOpenUserMenu = (event: React.MouseEvent<HTMLElement>) => {
setAnchorElUser(event.currentTarget);
};
const handleCloseNavMenu = () => {
setAnchorElNav(null);
};
const handleCloseUserMenu = () => {
setAnchorElUser(null);
};
return (
<div className='pb-2'>
<AppBar position="static">
<Container maxWidth="xl">
<Toolbar disableGutters>
<Image src={Logo} alt='logo' className='h-9 w-fit pr-5' />
<Box sx={{ flexGrow: 1, display: { xs: 'flex', md: 'none' } }}>
<IconButton
size="large"
aria-label="account of current user"
aria-controls="menu-appbar"
aria-haspopup="true"
onClick={handleOpenNavMenu}
color="inherit"
>
<MdMenu />
</IconButton>
<Menu
id="menu-appbar"
anchorEl={anchorElNav}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'left',
}}
keepMounted
transformOrigin={{
vertical: 'top',
horizontal: 'left',
}}
open={Boolean(anchorElNav)}
onClose={handleCloseNavMenu}
sx={{
display: { xs: 'block', md: 'none' },
}}
>
{pages.map((page) => (
<MenuItem key={page} onClick={handleCloseNavMenu}>
<Typography textAlign="center">{page}</Typography>
</MenuItem>
))}
</Menu>
</Box>
<Typography
variant="h5"
noWrap
component="a"
href="#app-bar-with-responsive-menu"
sx={{
mr: 2,
display: { xs: 'flex', md: 'none' },
flexGrow: 1,
fontFamily: 'monospace',
fontWeight: 700,
letterSpacing: '.3rem',
color: 'inherit',
textDecoration: 'none',
}}
>
AUTOMATA
</Typography>
<Box sx={{ flexGrow: 1, display: { xs: 'none', md: 'flex' } }}>
{pages.map((page) => (
<Button
key={page}
onClick={handleCloseNavMenu}
sx={{ my: 2, color: 'white', display: 'block' }}
>
{page}
</Button>
))}
</Box>
<Box sx={{ flexGrow: 0 }}>
<Tooltip title="Open settings">
<IconButton onClick={handleOpenUserMenu} sx={{ p: 0 }}>
<Avatar alt={'Account'} src={session!.user.image!} />
</IconButton>
</Tooltip>
<Menu
sx={{ mt: '45px' }}
id="menu-appbar"
anchorEl={anchorElUser}
anchorOrigin={{
vertical: 'top',
horizontal: 'right',
}}
keepMounted
transformOrigin={{
vertical: 'top',
horizontal: 'right',
}}
open={Boolean(anchorElUser)}
onClose={handleCloseUserMenu}
>
{settings.map((setting) => (
<MenuItem key={setting} onClick={() => {
handleCloseUserMenu();
settingLinkHandler(setting);
}}>
<Typography textAlign="center">{setting}</Typography>
</MenuItem>
))}
</Menu>
</Box>
</Toolbar>
</Container>
</AppBar>
</div>
);
}

View File

@@ -1,12 +0,0 @@
'use client';
import { SessionProvider } from 'next-auth/react';
import type { ReactNode } from 'react';
export default function NextAuthProvider({
children,
}: {
children: ReactNode;
}) {
return <SessionProvider basePath='/auth'>{children}</SessionProvider>;
}

View File

@@ -1,60 +0,0 @@
import { createEnv } from "@t3-oss/env-nextjs";
import { z } from "zod";
export const env = createEnv({
/**
* Specify your server-side environment variables schema here. This way you can ensure the app
* isn't built with invalid env vars.
*/
server: {
NODE_ENV: z
.enum(["development", "test", "production"])
.default("development"),
NEXTAUTH_SECRET:
process.env.NODE_ENV === "production"
? z.string()
: z.string().optional(),
NEXTAUTH_URL: z.preprocess(
// This makes Vercel deployments not fail if you don't set NEXTAUTH_URL
// Since NextAuth.js automatically uses the VERCEL_URL if present.
(str) => process.env.VERCEL_URL ?? str,
// VERCEL_URL doesn't include `https` so it cant be validated as a URL
process.env.VERCEL ? z.string() : z.string().url()
),
GITHUB_CLIENT_ID: z.string(),
GITHUB_CLIENT_SECRET: z.string(),
API_URL: z.string().url(),
},
/**
* Specify your client-side environment variables schema here. This way you can ensure the app
* isn't built with invalid env vars. To expose them to the client, prefix them with
* `NEXT_PUBLIC_`.
*/
client: {
// NEXT_PUBLIC_CLIENTVAR: z.string(),
},
/**
* You can't destruct `process.env` as a regular object in the Next.js edge runtimes (e.g.
* middlewares) or client-side so we need to destruct manually.
*/
runtimeEnv: {
NODE_ENV: process.env.NODE_ENV,
NEXTAUTH_SECRET: process.env.NEXTAUTH_SECRET,
NEXTAUTH_URL: process.env.NEXTAUTH_URL,
GITHUB_CLIENT_ID: process.env.GITHUB_CLIENT_ID,
GITHUB_CLIENT_SECRET: process.env.GITHUB_CLIENT_SECRET,
API_URL: process.env.API_URL,
},
/**
* Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially
* useful for Docker builds.
*/
skipValidation: !!process.env.SKIP_ENV_VALIDATION,
/**
* Makes it so that empty strings are treated as undefined. `SOME_VAR: z.string()` and
* `SOME_VAR=''` will throw an error.
*/
emptyStringAsUndefined: true,
});

View File

@@ -1,78 +0,0 @@
import {
getServerSession,
type DefaultSession,
type NextAuthOptions,
} from "next-auth";
import GithubProvider from "next-auth/providers/github";
import { env } from "~/env";
import db from "database/dist/index";
import { PostgresJsDrizzleAdapter } from "./db/authAdapter";
/**
* Module augmentation for `next-auth` types. Allows us to add custom properties to the `session`
* object and keep type safety.
*
* @see https://next-auth.js.org/getting-started/typescript#module-augmentation
*/
declare module "next-auth" {
interface Session extends DefaultSession {
user: {
id: string;
// ...other properties
// role: UserRole;
} & DefaultSession["user"];
}
// interface User {
// // ...other properties
// // role: UserRole;
// }
}
/**
* Options for NextAuth.js used to configure adapters, providers, callbacks, etc.
*
* @see https://next-auth.js.org/configuration/options
*/
export const authOptions: NextAuthOptions = {
callbacks: {
session: ({ session, user }) => ({
...session,
user: {
...session.user,
id: user.id,
},
}),
signIn(params) {
const allowedUsers = ['SrIzan10', 'jacoobes', 'Murtarxx', 'EvolutionX-10']
if (!allowedUsers.includes(params.user.name!)) {
return false
} else {
return true
}
},
},
adapter: PostgresJsDrizzleAdapter(db),
providers: [
GithubProvider({
clientId: env.GITHUB_CLIENT_ID,
clientSecret: env.GITHUB_CLIENT_SECRET,
}),
/**
* ...add more providers here.
*
* Most other providers require a bit more work than the Discord provider. For example, the
* GitHub provider requires you to add the `refresh_token_expires_in` field to the Account
* model. Refer to the NextAuth.js docs for the provider you want to use. Example:
*
* @see https://next-auth.js.org/providers/github
*/
],
};
/**
* Wrapper for `getServerSession` so that you don't need to import the `authOptions` in every file.
*
* @see https://next-auth.js.org/configuration/nextjs
*/
export const getServerAuthSession = () => getServerSession(authOptions);

View File

@@ -1,143 +0,0 @@
// @ts-nocheck shush lets pretend this is fine
import { accounts, sessions, users, verificationTokens } from "database/src/schema";
import type { PostgresJsDatabase } from "drizzle-orm/postgres-js";
import { and, eq } from "drizzle-orm";
import type { Adapter } from "next-auth/adapters";
import database from 'database/src/index'
export function PostgresJsDrizzleAdapter(db: typeof database): Adapter {
return {
createUser: async (data) => {
return db
.insert(users)
.values({ ...data, id: crypto.randomUUID() })
.returning()
.then((res) => res[0]);
},
getUser: async (data) => {
return (
db
.select()
.from(users)
.where(eq(users.id, data))
.then((res) => res[0]) ?? null
);
},
getUserByEmail: async (data) => {
return (
db
.select()
.from(users)
.where(eq(users.email, data))
.then((res) => res[0]) ?? null
);
},
createSession: async (data) => {
return db
.insert(sessions)
.values(data)
.returning()
.then((res) => res[0]);
},
getSessionAndUser: async (data) => {
return (
db
.select({
session: sessions,
user: users,
})
.from(sessions)
.where(eq(sessions.sessionToken, data))
.innerJoin(users, eq(users.id, sessions.userId))
.then((res) => res[0]) ?? null
);
},
updateUser: async (data) => {
if (!data.id) {
throw new Error("No user id.");
}
return db
.update(users)
.set(data)
.where(eq(users.id, data.id))
.returning()
.then((res) => res[0]);
},
updateSession: async (data) => {
return db
.update(sessions)
.set(data)
.where(eq(sessions.sessionToken, data.sessionToken))
.returning()
.then((res) => res[0]);
},
linkAccount: async (account) => {
await db
.insert(accounts)
.values(account)
.returning()
.then((res) => res[0]);
},
getUserByAccount: async (account) => {
const dbAccount = await db
.select()
.from(accounts)
.where(
and(
eq(accounts.providerAccountId, account.providerAccountId),
eq(accounts.provider, account.provider)
)
)
.leftJoin(users, eq(accounts.userId, users.id))
.then((res) => res[0]);
if (!dbAccount) return null;
return dbAccount.user;
},
deleteSession: async (sessionToken) => {
await db.delete(sessions).where(eq(sessions.sessionToken, sessionToken));
},
createVerificationToken: async (token) => {
return db
.insert(verificationTokens)
.values(token)
.returning()
.then((res) => res[0]);
},
useVerificationToken: async (token) => {
try {
return (
db
.delete(verificationTokens)
.where(
and(
eq(verificationTokens.identifier, token.identifier),
eq(verificationTokens.token, token.token)
)
)
.returning()
.then((res) => res[0]) ?? null
);
} catch (err) {
throw new Error("No verification token found.");
}
},
deleteUser: async (id) => {
await db
.delete(users)
.where(eq(users.id, id))
.returning()
.then((res) => res[0]);
},
unlinkAccount: async (account) => {
await db
.delete(accounts)
.where(
and(
eq(accounts.providerAccountId, account.providerAccountId),
eq(accounts.provider, account.provider)
)
);
},
};
}

View File

@@ -1,3 +0,0 @@
import database from 'database/src/index'
export const db = database

View File

@@ -1,29 +0,0 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
/*
deactivating the preflight plugin creates an ugly border
on all sides of the screen, so these css values removes them
*/
body,
h1,
h2,
h3,
h4,
h5,
h6,
p,
ul,
ol,
dl,
figure,
blockquote,
fieldset,
legend {
margin: 0;
padding: 0;
}
* {
box-sizing: border-box;
}

View File

@@ -1,17 +0,0 @@
import { type Config } from "tailwindcss";
import { fontFamily } from "tailwindcss/defaultTheme";
export default {
content: ["./src/**/*.tsx"],
theme: {
extend: {
fontFamily: {
sans: ["var(--font-sans)", ...fontFamily.sans],
},
},
},
plugins: [],
corePlugins: {
preflight: false
}
} satisfies Config;

View File

@@ -1,42 +0,0 @@
{
"compilerOptions": {
/* Base Options: */
"esModuleInterop": true,
"skipLibCheck": true,
"target": "es2022",
"allowJs": true,
"resolveJsonModule": true,
"moduleDetection": "force",
"isolatedModules": true,
/* Strictness */
"strict": true,
"noUncheckedIndexedAccess": true,
"checkJs": true,
/* Bundled projects */
"lib": ["dom", "dom.iterable", "ES2022"],
"noEmit": true,
"module": "ESNext",
"moduleResolution": "Bundler",
"jsx": "preserve",
"plugins": [{ "name": "next" }],
"incremental": true,
/* Path Aliases */
"baseUrl": ".",
"paths": {
"~/*": ["./src/*"]
}
},
"include": [
".eslintrc.cjs",
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
"**/*.cjs",
"**/*.js",
".next/types/**/*.ts"
, "prettier.config.mjs" ],
"exclude": ["node_modules"]
}

View File

@@ -16,7 +16,7 @@
"start:api": "yarn workspace api start",
"start:frontend": "yarn workspace frontend start",
"db:deploy": "yarn workspace database deploy",
"dev": "concurrently \"yarn workspace frontend dev\" \"yarn workspace api dev\" \"yarn workspace database dev\""
"dev": "concurrently \"yarn workspace api dev\" \"yarn workspace database dev\""
},
"//": [
"postgres: \"cd dev/postgresql && docker compose up\""

3921
yarn.lock

File diff suppressed because it is too large Load Diff