mirror of
https://github.com/sern-handler/automata
synced 2026-06-06 01:16:51 +00:00
feat: upload logs to sern bin, bye bye frontend, basically done
This commit is contained in:
@@ -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
|
||||
@@ -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 },
|
||||
],
|
||||
}])
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
1
apps/database/drizzle/0001_burly_carnage.sql
Normal file
1
apps/database/drizzle/0001_burly_carnage.sql
Normal file
@@ -0,0 +1 @@
|
||||
ALTER TABLE "jobsList" ADD COLUMN "sernbinid" text NOT NULL;
|
||||
@@ -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
|
||||
);
|
||||
@@ -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
|
||||
);
|
||||
@@ -1,2 +0,0 @@
|
||||
ALTER TABLE "jobsLogs" RENAME COLUMN "log" TO "logs";--> statement-breakpoint
|
||||
ALTER TABLE "jobsLogs" ALTER COLUMN "logs" SET DATA TYPE json;
|
||||
@@ -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": {}
|
||||
}
|
||||
}
|
||||
@@ -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": {}
|
||||
}
|
||||
}
|
||||
@@ -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": {}
|
||||
}
|
||||
}
|
||||
@@ -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": {}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
42
apps/frontend/.gitignore
vendored
42
apps/frontend/.gitignore
vendored
@@ -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
|
||||
@@ -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.
|
||||
@@ -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;
|
||||
@@ -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"
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
const config = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
@@ -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 |
@@ -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 };
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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 />;
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
export default async function Authenticated() {
|
||||
return (
|
||||
<div>
|
||||
<p>hi</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>;
|
||||
}
|
||||
@@ -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,
|
||||
});
|
||||
@@ -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);
|
||||
@@ -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)
|
||||
)
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
import database from 'database/src/index'
|
||||
|
||||
export const db = database
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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"]
|
||||
}
|
||||
@@ -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\""
|
||||
|
||||
Reference in New Issue
Block a user