From b0a40d0971276222940271a78b52060a25c717ab Mon Sep 17 00:00:00 2001 From: legonzaur Date: Tue, 28 Feb 2023 15:29:49 +0100 Subject: [PATCH] initial schema and migrations --- .env.example | 4 + .vscode/settings.json | 11 +- package-lock.json | 169 +++++++++++++++++++++++++++++ package.json | 4 + src/db/db.ts | 43 ++++++++ src/db/migrations.ts | 53 +++++++++ src/db/migrations/2023-02-28-01.ts | 76 +++++++++++++ src/db/model.ts | 43 ++++++++ src/index.ts | 6 + 9 files changed, 403 insertions(+), 6 deletions(-) create mode 100644 .env.example create mode 100644 src/db/db.ts create mode 100644 src/db/migrations.ts create mode 100644 src/db/migrations/2023-02-28-01.ts create mode 100644 src/db/model.ts diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..b763a40 --- /dev/null +++ b/.env.example @@ -0,0 +1,4 @@ +POSTGRES_HOST="localhost" +POSTGRES_DATABASE="postgres" +POSTGRES_USER="postgres" +POSTGRES_PASSWORD="postgres" \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 84a8c9b..1344b43 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,7 +1,6 @@ { - "editor.codeActionsOnSave": { - "source.fixAll": true, - "source.organizeImports": true - }, - "editor.formatOnSave": true -} \ No newline at end of file + "editor.codeActionsOnSave": { + "source.fixAll": true + }, + "editor.formatOnSave": true +} diff --git a/package-lock.json b/package-lock.json index 810f90c..9531882 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,11 +5,15 @@ "packages": { "": { "dependencies": { + "dotenv": "^16.0.3", "express": "^4.18.2", + "kysely": "^0.23.4", + "pg": "^8.9.0", "ts-node": "^10.9.1" }, "devDependencies": { "@types/express": "^4.17.17", + "@types/pg": "^8.6.6", "@typescript-eslint/eslint-plugin": "^5.54.0", "eslint": "^8.35.0", "eslint-config-standard-with-typescript": "^34.0.0", @@ -283,6 +287,17 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.14.2.tgz", "integrity": "sha512-1uEQxww3DaghA0RxqHx0O0ppVlo43pJhepY51OxuQIKHpjbnYLA7vcdwioNPzIqmC2u3I/dmylcqjlh0e7AyUA==" }, + "node_modules/@types/pg": { + "version": "8.6.6", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.6.6.tgz", + "integrity": "sha512-O2xNmXebtwVekJDD+02udOncjVcMZQuTEQEMpKJ0ZRf5E7/9JJX3izhKUcUifBkyKpljyUM6BTgy2trmviKlpw==", + "dev": true, + "dependencies": { + "@types/node": "*", + "pg-protocol": "*", + "pg-types": "^2.2.0" + } + }, "node_modules/@types/qs": { "version": "6.9.7", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", @@ -836,6 +851,14 @@ "node": ">=8" } }, + "node_modules/buffer-writer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz", + "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==", + "engines": { + "node": ">=4" + } + }, "node_modules/builtins": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz", @@ -1044,6 +1067,14 @@ "node": ">=6.0.0" } }, + "node_modules/dotenv": { + "version": "16.0.3", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz", + "integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==", + "engines": { + "node": ">=12" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -2432,6 +2463,14 @@ "json5": "lib/cli.js" } }, + "node_modules/kysely": { + "version": "0.23.4", + "resolved": "https://registry.npmjs.org/kysely/-/kysely-0.23.4.tgz", + "integrity": "sha512-3icLnj1fahUtZsP9zzOvF4DcdhekGsLX4ZaoBaIz0ZeHegyRDdbwpJD7zezAJ+KwQZNDeKchel6MikFNLsSZIA==", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -2721,6 +2760,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/packet-reader": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz", + "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -2788,6 +2832,80 @@ "node": ">=8" } }, + "node_modules/pg": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.9.0.tgz", + "integrity": "sha512-ZJM+qkEbtOHRuXjmvBtOgNOXOtLSbxiMiUVMgE4rV6Zwocy03RicCVvDXgx8l4Biwo8/qORUnEqn2fdQzV7KCg==", + "dependencies": { + "buffer-writer": "2.0.0", + "packet-reader": "1.0.0", + "pg-connection-string": "^2.5.0", + "pg-pool": "^3.5.2", + "pg-protocol": "^1.6.0", + "pg-types": "^2.1.0", + "pgpass": "1.x" + }, + "engines": { + "node": ">= 8.0.0" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, + "node_modules/pg-connection-string": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz", + "integrity": "sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==" + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-pool": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.5.2.tgz", + "integrity": "sha512-His3Fh17Z4eg7oANLob6ZvH8xIVen3phEZh2QuyrIl4dQSDVEabNducv6ysROKpDNPSD+12tONZVWfSgMvDD9w==", + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.6.0.tgz", + "integrity": "sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q==" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "dependencies": { + "split2": "^4.1.0" + } + }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -2800,6 +2918,41 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -3132,6 +3285,14 @@ "node": ">=8" } }, + "node_modules/split2": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.1.0.tgz", + "integrity": "sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ==", + "engines": { + "node": ">= 10.x" + } + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -3507,6 +3668,14 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "engines": { + "node": ">=0.4" + } + }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", diff --git a/package.json b/package.json index ab9c68c..ab9c719 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,14 @@ { "dependencies": { + "dotenv": "^16.0.3", "express": "^4.18.2", + "kysely": "^0.23.4", + "pg": "^8.9.0", "ts-node": "^10.9.1" }, "devDependencies": { "@types/express": "^4.17.17", + "@types/pg": "^8.6.6", "@typescript-eslint/eslint-plugin": "^5.54.0", "eslint": "^8.35.0", "eslint-config-standard-with-typescript": "^34.0.0", diff --git a/src/db/db.ts b/src/db/db.ts new file mode 100644 index 0000000..3a86e6b --- /dev/null +++ b/src/db/db.ts @@ -0,0 +1,43 @@ +import { Kysely, PostgresDialect } from "kysely"; +import { Pool } from "pg"; +import migrateToLatest from "./migrations"; +import Database from "./model"; +migrateToLatest(); +// You'd create one of these when you start your app. +const db = new Kysely({ + // Use MysqlDialect for MySQL and SqliteDialect for SQLite. + dialect: new PostgresDialect({ + pool: new Pool({ + host: process.env.POSTGRES_HOST, + database: process.env.POSTGRES_DATABASE, + user: process.env.POSTGRES_USER, + password: process.env.POSTGRES_PASSWORD, + }), + }), +}); +/* +async function demo() { + const { id } = await db + .insertInto("person") + .values({ first_name: "Jennifer", gender: "female" }) + .returning("id") + .executeTakeFirstOrThrow(); + + await db + .insertInto("pet") + .values({ name: "Catto", species: "cat", owner_id: id }) + .execute(); + + const person = await db + .selectFrom("person") + .innerJoin("pet", "pet.owner_id", "person.id") + .select(["first_name", "pet.name as pet_name"]) + .where("person.id", "=", id) + .executeTakeFirst(); + + if (person) { + person.pet_name; + } +} +*/ +export default "e"; diff --git a/src/db/migrations.ts b/src/db/migrations.ts new file mode 100644 index 0000000..a0f5555 --- /dev/null +++ b/src/db/migrations.ts @@ -0,0 +1,53 @@ +import { promises as fs } from "fs"; +import { + FileMigrationProvider, + Kysely, + Migrator, + PostgresDialect, +} from "kysely"; +import * as path from "path"; +import { Pool } from "pg"; +import Database from "./model"; + +console.log(process.env.POSTGRES_HOST); +async function migrateToLatest() { + const db = new Kysely({ + dialect: new PostgresDialect({ + pool: new Pool({ + host: process.env.POSTGRES_HOST, + database: process.env.POSTGRES_DATABASE, + user: process.env.POSTGRES_USER, + password: process.env.POSTGRES_PASSWORD, + }), + }), + }); + + const migrator = new Migrator({ + db, + provider: new FileMigrationProvider({ + fs, + path, + migrationFolder: path.join(__dirname, "./migrations"), + }), + }); + + const { error, results } = await migrator.migrateToLatest(); + + results?.forEach((it) => { + if (it.status === "Success") { + console.log(`migration "${it.migrationName}" was executed successfully`); + } else if (it.status === "Error") { + console.error(`failed to execute migration "${it.migrationName}"`); + } + }); + + if (error) { + console.error("failed to migrate"); + console.error(error); + process.exit(1); + } + + await db.destroy(); +} + +export default migrateToLatest; diff --git a/src/db/migrations/2023-02-28-01.ts b/src/db/migrations/2023-02-28-01.ts new file mode 100644 index 0000000..a9afac4 --- /dev/null +++ b/src/db/migrations/2023-02-28-01.ts @@ -0,0 +1,76 @@ +import { Kysely, sql } from "kysely"; + +export async function up(db: Kysely): Promise { + await db.schema + .createTable("entity") + .addColumn("id", "integer", (col) => col.unique().primaryKey()) + .addColumn("name", "varchar") + .execute(); + + await db.schema + .createTable("weapon") + .addColumn("id", "varchar", (col) => col.unique().primaryKey()) + .addColumn("name", "varchar") + .addColumn("description", "varchar") + .addColumn("image", "varchar") + .execute(); + + await db.schema + .createTable("map") + .addColumn("id", "varchar", (col) => col.unique().primaryKey()) + .addColumn("name", "varchar") + .addColumn("description", "varchar") + .addColumn("image", "varchar") + .execute(); + + await db.schema + .createTable("server") + .addColumn("name", "varchar", (col) => col.unique().primaryKey()) + .addColumn("description", "varchar") + .addColumn("token", "varchar") + .execute(); + + await db.schema + .createTable("kill") + .addColumn("id", "serial", (col) => col.primaryKey()) + .addColumn("server", "varchar", (col) => + col.references("server.name").notNull() + ) + .addColumn("attacker", "integer", (col) => + col.references("entity.id").notNull() + ) + .addColumn("victim", "integer", (col) => + col.references("entity.id").notNull() + ) + .addColumn("weapon", "varchar", (col) => + col.references("weapon.id").notNull() + ) + .addColumn("map", "varchar", (col) => col.references("map.id").notNull()) + .addColumn("distance", "integer", (col) => col.notNull()) + .addColumn("date", "varchar", (col) => col.defaultTo(sql`now()`).notNull()) + .execute(); + + /*await db.schema + .createTable("pet") + .addColumn("id", "serial", (col) => col.primaryKey()) + .addColumn("name", "varchar", (col) => col.notNull().unique()) + .addColumn("owner_id", "integer", (col) => + col.references("person.id").onDelete("cascade").notNull() + ) + .addColumn("species", "varchar", (col) => col.notNull()) + .execute();*/ + /* + await db.schema + .createIndex("kill") + .on("pet") + .column("owner_id") + .execute();*/ +} + +export async function down(db: Kysely): Promise { + await db.schema.dropTable("kill").execute(); + await db.schema.dropTable("entity").execute(); + await db.schema.dropTable("weapon").execute(); + await db.schema.dropTable("map").execute(); + await db.schema.dropTable("server").execute(); +} diff --git a/src/db/model.ts b/src/db/model.ts new file mode 100644 index 0000000..e13ae2e --- /dev/null +++ b/src/db/model.ts @@ -0,0 +1,43 @@ +import { Generated } from "kysely"; + +interface KillTable { + id: Generated; + server: string; + attacker: number; + victim: number; + weapon: string; + map: string; + distance: number; + date: Date; +} + +interface EntityTable { + id: number; + name: string; +} +interface WeaponTable { + id: string; + name: string; + description: string; + image: string; +} +interface MapTable { + id: string; + name: string; + description: string; + image: string; +} +interface ServerTable { + id: string; + name: string; + description: string; +} +interface Database { + kill: KillTable; + entity: EntityTable; + weapon: WeaponTable; + maps: MapTable; + server: ServerTable; +} + +export default Database; diff --git a/src/index.ts b/src/index.ts index 960e325..5ebee6d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,13 @@ +import * as dotenv from "dotenv"; +dotenv.config(); import express from "express"; import client from "./client"; +import db from "./db/db"; import server from "./server"; +console.log("dotenv"); +console.log(db); + const app = express(); const port = 3000;