feat: prepare TypeORM support for Postgres db calls

This commit is contained in:
2025-12-01 01:48:24 +01:00
parent 7dd1025aef
commit 56975a0e92
12 changed files with 2970 additions and 0 deletions

13
.env.default Normal file
View File

@@ -0,0 +1,13 @@
# TypeORM specific
# Please make sure these match with docker-compose.yml, or your own postgres server.
PG_USER=kitty
PG_PASS=CHANGEME
PG_HOST=127.0.0.1
PG_PORT=5432
PG_DB=kittyurl
# Site info
PUBLIC_URL=https://example.com # Publicly accessible website root, used for rewrites. Note there is no trailing slash in the URL.
IS_PROXIED=false # Set to `true` if behind a reverse proxy, like apache/nginx.
USE_SUBDOMAINS=true # Whether to use subdomains for URL generation.
DEBUG=false # Set to `false` to disable some features not meant to be seen publicly (like swagger documentation).

10
.gitignore vendored Normal file
View File

@@ -0,0 +1,10 @@
# TypeORM
.idea/
.vscode/
node_modules/
build/
tmp/
temp/
# .env
.env

12
docker-compose.yml Normal file
View File

@@ -0,0 +1,12 @@
# This file will likely be moved into the main repo soon.
services:
postgres:
image: "postgres:17.2"
ports:
- "5432:5432"
environment:
POSTGRES_USER: "kitty"
POSTGRES_PASSWORD: "CHANGEME"
POSTGRES_DB: "kittyurl"

2735
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

25
package.json Normal file
View File

@@ -0,0 +1,25 @@
{
"name": "kittyBE",
"version": "0.0.1",
"description": "Your go-to place for short and memorable URLs.",
"type": "commonjs",
"devDependencies": {
"@types/node": "^22.19.1",
"ts-node": "^10.9.2",
"typescript": "^5.8.2"
},
"dependencies": {
"dotenv": "^17.2.3",
"pg": "^8.16.3",
"reflect-metadata": "^0.2.2",
"swagger-jsdoc": "^6.2.8",
"swagger-ui-express": "^5.0.1",
"typeorm": "0.3.27"
},
"scripts": {
"start": "ts-node src/index.ts",
"typeorm": "typeorm-ts-node-commonjs",
"newMigration": "typeorm-ts-node-commonjs migration:generate -p -d ./src/data-source.ts",
"pendingMigration": "typeorm-ts-node-commonjs migration:run -d ./src/data-source.ts"
}
}

18
src/data-source.ts Normal file
View File

@@ -0,0 +1,18 @@
import "reflect-metadata"
import { DataSource } from "typeorm"
import * as dotenv from "dotenv";
dotenv.config();
export const AppDataSource = new DataSource({
type: "postgres",
host: process.env.PG_HOST ?? "localhost",
port: parseInt(process.env.PG_PORT, 10) || 5432,
username: process.env.PG_USER ?? "kitty",
password: process.env.PG_PASS ?? "CHANGEME", // Please change your password inside of the .env file!
database: process.env.PG_DB ?? "kittyurl",
synchronize: false,
logging: false,
entities: [__dirname + '/entities/*.ts'],
migrations: [__dirname + '/migrations/*.ts'],
subscribers: [],
})

31
src/entities/Link.ts Normal file
View File

@@ -0,0 +1,31 @@
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinTable } from "typeorm"
import { User } from "./User"
@Entity("links")
export class Link {
@PrimaryGeneratedColumn()
id: number
@Column({ nullable: true })
subdomain: string
@Column()
shortUri: string
@Column()
fullUrl: string
@Column()
role: string
@Column('bigint')
createDate: number
@Column('bigint')
expiryDate: number
@ManyToOne(() => User, (user) => user.links)
@JoinTable()
author: User
}

24
src/entities/User.ts Normal file
View File

@@ -0,0 +1,24 @@
import { Entity, PrimaryGeneratedColumn, Column, OneToMany } from "typeorm"
import { Link } from "./Link"
@Entity("users")
export class User {
@PrimaryGeneratedColumn()
id: number
@Column({ unique: true })
name: string
@Column()
passwordHash: string
@Column()
role: string
@Column('bigint')
createdAt: number
@OneToMany(() => Link, (link) => link.author)
links: Link[];
}

25
src/index.ts Normal file
View File

@@ -0,0 +1,25 @@
import { AppDataSource } from "./data-source"
import { User } from "./entities/User"
import { smokeTest } from "./smoke-test";
AppDataSource.initialize().then(async () => {
// console.log("Inserting a new user into the database...");
// const user = new User();
// // user.firstName = "Timber";
// // user.lastName = "Saw";
// // user.age = 25;
// await AppDataSource.manager.save(user);
// console.log("Saved a new user with id: " + user.id);
// console.log("Loading users from the database...");
// const users = await AppDataSource.manager.find(User);
// console.log("Loaded users: ", users);
// console.log("Here you can setup and run express / fastify / any other framework.");
await AppDataSource.runMigrations();
await smokeTest(AppDataSource);
}).catch(error => console.log(error))

View File

@@ -0,0 +1,49 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class InitialMigration1764549652720 implements MigrationInterface {
name = 'InitialMigration1764549652720'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
CREATE TABLE "links" (
"id" SERIAL NOT NULL,
"subdomain" character varying,
"shortUri" character varying NOT NULL,
"fullUrl" character varying NOT NULL,
"role" character varying NOT NULL,
"createDate" bigint NOT NULL,
"expiryDate" bigint NOT NULL,
"authorId" integer,
CONSTRAINT "PK_ecf17f4a741d3c5ba0b4c5ab4b6" PRIMARY KEY ("id")
)
`);
await queryRunner.query(`
CREATE TABLE "users" (
"id" SERIAL NOT NULL,
"name" character varying NOT NULL,
"passwordHash" character varying NOT NULL,
"role" character varying NOT NULL,
"createdAt" bigint NOT NULL,
CONSTRAINT "UQ_51b8b26ac168fbe7d6f5653e6cf" UNIQUE ("name"),
CONSTRAINT "PK_a3ffb1c0c8416b9fc6f907b7433" PRIMARY KEY ("id")
)
`);
await queryRunner.query(`
ALTER TABLE "links"
ADD CONSTRAINT "FK_c5287c1e74cbb62159104715543" FOREIGN KEY ("authorId") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION
`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE "links" DROP CONSTRAINT "FK_c5287c1e74cbb62159104715543"
`);
await queryRunner.query(`
DROP TABLE "users"
`);
await queryRunner.query(`
DROP TABLE "links"
`);
}
}

13
src/smoke-test.ts Normal file
View File

@@ -0,0 +1,13 @@
import { Connection } from "typeorm";
import { User } from "./entities/User";
export async function smokeTest(connection: Connection) {
const user = new User();
user.name = "admin";
user.role = "admin";
user.createdAt = Date.now();
user.passwordHash = "pretend this is a hash";
await connection.manager.save(user);
}

15
tsconfig.json Normal file
View File

@@ -0,0 +1,15 @@
{
"compilerOptions": {
"lib": [
"es2021"
],
"types": ["node"],
"target": "es2021",
"module": "commonjs",
"moduleResolution": "node",
"outDir": "./build",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"sourceMap": true
}
}