Compare commits

...

13 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
58460d988d chore: add new workflows (changelog, automatic releases)
All checks were successful
Update changelog / changelog (push) Successful in 26s
Docker images will now be built on every release
2025-12-09 12:32:38 +01:00
8fb05f2662 fix: use debian-based container for image building
All checks were successful
Build and push Docker image / build (push) Successful in 2m26s
fixes https://stackoverflow.com/a/75730731
2025-12-07 20:21:45 +01:00
2e325fe018 feat: refactor docker workflow; build both amd64 and arm64 docker images
Some checks failed
Build and push Docker image / build (push) Failing after 54s
2025-12-07 20:18:24 +01:00
52dbd158a9 fix: respect debug flag in .env for swagger endpoint generation
All checks were successful
Build and push Docker image / build (push) Successful in 22s
2025-12-04 17:15:20 +01:00
f897a3e4ce chore: move docker-compose to main repo
All checks were successful
Build and push Docker image / build (push) Successful in 27s
2025-12-04 15:33:54 +01:00
bf6fb81a73 fix: add .dockerignore to not build containers with .env secrets
All checks were successful
Build and push Docker image / build (push) Successful in 24s
2025-12-03 00:19:25 +01:00
7bb2dd5434 fix: typo in Gitea workflow 2
All checks were successful
Build and push Docker image / build (push) Successful in 22s
2025-12-02 23:17:10 +01:00
fa5932d270 fix: typo in Gitea workflow
Some checks failed
Build and push Docker image / build (push) Failing after 1s
2025-12-02 23:11:17 +01:00
09f0d3ce1f feat: add Gitea workflow for Docker image building
Some checks failed
Build and push Docker image / build (push) Failing after 22s
2025-12-02 23:04:19 +01:00
6b2f93b962 feat: add a Dockerfile for image building 2025-12-02 22:59:21 +01:00
1b8c9ccaeb fix: add type hints for nullable columns 2025-12-01 17:26:54 +01:00
10 changed files with 210 additions and 27 deletions

3
.dockerignore Normal file
View File

@@ -0,0 +1,3 @@
.env
*/.env
*.md

View File

@@ -0,0 +1,58 @@
# Credits: https://www.vanmeeuwen.dev/blog/automating-docker-builds-with-gitea-actions
# https://gitea.com/gitea/runner-images/src/branch/main/.gitea/workflows/release.yaml
name: Build and push Docker image
run-name: Build ${{ github.ref_name }} image
on:
push:
tags:
- "*"
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker BuildX
uses: docker/setup-buildx-action@v3
- name: Login to registry
uses: docker/login-action@v2
with:
registry: gitea.7o7.cx
username: ${{ secrets.REGISTRY_USER }}
password: ${{ secrets.REGISTRY_PASSWORD }}
- name: Set outputs
id: vars
env:
BRANCH_NAME: ${{ github.ref_name }}
run: |
#echo branch_name="$BRANCH_NAME" | tee -a $GITHUB_OUTPUT
#echo short_hash=$(git rev-parse --short HEAD) | tee -a $GITHUB_OUTPUT
echo current_tag=$GITHUB_REF_NAME | tee -a $GITHUB_OUTPUT
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
platforms: |
linux/amd64
linux/arm64
pull: true
push: true
# Change when working on a different branch!
tags: |
gitea.7o7.cx/kittyteam/kittybe:master-${{ steps.vars.outputs.current_tag }}
gitea.7o7.cx/kittyteam/kittybe:latest
- name: Log out from registry
if: always()
run: docker logout gitea.7o7.cx

View File

@@ -0,0 +1,35 @@
# Credits: # https://gitea.com/gitea/runner-images/src/branch/main/.gitea/workflows/release.yaml
name: Release new version
run-name: Release ${{ github.ref_name }}
on:
push:
tags:
- "*"
jobs:
release:
runs-on: ubuntu-latest
container: docker.io/thegeeklab/git-sv:2.0.9
steps:
- name: Install tools
run: |
apk add -q --update --no-cache nodejs
- name: Checkout repository
uses: actions/checkout@v6
with:
fetch-tags: true
fetch-depth: 0
- name: Create changelog
run: |
git sv current-version
git sv release-notes -t ${GITHUB_REF#refs/tags/} -o CHANGELOG.md
sed -i '1,2d' CHANGELOG.md # remove version
cat CHANGELOG.md
- name: Release
uses: https://github.com/akkuman/gitea-release-action@v1
with:
body_path: CHANGELOG.md
token: "${{ secrets.REPO_RW_TOKEN }}"

View File

@@ -0,0 +1,37 @@
# Credit: https://gitea.com/gitea/helm-gitea/src/branch/main/.gitea/workflows/changelog.yml
name: Update changelog
run-name: Update changelog on push
on:
push:
branches:
- master
tags:
- "*"
jobs:
changelog:
runs-on: ubuntu-latest
container: docker.io/thegeeklab/git-sv:2.0.9
steps:
- name: Install tools
run: |
apk add -q --update --no-cache nodejs curl jq sed
- name: Checkout repository
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Generate upcoming changelog
run: |
git sv rn -o changelog.md
export RELEASE_NOTES=$(cat changelog.md)
export ISSUE_NUMBER=$(curl -s -H 'Authorization: token ${{ secrets.ISSUE_RW_TOKEN }}' "https://gitea.7o7.cx/api/v1/repos/kittyteam/kittyBE/issues?state=open&q=Changelog%20for%20upcoming%20version" | jq '.[].number')
echo $RELEASE_NOTES
JSON_DATA=$(echo "" | jq -Rs --arg title 'Changelog for upcoming version' --arg body "$(cat changelog.md)" '{title: $title, body: $body}')
if [ -z "$ISSUE_NUMBER" ]; then
curl -s -X POST "https://gitea.7o7.cx/api/v1/repos/kittyteam/kittyBE/issues" -H "Authorization: token ${{ secrets.ISSUE_RW_TOKEN }}" -H "Content-Type: application/json" -d "$JSON_DATA" > /dev/null
else
curl -s -X PATCH "https://gitea.7o7.cx/api/v1/repos/kittyteam/kittyBE/issues/$ISSUE_NUMBER" -H "Authorization: token ${{ secrets.ISSUE_RW_TOKEN }}" -H "Content-Type: application/json" -d "$JSON_DATA" > /dev/null
fi

19
Dockerfile Normal file
View File

@@ -0,0 +1,19 @@
# Credit: https://www.digitalocean.com/community/tutorials/how-to-build-a-node-js-application-with-docker
FROM node:24-trixie-slim AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production && npm cache clean --force
COPY . .
FROM node:24-trixie-slim AS production
WORKDIR /app
RUN addgroup --gid 1001 nodejs && \
adduser --gid 1001 --uid 1001 nodejs
COPY --from=builder --chown=nodejs:nodejs /app /app
USER nodejs
EXPOSE 6567
CMD ["npm", "run", "server"]

View File

@@ -1,12 +0,0 @@
# 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"

View File

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

View File

@@ -1,5 +1,7 @@
import * as express from 'express';
import router from './routes';
import * as dotenv from "dotenv";
dotenv.config({ quiet: true });
const app = express();
@@ -20,6 +22,21 @@ const swaggerSpec = swaggerJsdoc(swaggerJsdocOpts);
app.use(express.json());
app.use(router);
app.use('/swagger', swaggerUi.serve, swaggerUi.setup(swaggerSpec));
if (process.env.DEBUG === "true") {
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.'));

View File

@@ -4,28 +4,45 @@ import { User } from "./User"
@Entity("links")
export class Link {
// Unique link id.
@PrimaryGeneratedColumn()
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 })
subdomain: string
subdomain: string | null
// Shortened Uri.
@Column()
shortUri: string
// URL to which the user should be redirected
@Column()
fullUrl: string
@Column()
role: string
// Unix timestamp of link creation date.
@Column('bigint')
createDate: number
@Column('bigint')
expiryDate: number
// Unix timestamp of when the link should expire.
// If null, the link will never expire unless deleted.
@Column('bigint', { nullable: true })
expiryDate: number | null
@ManyToOne(() => User, (user) => user.links)
// 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 })
@JoinTable()
author: User
author: User | null
}

View File

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