Compare commits

..

2 Commits

Author SHA1 Message Date
3f225a1ecb fix: add revised migration
All checks were successful
Update changelog / changelog (push) Successful in 25s
2025-12-11 22:37:24 +01:00
bade2f9b86 feat: add 404 handling
All checks were successful
Update changelog / changelog (push) Successful in 25s
2025-12-09 12:49:55 +01:00
5 changed files with 53 additions and 13 deletions

View File

@@ -12,10 +12,11 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
container: docker.io/thegeeklab/git-sv:2.0.9 container: docker.io/thegeeklab/git-sv:2.0.9
steps: steps:
- name: install tools - name: Install tools
run: | run: |
apk add -q --update --no-cache nodejs apk add -q --update --no-cache nodejs
- uses: actions/checkout@v6 - name: Checkout repository
uses: actions/checkout@v6
with: with:
fetch-tags: true fetch-tags: true
fetch-depth: 0 fetch-depth: 0

View File

@@ -1,7 +1,7 @@
import { MigrationInterface, QueryRunner } from "typeorm"; import { MigrationInterface, QueryRunner } from "typeorm";
export class InitialMigration1764549652720 implements MigrationInterface { export class RevisedMigration1765488793696 implements MigrationInterface {
name = 'InitialMigration1764549652720' name = 'RevisedMigration1765488793696'
public async up(queryRunner: QueryRunner): Promise<void> { public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(` await queryRunner.query(`
@@ -10,9 +10,10 @@ export class InitialMigration1764549652720 implements MigrationInterface {
"subdomain" character varying, "subdomain" character varying,
"shortUri" character varying NOT NULL, "shortUri" character varying NOT NULL,
"fullUrl" character varying NOT NULL, "fullUrl" character varying NOT NULL,
"role" character varying NOT NULL,
"createDate" bigint NOT NULL, "createDate" bigint NOT NULL,
"expiryDate" bigint NOT NULL, "expiryDate" bigint,
"visits" bigint NOT NULL,
"privacy" boolean NOT NULL,
"authorId" integer, "authorId" integer,
CONSTRAINT "PK_ecf17f4a741d3c5ba0b4c5ab4b6" PRIMARY KEY ("id") CONSTRAINT "PK_ecf17f4a741d3c5ba0b4c5ab4b6" PRIMARY KEY ("id")
) )
@@ -22,7 +23,7 @@ export class InitialMigration1764549652720 implements MigrationInterface {
"id" SERIAL NOT NULL, "id" SERIAL NOT NULL,
"name" character varying NOT NULL, "name" character varying NOT NULL,
"passwordHash" character varying NOT NULL, "passwordHash" character varying NOT NULL,
"role" character varying NOT NULL, "role" integer NOT NULL,
"createdAt" bigint NOT NULL, "createdAt" bigint NOT NULL,
CONSTRAINT "UQ_51b8b26ac168fbe7d6f5653e6cf" UNIQUE ("name"), CONSTRAINT "UQ_51b8b26ac168fbe7d6f5653e6cf" UNIQUE ("name"),
CONSTRAINT "PK_a3ffb1c0c8416b9fc6f907b7433" PRIMARY KEY ("id") CONSTRAINT "PK_a3ffb1c0c8416b9fc6f907b7433" PRIMARY KEY ("id")

View File

@@ -25,5 +25,18 @@ app.use(router);
if (process.env.DEBUG === "true") { if (process.env.DEBUG === "true") {
app.use('/swagger', swaggerUi.serve, swaggerUi.setup(swaggerSpec)); app.use('/swagger', swaggerUi.serve, swaggerUi.setup(swaggerSpec));
} }
// Handle 404s
// https://stackoverflow.com/a/9802006
app.use(function(req, res) {
res.status(404);
if (req.accepts('json')) {
res.json({ status: 'error', error: 'Not found' });
return;
}
res.type('txt').send('Not found');
});
app.listen(6567, () => console.log('(HTTP Server) Listening on port 6567.')); app.listen(6567, () => console.log('(HTTP Server) Listening on port 6567.'));

View File

@@ -4,27 +4,44 @@ import { User } from "./User"
@Entity("links") @Entity("links")
export class Link { export class Link {
// Unique link id.
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn()
id: number id: number
// Experimental: subdomain which should be a part of the short url.
// For instance in the URL "abc.example.com/def", abc is the subdomain.
// "def.example.com/def" won't resolve to the URL that "abc.example.com/def" does.
@Column({ nullable: true }) @Column({ nullable: true })
subdomain: string | null subdomain: string | null
// Shortened Uri.
@Column() @Column()
shortUri: string shortUri: string
// URL to which the user should be redirected
@Column() @Column()
fullUrl: string fullUrl: string
@Column() // Unix timestamp of link creation date.
role: string
@Column('bigint') @Column('bigint')
createDate: number createDate: number
@Column('bigint') // Unix timestamp of when the link should expire.
expiryDate: number // If null, the link will never expire unless deleted.
@Column('bigint', { nullable: true })
expiryDate: number | null
// Aggregated amount of visits.
@Column('bigint')
visits: number
// Link privacy:
// - true, if link is private
// - false, if link can be shown in a list of recent links publicly.
@Column()
privacy: boolean
// User to which the shortened URL belongs.
@ManyToOne(() => User, (user) => user.links, { nullable: true }) @ManyToOne(() => User, (user) => user.links, { nullable: true })
@JoinTable() @JoinTable()
author: User | null author: User | null

View File

@@ -4,21 +4,29 @@ import { Link } from "./Link"
@Entity("users") @Entity("users")
export class User { export class User {
// Unique user id.
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn()
id: number id: number
// User name, must be unique.
@Column({ unique: true }) @Column({ unique: true })
name: string name: string
// Salted password hash.
@Column() @Column()
passwordHash: string passwordHash: string
// User role:
// - 0 - means unprivileged user,
// - 1 - means administrative user.
@Column() @Column()
role: string role: number
// Account creation date as a Unix timestamp.
@Column('bigint') @Column('bigint')
createdAt: number createdAt: number
// List of shortened URLs which belong to the user.
@OneToMany(() => Link, (link) => link.author) @OneToMany(() => Link, (link) => link.author)
links: Link[]; links: Link[];
} }