Compare commits

...

2 Commits

14 changed files with 2971 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"

2689
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

27
package.json Normal file
View File

@@ -0,0 +1,27 @@
{
"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",
"express": "^5.1.0",
"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",
"server": "ts-node src/app.ts"
}
}

25
src/app.ts Normal file
View File

@@ -0,0 +1,25 @@
import * as express from 'express';
import router from './routes';
const app = express();
const swaggerJsdocOpts = {
failOnErrors: true,
definition: {
openapi: '3.0.0',
info: {
title: 'kittyurl',
version: '0.0.1'
}
},
apis: ['./src/routes/*.ts']
};
const swaggerUi = require('swagger-ui-express');
const swaggerJsdoc = require('swagger-jsdoc');
const swaggerSpec = swaggerJsdoc(swaggerJsdocOpts);
app.use(express.json());
app.use(router);
app.use('/swagger', swaggerUi.serve, swaggerUi.setup(swaggerSpec));
app.listen(6567, () => console.log('(HTTP Server) Listening on port 6567.'));

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"
`);
}
}

20
src/routes/index.ts Normal file
View File

@@ -0,0 +1,20 @@
import { Router } from 'express';
const router = Router();
/**
* @swagger
*
* /:
* get:
* description: Hello world!
* tags: [Default]
* responses:
* 200:
* description: Returns "Hello world!"
*/
router.get('/', (req, res) => {
res.send("Hello world!");
});
export default router;

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
}
}