diff --git a/.gitignore b/.gitignore index d02fe76..4d84429 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ node_modules -.env +*.env /apps/api/repos dist diff --git a/apps/api/.prettierrc b/apps/api/.prettierrc new file mode 100644 index 0000000..5e695cb --- /dev/null +++ b/apps/api/.prettierrc @@ -0,0 +1,4 @@ +{ + "tabWidth": 4, + "useTabs": true +} \ No newline at end of file diff --git a/apps/api/index.ts b/apps/api/index.ts index dcc9233..ef2b262 100644 --- a/apps/api/index.ts +++ b/apps/api/index.ts @@ -1,19 +1,23 @@ import express from 'express'; import 'dotenv/config'; import { execa } from 'execa'; -import { validateJsonWebhook } from './util/validateJsonWebhook.js'; -import babashkaScripts from './babashka/scripts.json' assert { type: 'json' }; -import { FeedbackRequestBody, FeedbackRequestBodySchema } from './util/types.js'; +import validateJsonWebhook from './plugins/validateJsonWebhook.js'; +import { FeedbackRequestBody, FeedbackRequestBodySchema, Logs } from './util/types.js'; import cors from 'cors' 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 expressWs from 'express-ws'; +import resolvePlugins from './util/resolvePlugins.js'; +import { PassThrough } from 'node:stream'; const devMode = process.argv[2] === '--dev'; if (devMode) console.log('You\'re a developer 😎 (sorry for that emoji jumpscare)') +const cwd = process.cwd() -const app = express() +const { app } = expressWs(express()) app.use(express.json()) app.use(cors()) @@ -28,6 +32,100 @@ app.get('/', (req, res) => { res.send('hi this is the api what did you even expect') }) +for (const job of jobs) { + const jobLogs: Logs[] = [] + switch (job.method) { + case "POST": + app.post(job.route, async (req, res) => { + await expressCode(req, res); + }); + break; + case "GET": + app.get(job.route, async (req, res) => { + await expressCode(req, res); + }); + break; + } + const expressCode = async (req: express.Request, res: express.Response) => { + if (resolvePlugins(job.plugins, req, res).includes(false)) + // Believe it or not, the code 418 I'm a teapot is the most appropiate one IMO. + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/418 + 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)) + }) + + for (const steps of job.steps) { + console.log(`Running step ${steps.name}`); + const cmd = execa( + "bash", + [`${cwd}/scripts/${job.stepsMainDir}/${steps.script}`], + { + cwd: steps.cwd, + shell: true, + 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 { + console.log(`Step ${steps.name} finished successfully`); + } + 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 + }) + } + + } + + // const cmd = execa( + // "bash", + // [`${cwd}/scripts/${job.stepsMainDir}/${steps.script}`], + // { + // cwd: steps.cwd, + // shell: true, + // env: { NT_ARGS: JSON.stringify(job.cmdArgs) }, + // }, + // ); + } +}; + +app.ws('/ws/jobs/logs/:id', (ws, req) => { + const id = req.params.id + if (!id) { + ws.send(JSON.stringify({ success: false, error: 'No id provided' })) + return ws.close() + } +}) + app.post('/wh/updateDocsJson', async (req, res) => { const validate = validateJsonWebhook(req) if (!validate) { @@ -35,7 +133,7 @@ app.post('/wh/updateDocsJson', async (req, res) => { success: false, error: 'Invalid token' }) - return + return } if (req.body.action !== 'released') { res.send({ @@ -143,32 +241,7 @@ app.get('/ping', (req, res) => { res.send('Pong') }) -for (const script of babashkaScripts) { - switch (script.method) { - case 'GET': - app.get(script.route, async (req, res) => { - const command = await execa('bb', [`babashka/${script.file}`]) - res.send({ - success: command.exitCode === 0 ? true : false, - cmdoutput: command.stdout - }) - }) - break; - case 'POST': - app.post(script.route, async (req, res) => { - const command = await execa('bb', [`babashka/${script.file}`]) - res.send({ - success: command.exitCode === 0 ? true : false, - cmdoutput: command.stdout - }) - }) - break; - } - console.log(`Babashka script ${script.file} was registered successfully in ${script.method} ${script.route}`) -} - const port = 4000 - app.listen(port, '::', () => { console.log(`Server listening on [::]${port}`) }) diff --git a/apps/api/jobs.ts b/apps/api/jobs.ts new file mode 100644 index 0000000..41c8d59 --- /dev/null +++ b/apps/api/jobs.ts @@ -0,0 +1,103 @@ +export default [ + { + name: 'Update docs', + method: 'POST', + route: '/wh/updateDocs', + plugins: ['validateJsonWebhook'], + cmdArgs: { + githubToken: process.env.GHTOKEN!, + email: process.env.EMAIL! + }, + stepsMainDir: 'updateDocs', + steps: [ + { + id: 1, + name: 'Move docusaurus config files', + cwd: 'repos/website', + script: 'moveFiles.sh' + }, + { + id: 2, + name: 'Build docs', + cwd: 'repos/website', + script: 'buildWebsite.sh' + }, + { + id: 3, + name: 'Revert moved config files', + cwd: 'repos/website', + script: 'revertMovedFiles.sh' + }, + { + id: 4, + name: 'Generate Typedoc JSON', + cwd: 'repos/website', + script: 'typedocJson.sh' + }, + { + id: 5, + name: 'Push website', + cwd: 'repos/website', + script: 'pushWebsite.sh' + }, + { + id: 6, + name: 'Push community bot', + cwd: 'repos/sern-community', + script: 'pushCommunityBot.sh' + } + ] + }, + { + name: 'Test', + method: 'GET', + route: '/test', + plugins: [], + cmdArgs: { + randomVariable: 'hey this is a variable' + }, + stepsMainDir: 'test', + steps: [ + { + id: 1, + name: 'Hello world', + cwd: 'scripts/test', + script: 'test.sh' + }, + { + id: 2, + name: 'Hello world from variable', + cwd: 'scripts/test', + script: 'variable.sh' + } + ] + } +] satisfies Jobs[] + +export interface Jobs { + name: string; + method: 'GET' | 'POST'; + route: string; + plugins: string[]; + cmdArgs: Record; + stepsMainDir: string; + steps: Step[]; +} + +export interface Step { + id: number; + name: string; + cwd: string; + script: string +} + +export interface Logs { + timestamp: Date; + message: string; + level: 'info' | 'error'; +} + +export interface LogGroup { + stepId: string; + logs: Logs[]; +} \ No newline at end of file diff --git a/apps/api/package.json b/apps/api/package.json index 8d53b77..46e2764 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -15,12 +15,14 @@ "execa": "^7.1.1", "express": "^4.18.2", "express-rate-limit": "^6.11.1", + "express-ws": "^5.0.2", "simple-discord-webhooks": "^2.1.0", "zod": "^3.22.4" }, "devDependencies": { "@types/cors": "^2.8.14", "@types/express": "^4.17.17", + "@types/express-ws": "^3.0.4", "@types/node": "^18.15.11", "tsc-watch": "^6.0.0", "typescript": "^5.0.4" diff --git a/apps/api/util/validateJsonWebhook.ts b/apps/api/plugins/validateJsonWebhook.ts similarity index 75% rename from apps/api/util/validateJsonWebhook.ts rename to apps/api/plugins/validateJsonWebhook.ts index cefba46..bb6d6f6 100644 --- a/apps/api/util/validateJsonWebhook.ts +++ b/apps/api/plugins/validateJsonWebhook.ts @@ -1,7 +1,7 @@ -import type { Request } from "express"; +import type { Request, Response } from "express"; import * as crypto from 'crypto'; -export function validateJsonWebhook(request: Request) { +export default function validateJsonWebhook(request: Request, _response?: Response) { // calculate the signature const expectedSignature = "sha256=" + diff --git a/apps/api/scripts/test/test.sh b/apps/api/scripts/test/test.sh new file mode 100644 index 0000000..05b2be2 --- /dev/null +++ b/apps/api/scripts/test/test.sh @@ -0,0 +1 @@ +echo "hi" \ No newline at end of file diff --git a/apps/api/scripts/test/variable.sh b/apps/api/scripts/test/variable.sh new file mode 100644 index 0000000..4b6d98a --- /dev/null +++ b/apps/api/scripts/test/variable.sh @@ -0,0 +1,3 @@ +RANDOMVARIABLE=$(echo $NT_ARGS | jq -r '.randomVariable') + +echo $RANDOMVARIABLE \ No newline at end of file diff --git a/apps/api/scripts/updateDocs/buildWebsite.sh b/apps/api/scripts/updateDocs/buildWebsite.sh new file mode 100644 index 0000000..15ec4f6 --- /dev/null +++ b/apps/api/scripts/updateDocs/buildWebsite.sh @@ -0,0 +1 @@ +yarn build \ No newline at end of file diff --git a/apps/api/scripts/updateDocs/moveFiles.sh b/apps/api/scripts/updateDocs/moveFiles.sh new file mode 100644 index 0000000..949e08d --- /dev/null +++ b/apps/api/scripts/updateDocs/moveFiles.sh @@ -0,0 +1,2 @@ +mv ./docusaurus.config.js ./original.docusaurus.config.js +mv ./docgen.docusaurus.config.js ./docusaurus.config.js \ No newline at end of file diff --git a/apps/api/scripts/updateDocs/pushCommunityBot.sh b/apps/api/scripts/updateDocs/pushCommunityBot.sh new file mode 100644 index 0000000..9faa6a3 --- /dev/null +++ b/apps/api/scripts/updateDocs/pushCommunityBot.sh @@ -0,0 +1,6 @@ +GITHUBTOKEN=$(echo $@ | jq ".githubToken") +EMAIL=$(echo $@ | jq ".email") + +git add . +git -c user.name="sern bot" -c user.email="$EMAIL" commit -m "chore: update typedoc" +git push https://sernbot:$GITHUBTOKEN@github.com/sern-handler/sern-community.git \ No newline at end of file diff --git a/apps/api/scripts/updateDocs/pushWebsite.sh b/apps/api/scripts/updateDocs/pushWebsite.sh new file mode 100644 index 0000000..1dc5e90 --- /dev/null +++ b/apps/api/scripts/updateDocs/pushWebsite.sh @@ -0,0 +1,6 @@ +GITHUBTOKEN=$(echo $@ | jq ".githubToken") +EMAIL=$(echo $@ | jq ".email") + +git add . +git -c user.name="sern bot" -c user.email="$EMAIL" commit -m "chore: update api documentation" +git push --force https://sernbot:$GITHUBTOKEN@github.com/sern-handler/website.git \ No newline at end of file diff --git a/apps/api/scripts/updateDocs/revertMovedFiles.sh b/apps/api/scripts/updateDocs/revertMovedFiles.sh new file mode 100644 index 0000000..4b17efc --- /dev/null +++ b/apps/api/scripts/updateDocs/revertMovedFiles.sh @@ -0,0 +1,2 @@ +mv docusaurus.config.js docgen.docusaurus.config.js +mv original.docusaurus.config.js docusaurus.config.js \ No newline at end of file diff --git a/apps/api/scripts/updateDocs/typedocJson.sh b/apps/api/scripts/updateDocs/typedocJson.sh new file mode 100644 index 0000000..155cd3f --- /dev/null +++ b/apps/api/scripts/updateDocs/typedocJson.sh @@ -0,0 +1 @@ +yarn typedoc-json \ No newline at end of file diff --git a/apps/api/scripts/updateDocsJson.sh b/apps/api/scripts/updateDocsJson.sh deleted file mode 100644 index b016999..0000000 --- a/apps/api/scripts/updateDocsJson.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/bash - -cd repos/sern-community -git checkout main -git pull - -cd .. -cd website -mv ./docusaurus.config.js ./original.docusaurus.config.js -mv ./docgen.docusaurus.config.js ./docusaurus.config.js -npm run build -mv docusaurus.config.js docgen.docusaurus.config.js -mv original.docusaurus.config.js docusaurus.config.js -npm run typedoc-json -git add . -git -c user.name="sern bot" -c user.email="$2" commit -m "chore: update api documentation" -git push --force https://sernbot:$1@github.com/sern-handler/website.git -cd .. -cd sern-community -git add . -git -c user.name="sern bot" -c user.email="$2" commit -m "chore: update typedoc" -git push https://sernbot:$1@github.com/sern-handler/sern-community.git diff --git a/apps/api/util/resolvePlugins.ts b/apps/api/util/resolvePlugins.ts new file mode 100644 index 0000000..66b1a85 --- /dev/null +++ b/apps/api/util/resolvePlugins.ts @@ -0,0 +1,14 @@ +import type { Request, Response } from "express" + +export default function resolvePlugins(plugins: string[], req: Request, res: Response) { + if (plugins.length === 0) + // not doing any crazy types today sorry + return [true] + const resolvedPlugins: boolean[] = [] + plugins.forEach(async (plugin) => { + const resolvedPlugin = await import(`../plugins/${plugin}.js`) + .then((plugin) => plugin.default(req, res)) as boolean + resolvedPlugins.push(resolvedPlugin) + }) + return resolvedPlugins +} \ No newline at end of file diff --git a/apps/api/util/types.ts b/apps/api/util/types.ts index 3f17417..574ae6b 100644 --- a/apps/api/util/types.ts +++ b/apps/api/util/types.ts @@ -1,11 +1,5 @@ import { z } from "zod"; -/* export interface FeedbackRequestBody { - turnstileToken?: string; - feedback: 'up' | 'down'; - inputText?: string; - route: string; -} */ export const FeedbackRequestBodySchema = z.object({ turnstileToken: z.string().min(1), feedback: z.enum(['up', 'down']), @@ -13,3 +7,9 @@ export const FeedbackRequestBodySchema = z.object({ route: z.string(), }) export type FeedbackRequestBody = z.infer + +export interface Logs { + timestamp: Date; + message: string; + level: 'info' | 'error'; +} \ No newline at end of file diff --git a/apps/database/drizzle/0002_rainy_catseye.sql b/apps/database/drizzle/0002_rainy_catseye.sql new file mode 100644 index 0000000..9b41c9e --- /dev/null +++ b/apps/database/drizzle/0002_rainy_catseye.sql @@ -0,0 +1,11 @@ +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 +); diff --git a/apps/database/drizzle/0003_nervous_songbird.sql b/apps/database/drizzle/0003_nervous_songbird.sql new file mode 100644 index 0000000..81fdd48 --- /dev/null +++ b/apps/database/drizzle/0003_nervous_songbird.sql @@ -0,0 +1,2 @@ +ALTER TABLE "jobsLogs" RENAME COLUMN "log" TO "logs";--> statement-breakpoint +ALTER TABLE "jobsLogs" ALTER COLUMN "logs" SET DATA TYPE json; \ No newline at end of file diff --git a/apps/database/drizzle/meta/0002_snapshot.json b/apps/database/drizzle/meta/0002_snapshot.json new file mode 100644 index 0000000..9b61319 --- /dev/null +++ b/apps/database/drizzle/meta/0002_snapshot.json @@ -0,0 +1,321 @@ +{ + "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": {} + } +} \ No newline at end of file diff --git a/apps/database/drizzle/meta/0003_snapshot.json b/apps/database/drizzle/meta/0003_snapshot.json new file mode 100644 index 0000000..94a7129 --- /dev/null +++ b/apps/database/drizzle/meta/0003_snapshot.json @@ -0,0 +1,321 @@ +{ + "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": {} + } +} \ No newline at end of file diff --git a/apps/database/drizzle/meta/_journal.json b/apps/database/drizzle/meta/_journal.json index 6388779..46ffabf 100644 --- a/apps/database/drizzle/meta/_journal.json +++ b/apps/database/drizzle/meta/_journal.json @@ -15,6 +15,20 @@ "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", + "breakpoints": true } ] } \ No newline at end of file diff --git a/apps/database/package.json b/apps/database/package.json index 22f7ff9..935b3fc 100644 --- a/apps/database/package.json +++ b/apps/database/package.json @@ -7,6 +7,7 @@ "dev": "tsc-watch --preserveWatchOutput", "migrate": "node dist/migrations.js", "generateMigrations": "drizzle-kit generate:pg --schema ./src/schema.ts", + "deploy": "yarn generateMigrations && yarn migrate", "build": "tsc" }, "dependencies": { diff --git a/apps/database/src/schema.ts b/apps/database/src/schema.ts index 9b3459c..9b138d1 100644 --- a/apps/database/src/schema.ts +++ b/apps/database/src/schema.ts @@ -4,6 +4,8 @@ import { text, primaryKey, integer, + smallserial, + json } from "drizzle-orm/pg-core"; import type { AdapterAccount } from "@auth/core/adapters"; @@ -15,6 +17,25 @@ export const guideFeedback = pgTable("guideFeedback", { inputText: text("inputText"), }) +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(), + 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().notNull(), +}) + // next-auth schema export const users = pgTable("user", { id: text("id").notNull().primaryKey(), @@ -65,3 +86,10 @@ export const verificationTokens = pgTable( compoundKey: primaryKey(vt.identifier, vt.token), }) ); + +// types +interface JobLog { + timestamp: Date; + message: string; + level: 'info' | 'error'; +} \ No newline at end of file diff --git a/apps/frontend/package.json b/apps/frontend/package.json index 3c164aa..b1525bc 100644 --- a/apps/frontend/package.json +++ b/apps/frontend/package.json @@ -21,7 +21,7 @@ "@t3-oss/env-nextjs": "^0.7.1", "database": "1.0.0", "drizzle-orm": "^0.29.1", - "next": "^14.0.4", + "next": "14.0.0", "next-auth": "^4.24.5", "react": "18.2.0", "react-dom": "18.2.0", diff --git a/apps/frontend/src/app/layout.tsx b/apps/frontend/src/app/layout.tsx index 126aa56..c925ab8 100644 --- a/apps/frontend/src/app/layout.tsx +++ b/apps/frontend/src/app/layout.tsx @@ -26,9 +26,7 @@ export default function RootLayout({ - - {children} - + {children} diff --git a/apps/frontend/src/components/Layout/AppBar.tsx b/apps/frontend/src/components/Layout/AppBar.tsx index 62e7b1c..8850856 100644 --- a/apps/frontend/src/components/Layout/AppBar.tsx +++ b/apps/frontend/src/components/Layout/AppBar.tsx @@ -127,7 +127,7 @@ export default function NavBar() { - +