From ce914f91fd852da5e4fe2ce20c77ecfcfe54cd76 Mon Sep 17 00:00:00 2001 From: Joseph Nelson Date: Wed, 13 May 2026 23:16:20 -0700 Subject: [PATCH] Working base server with migrations and build steps ironed out (#1) Reviewed-on: https://gitea.nelson-household.com/Hard-at-Work/tcg-collectors-server/pulls/1 Co-authored-by: Joseph Nelson Co-committed-by: Joseph Nelson --- .gitea/hooks/pre-commit | 20 +++++++++++ .gitea/workflows/build.yaml | 39 +++++++++++++++++++++ .gitignore | 2 ++ Dockerfile | 11 ++++++ app.js | 30 ++++++++++++++++ config/index.js | 10 ++++++ eslint.config.js | 27 +++++++++++++++ migrations/001_create-image-table.js | 17 +++++++++ migrations/002_create-collection-table.js | 18 ++++++++++ migrations/003_create-item-table.js | 25 ++++++++++++++ migrations/004_create-location-table.js | 16 +++++++++ migrations/005_create-set-table.js | 22 ++++++++++++ migrations/006_create-role-table.js | 16 +++++++++ migrations/007_create-user-table.js | 20 +++++++++++ package.json | 42 +++++++++++++++++++++++ prettier.config.js | 8 +++++ 16 files changed, 323 insertions(+) create mode 100755 .gitea/hooks/pre-commit create mode 100644 .gitea/workflows/build.yaml create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 app.js create mode 100644 config/index.js create mode 100644 eslint.config.js create mode 100644 migrations/001_create-image-table.js create mode 100644 migrations/002_create-collection-table.js create mode 100644 migrations/003_create-item-table.js create mode 100644 migrations/004_create-location-table.js create mode 100644 migrations/005_create-set-table.js create mode 100644 migrations/006_create-role-table.js create mode 100644 migrations/007_create-user-table.js create mode 100644 package.json create mode 100644 prettier.config.js diff --git a/.gitea/hooks/pre-commit b/.gitea/hooks/pre-commit new file mode 100755 index 0000000..4b6a65f --- /dev/null +++ b/.gitea/hooks/pre-commit @@ -0,0 +1,20 @@ +#!/bin/bash + +# Check if the lint fix command fails +# If it does, exit with a non-zero status to prevent the commit. + +echo "Running lint fix..." +# Replace with your actual lint fix command +# This is just an example, adapt it to your linter and project +npm run lint:fix # e.g., "eslint . --fix" + +# Example: Capture the exit code of the command +lint_fix_result=$? + +if [ $lint_fix_result -ne 0 ]; then + echo "Linting failed. Commit aborted." + exit 1 # Exit with a non-zero status to prevent the commit +fi + +echo "Lint fix completed successfully. Commit allowed." +exit 0 # Exit with a zero status to allow the commit diff --git a/.gitea/workflows/build.yaml b/.gitea/workflows/build.yaml new file mode 100644 index 0000000..cd24e97 --- /dev/null +++ b/.gitea/workflows/build.yaml @@ -0,0 +1,39 @@ +name: Build and Push Image +on: + pull_request: + branches: + - main + types: [closed] +jobs: + build-and-push: + if: gitea.event.pull_request.merged == true + runs-on: ubuntu-latest + container: + image: catthehacker/ubuntu:act-latest + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Log in to Gitea Registry + uses: docker/login-action@v2 + with: + registry: gitea.nelson-household.com # Replace with your Gitea domain + username: ${{ gitea.actor }} + password: ${{ secrets.RUNNER_TOKEN }} + + - name: Delete Old Images and Containers + run: | + docker ps -a -q -f "name=tcg-collectors-server" | xargs -I {} docker rm {} || true + docker images --format "{{.Repository}}:{{.Tag}}" | grep "tcg-collectors-server" | xargs -I {} docker rmi {} || true + + - name: Build and Push Image + run: | + docker build \ + --build-arg DATABASE_URL=${{ secrets.DATABASE_URL }} \ + -t gitea.nelson-household.com/hard-at-work/tcg-collectors-server/tcg-collectors-server:${{ github.sha }} . + docker push gitea.nelson-household.com/hard-at-work/tcg-collectors-server/tcg-collectors-server:${{ github.sha }} + + - name: Execute Migrations + run: | + docker run gitea.nelson-household.com/hard-at-work/tcg-collectors-server/tcg-collectors-server:${{ github.sha }} npm run migrate up diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d5f19d8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules +package-lock.json diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..d1a5e5c --- /dev/null +++ b/Dockerfile @@ -0,0 +1,11 @@ +FROM node:24-alpine + +ARG DATABASE_URL + +ENV DATABASE_URL=${DATABASE_URL} + +WORKDIR /app +COPY . . +RUN npm install --force + +CMD ["npm", "run", "start"] diff --git a/app.js b/app.js new file mode 100644 index 0000000..b8629cf --- /dev/null +++ b/app.js @@ -0,0 +1,30 @@ +const express = require('express') +const path = require('path') + +const authRoutes = require('./routes/auth') +const productsRoutes = require('./routes/products') +const usersRoutes = require('./routes/users') + +const app = express() +const port = process.env.PORT || 3000 + +// Middleware +app.use(express.json()) // For parsing application/json +app.use(express.urlencoded({ extended: true })) // For parsing URL-encoded form data + +// Static Files (Serving Frontend) +app.use(express.static(path.join(__dirname, 'public'))) + +// Mount Routes +app.use('/auth', authRoutes) +app.use('/products', productsRoutes) +app.use('/users', usersRoutes) + +// Default Route (Catch-all for undefined routes) +app.use((req, res, next) => { + res.status(404).send('Sorry, the resource you requested could not be found.') +}) + +app.listen(port, () => { + console.log(`Server listening on port ${port}`) +}) diff --git a/config/index.js b/config/index.js new file mode 100644 index 0000000..c3f9d4b --- /dev/null +++ b/config/index.js @@ -0,0 +1,10 @@ +// config/index.js +export default { + database: { + host: process.env.DB_HOST || 'localhost', + user: process.env.DB_USER || 'your_db_user', + password: process.env.DB_PASSWORD || 'your_db_password', + database: process.env.DB_NAME || 'your_db_name', + port: process.env.DB_PORT || 5432, + }, +} diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..6ea7cfd --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,27 @@ +// eslint.config.js (Advanced alternative) +import prettierPlugin from 'eslint-plugin-prettier' +import prettierConfig from 'eslint-config-prettier' +import prettierOptions from './prettier.config.js' // Import your config directly +import importPlugin from 'eslint-plugin-import' + +export default [ + { + plugins: { + prettier: prettierPlugin, + import: importPlugin, + }, + rules: { + ...prettierConfig.rules, + 'prettier/prettier': ['error', prettierOptions], + 'no-sequences': 'off', + 'no-console': 'warn', + 'import/first': 'error', + 'import/newline-after-import': 'error', + 'import/no-duplicates': 'error', + 'import/no-unresolved': 'error', + 'import/no-unused-modules': ['warn', { missingExports: true }], + quotes: ['error', 'single'], + semi: 'off', + }, + }, +] diff --git a/migrations/001_create-image-table.js b/migrations/001_create-image-table.js new file mode 100644 index 0000000..aa5ba6c --- /dev/null +++ b/migrations/001_create-image-table.js @@ -0,0 +1,17 @@ +export const up = (pgm) => { + pgm.sql(` + CREATE TABLE images ( + id UUID PRIMARY KEY, + file TEXT, + image BYTEA, + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW() + ); + `) +} + +export const down = (pgm) => { + pgm.sql(` + DROP TABLE images; + `) +} diff --git a/migrations/002_create-collection-table.js b/migrations/002_create-collection-table.js new file mode 100644 index 0000000..fa29a28 --- /dev/null +++ b/migrations/002_create-collection-table.js @@ -0,0 +1,18 @@ +export const up = (pgm) => { + pgm.sql(` + CREATE TABLE collections ( + id UUID PRIMARY KEY, + name TEXT, + image_id UUID, + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW(), + FOREIGN KEY (image_id) REFERENCES images(id) + ); + `) +} + +export const down = (pgm) => { + pgm.sql(` + DROP TABLE collections; + `) +} diff --git a/migrations/003_create-item-table.js b/migrations/003_create-item-table.js new file mode 100644 index 0000000..75f88e1 --- /dev/null +++ b/migrations/003_create-item-table.js @@ -0,0 +1,25 @@ +export const up = (pgm) => { + pgm.sql(` + CREATE TABLE items ( + id UUID PRIMARY KEY, + collection_id UUID, + image_id UUID, + productId TEXT, + name TEXT, + cleanName TEXT, + extCardText TEXT, + marketPrice TEXT, + extRarity TEXT, + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW(), + FOREIGN KEY (collection_id) REFERENCES collections(id), + FOREIGN KEY (image_id) REFERENCES images(id) + ); + `) +} + +export const down = (pgm) => { + pgm.sql(` + DROP TABLE items; + `) +} diff --git a/migrations/004_create-location-table.js b/migrations/004_create-location-table.js new file mode 100644 index 0000000..3298635 --- /dev/null +++ b/migrations/004_create-location-table.js @@ -0,0 +1,16 @@ +export const up = (pgm) => { + pgm.sql(` + CREATE TABLE locations ( + id UUID PRIMARY KEY, + name TEXT UNIQUE NOT NULL, + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW() + ); + `) +} + +export const down = (pgm) => { + pgm.sql(` + DROP TABLE locations; + `) +} diff --git a/migrations/005_create-set-table.js b/migrations/005_create-set-table.js new file mode 100644 index 0000000..3c220f5 --- /dev/null +++ b/migrations/005_create-set-table.js @@ -0,0 +1,22 @@ +export const up = (pgm) => { + pgm.sql(` + CREATE TABLE sets ( + id UUID PRIMARY KEY, + collection_id UUID NOT NULL, + name TEXT NOT NULL, + location_id UUID, + image_id UUID, + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW(), + FOREIGN KEY (collection_id) REFERENCES collections(id), + FOREIGN KEY (location_id) REFERENCES locations(id), + FOREIGN KEY (image_id) REFERENCES images(id) + ); + `) +} + +export const down = (pgm) => { + pgm.sql(` + DROP TABLE sets; + `) +} diff --git a/migrations/006_create-role-table.js b/migrations/006_create-role-table.js new file mode 100644 index 0000000..52ef845 --- /dev/null +++ b/migrations/006_create-role-table.js @@ -0,0 +1,16 @@ +export const up = (pgm) => { + pgm.sql(` + CREATE TABLE roles ( + id UUID PRIMARY KEY, + name TEXT UNIQUE NOT NULL, + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW() + ); + `) +} + +export const down = (pmg) => { + pgm.sql(` + DROP TABLE roles; + `) +} diff --git a/migrations/007_create-user-table.js b/migrations/007_create-user-table.js new file mode 100644 index 0000000..1be04d5 --- /dev/null +++ b/migrations/007_create-user-table.js @@ -0,0 +1,20 @@ +export const up = (pgm) => { + pgm.sql(` + CREATE TABLE users ( + id UUID PRIMARY KEY, + username TEXT UNIQUE NOT NULL, + password TEXT NOT NULL, + role_id UUID, + email TEXT UNIQUE NOT NULL, + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW(), + FOREIGN KEY (role_id) REFERENCES roles(id) + ); + `) +} + +export const down = (pgm) => { + pgm.sql(` + DROP TABLE users; + `) +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..b661d5b --- /dev/null +++ b/package.json @@ -0,0 +1,42 @@ +{ + "name": "tcg-collectors-server", + "version": "1.0.0", + "description": "A basic Express application with PostgreSQL integration.", + "main": "app.js", + "scripts": { + "start": "node app.js", + "dev": "nodemon app.js", + "lint": "eslint .", + "lint:fix": "eslint . --fix", + "format": "prettier --config prettier.config.js --write .", + "migrate": "node-pg-migrate" + }, + "type": "module", + "keywords": [ + "express", + "node.js", + "postgres", + "postgresql", + "database" + ], + "author": "Your Name", + "license": "MIT", + "dependencies": { + "csv-parser": "^3.0.0", + "dotenv": "^16.0.3", + "express": "^4.18.2", + "node-pg": "^1.0.1", + "p-limit": "^7.3.0", + "pg": "^8.20.0" + }, + "devDependencies": { + "eslint": "^10.3.0", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-import": "^2.32.0", + "eslint-plugin-prettier": "^5.5.5", + "jest": "^29.0.0", + "node-pg-migrate": "^8.0.4", + "nodemon": "^3.0.0", + "prettier": "^3.8.3" + } +} diff --git a/prettier.config.js b/prettier.config.js new file mode 100644 index 0000000..b54afba --- /dev/null +++ b/prettier.config.js @@ -0,0 +1,8 @@ +// prettier.config.js +export default { + trailingComma: 'es5', + tabWidth: 2, + semi: false, // Crucial: Sets semicolon removal. + useTabs: true, + singleQuote: true, +}