diff --git a/.gitignore b/.gitignore index 9b1ee42..d6da680 100644 --- a/.gitignore +++ b/.gitignore @@ -1,175 +1,2 @@ -# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore - -# Logs - -logs -_.log -npm-debug.log_ -yarn-debug.log* -yarn-error.log* -lerna-debug.log* -.pnpm-debug.log* - -# Caches - -.cache - -# Diagnostic reports (https://nodejs.org/api/report.html) - -report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json - -# Runtime data - -pids -_.pid -_.seed -*.pid.lock - -# Directory for instrumented libs generated by jscoverage/JSCover - -lib-cov - -# Coverage directory used by tools like istanbul - -coverage -*.lcov - -# nyc test coverage - -.nyc_output - -# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) - -.grunt - -# Bower dependency directory (https://bower.io/) - -bower_components - -# node-waf configuration - -.lock-wscript - -# Compiled binary addons (https://nodejs.org/api/addons.html) - -build/Release - -# Dependency directories - -node_modules/ -jspm_packages/ - -# Snowpack dependency directory (https://snowpack.dev/) - -web_modules/ - -# TypeScript cache - -*.tsbuildinfo - -# Optional npm cache directory - -.npm - -# Optional eslint cache - -.eslintcache - -# Optional stylelint cache - -.stylelintcache - -# Microbundle cache - -.rpt2_cache/ -.rts2_cache_cjs/ -.rts2_cache_es/ -.rts2_cache_umd/ - -# Optional REPL history - -.node_repl_history - -# Output of 'npm pack' - -*.tgz - -# Yarn Integrity file - -.yarn-integrity - -# dotenv environment variable files - -.env -.env.development.local -.env.test.local -.env.production.local -.env.local - -# parcel-bundler cache (https://parceljs.org/) - -.parcel-cache - -# Next.js build output - -.next -out - -# Nuxt.js build / generate output - -.nuxt -dist - -# Gatsby files - -# Comment in the public line in if your project uses Gatsby and not Next.js - -# https://nextjs.org/blog/next-9-1#public-directory-support - -# public - -# vuepress build output - -.vuepress/dist - -# vuepress v2.x temp and cache directory - -.temp - -# Docusaurus cache and generated files - -.docusaurus - -# Serverless directories - -.serverless/ - -# FuseBox cache - -.fusebox/ - -# DynamoDB Local files - -.dynamodb/ - -# TernJS port file - -.tern-port - -# Stores VSCode versions used for testing VSCode extensions - -.vscode-test - -# yarn v2 - -.yarn/cache -.yarn/unplugged -.yarn/build-state.yml -.yarn/install-state.gz -.pnp.* - -# IntelliJ based IDEs -.idea - -# Finder (MacOS) folder config -.DS_Store +node_modules +.mysql diff --git a/bun.lockb b/bun.lockb index c3c9d12..84c9b1c 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..16c3776 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,22 @@ +services: + adminer: + image: adminer:latest + restart: always + environment: + ADMINER_DESIGN: 'dracula' + ports: + - 8082:8080 + db: + image: mysql:lts + restart: always + environment: + MYSQL_ROOT_PASSWORD: password + ports: + - 8081:3306 + volumes: + - ./.mysql:/var/lib/mysql + healthcheck: + test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost"] + timeout: 20s + interval: 2s + retries: 10 diff --git a/drizzle.config.ts b/drizzle.config.ts new file mode 100644 index 0000000..2c477f4 --- /dev/null +++ b/drizzle.config.ts @@ -0,0 +1,18 @@ +import { defineConfig } from "drizzle-kit"; + +export const dbCredentials = { + host: "127.0.0.1", + //host: "db", + port: 8081, + //port: 3306, + user: "root", + password: "password", + database: "sous-podzim2024-app" +}; + +export default defineConfig({ + dialect: "mysql", + schema: "./src/database/schema.ts", + out: "./drizzle", + dbCredentials: dbCredentials +}); diff --git a/package.json b/package.json index 255020e..801a516 100644 --- a/package.json +++ b/package.json @@ -12,11 +12,15 @@ "@types/ejs": "^3.1.5", "@types/express": "^4.17.21", "@types/multer": "^1.4.12", + "drizzle-kit": "^0.24.2", + "drizzle-orm": "^0.33.0", "ejs": "^3.1.10", "express": "^4.19.2", - "multer": "^1.4.5-lts.1" + "multer": "^1.4.5-lts.1", + "mysql2": "^3.11.0" }, "scripts": { - "dev": "bun --watch src/index.ts" + "dev": "bun --watch src/index.ts", + "push": "bun drizzle-kit push" } } diff --git a/src/database/db.ts b/src/database/db.ts new file mode 100644 index 0000000..8b95239 --- /dev/null +++ b/src/database/db.ts @@ -0,0 +1,7 @@ +import { dbCredentials } from "../../drizzle.config.ts"; +import { drizzle } from "drizzle-orm/mysql2"; +import mysql from "mysql2/promise"; +import * as schema from "./schema"; + +const connection = await mysql.createConnection(dbCredentials); +export const db = drizzle(connection, { schema, mode: 'default' }); diff --git a/src/database/schema.ts b/src/database/schema.ts new file mode 100644 index 0000000..e48fb02 --- /dev/null +++ b/src/database/schema.ts @@ -0,0 +1,25 @@ +import { relations } from "drizzle-orm"; +import { int, mysqlTable, varchar } from "drizzle-orm/mysql-core"; + +export const personTable = mysqlTable('person', { + personId: int('personId').primaryKey().autoincrement(), + name: varchar('name', { length: 255 }), + secret: varchar('secret', { length: 255 }), +}); + +export const personRelations = relations(personTable, ({ many }) => ({ + points: many(pointsTable) +})); + +export const pointsTable = mysqlTable('points', { + pointsId: int('pointsId').primaryKey().autoincrement(), + personId: int('personId').notNull().references(() => personTable.personId), + points: int('points'), +}); + +export const pointsRelations = relations(pointsTable, ({ one }) => ({ + person: one(personTable, { + fields: [pointsTable.personId], + references: [personTable.personId], + }) +})); diff --git a/src/index.ts b/src/index.ts index c904e62..3e9431f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,9 @@ import express from "express"; import ejs from "ejs"; import multer from "multer"; +import { db } from "./database/db"; +import { eq, sql, sum } from "drizzle-orm"; +import { personTable, pointsTable } from "./database/schema"; const app = express(); const port = 8080; @@ -10,19 +13,29 @@ let people = [ { id: 1, name: "Adam", - secret: "adam" + secret: "adam", + points: [] }, { id: 2, name: "Vojta", - secret: "vojta" + secret: "vojta", + points: [] }, ] //app.use(express.urlencoded); app.use(express.static('www')); -app.get("/", (req, res) => { +app.get("/", async (req, res) => { + const people = await db.select({ + personId: personTable.personId, + name: personTable.name, + pointsTotal: sql`coalesce(sum(${pointsTable.points}), 0)` + }).from(personTable) + .leftJoin(pointsTable, eq(personTable.personId, pointsTable.personId)) + .groupBy(personTable.personId) + ejs.renderFile('src/templates/index.ejs', { people: people }, function (err, str) { if (err) { res.status(500).send(err); @@ -32,8 +45,23 @@ app.get("/", (req, res) => { }); }); -app.get("/gamble", (req, res) => { - ejs.renderFile('src/templates/gamble.ejs', { people: people }, function (err, str) { +app.get("/gamble/:id", async (req, res) => { + let id = parseInt(req.params.id); + if (id == undefined) { + res.status(404).send("Invalid person id"); + return; + } + + let person = await db.query.personTable.findFirst({ + where: eq(personTable.personId, id) + }) + + if (!person) { + res.status(404).send("Invalid person id"); + return; + } + + ejs.renderFile('src/templates/gamble.ejs', { person: person }, function (err, str) { if (err) { res.status(500).send(err); } @@ -42,13 +70,56 @@ app.get("/gamble", (req, res) => { }); }); -app.post("/gamble", upload.none(), (req, res) => { - console.log(req.body); - res.redirect('/'); +app.post("/gamble/:id", upload.none(), async (req, res) => { + let id = parseInt(req.params.id); + if (id == undefined) { + res.status(404).send("Invalid person id"); + return; + } + + let person = await db.query.personTable.findFirst({ + where: eq(personTable.personId, id) + }) + + if (!person) { + res.status(404).send("Invalid person id"); + return; + } + + if (person.secret != req.body['secret']) { + res.sendStatus(403); + return; + } + + await db.insert(pointsTable).values({ + personId: id, + points: req.body['points'] + }) + + res.redirect('/person/' + id); }); -app.get("/person/:id", (req, res) => { - let person = people.find((elem) => elem.id == req.params.id); +app.get("/person/:id", async (req, res) => { + let id = parseInt(req.params.id); + if (id == undefined) { + res.status(404).send("Invalid person id"); + return; + } + + let person = (await db.select({ + personId: personTable.personId, + name: personTable.name, + pointsTotal: sql`coalesce(sum(${pointsTable.points}), 0)` + }).from(personTable) + .leftJoin(pointsTable, eq(personTable.personId, pointsTable.personId)) + .where(eq(personTable.personId, id)) + .groupBy(personTable.personId))[0]; + + if (!person) { + res.status(404).send("Invalid person id"); + return; + } + ejs.renderFile('src/templates/person.ejs', { person: person }, function (err, str) { if (err) { res.status(500).send(err); diff --git a/src/templates/gamble.ejs b/src/templates/gamble.ejs index 6fabde3..a74bab8 100644 --- a/src/templates/gamble.ejs +++ b/src/templates/gamble.ejs @@ -10,11 +10,11 @@ -
-
-
-
-

+ +
+
+
+
diff --git a/src/templates/index.ejs b/src/templates/index.ejs index e3d999c..f1f313a 100644 --- a/src/templates/index.ejs +++ b/src/templates/index.ejs @@ -15,11 +15,11 @@
<% people.forEach((person) => {%> -
<%= person.name %> - <%= person.id %> + <%= person.pointsTotal %>
<% }); %> diff --git a/src/templates/person.ejs b/src/templates/person.ejs index 6a63d0d..0d6f880 100644 --- a/src/templates/person.ejs +++ b/src/templates/person.ejs @@ -11,15 +11,12 @@ Index - gamble + gamble

Name: <%= person.name %>

- Id: <%= person.id %> -

-

- secret: <%= person.secret %> + Points: <%= person.pointsTotal %>