Compare commits

..

2 Commits

Author SHA1 Message Date
joseph.nelson4456 6942a2de8f added some base code to start doing work 2026-05-10 13:20:08 -07:00
joseph.nelson4456 7ca5c4e574 adding package.json file 2026-05-10 11:28:25 -07:00
29 changed files with 47 additions and 1425 deletions
-45
View File
@@ -1,45 +0,0 @@
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 -f {} || true
docker images --format "{{.Repository}}:{{.Tag}}" | grep "tcg-collectors-server" | xargs -I {} docker rmi -f {} || true
- name: Build and Push Image
run: |
docker build \
--build-arg DATABASE_URL=${{ secrets.DATABASE_URL }} \
--build-arg DB_HOST=${{ secrets.DB_HOST }} \
--build-arg DB_USERNAME=${{ secrets.DB_USERNAME }} \
--build-arg DB_PASSWORD=${{ secrets.DB_PASSWORD }} \
--build-arg DB_PORT=${{ secrets.DB_PORT }} \
--build-arg DB_DATABASE=${{ secrets.DB_DATABASE }} \
--build-arg JWT_SECRET=${{ secrets.JWT_SECRET }} \
-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
-26
View File
@@ -1,26 +0,0 @@
name: Test Workflow
on:
pull_request:
branches:
- main
jobs:
test-and-lint:
# Use the label matching your registered runner
runs-on: ubuntu-latest
# Or specify a custom image directly if your runner supports it
container:
image: node:24-alpine
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Install dependencies
run: npm install --force
- name: Run linter
run: npm run lint
- name: Run tests
run: npm test
-30
View File
@@ -1,30 +0,0 @@
FROM node:24-alpine
ARG DATABASE_URL
ARG DB_HOST
ARG DB_USERNAME
ARG DB_PASSWORD
ARG DB_PORT
ARG DB_DATABASE
ARG JWT_SECRET
ENV DATABASE_URL=${DATABASE_URL}
ENV DB_HOST=${DB_HOST}
ENV DB_USERNAME=${DB_USERNAME}
ENV DB_PASSWORD=${DB_PASSWORD}
ENV DB_PORT=${DB_PORT}
ENV DB_DATABASE=${DB_DATABASE}
ENV JWT_SECRET=${JWT_SECRET}
WORKDIR /app
COPY . .
RUN npm install --force && \
echo "DATABASE_URL=${DATABASE_URL}" >> .env && \
echo "DB_HOST=${DB_HOST}" >> .env && \
echo "DB_USERNAME=${DB_USERNAME}" >> .env && \
echo "DB_PASSWORD=${DB_PASSWORD}" >> .env && \
echo "DB_PORT=${DB_PORT}" >> .env && \
echo "DB_DATABASE=${DB_DATABASE}" >> .env && \
echo "JWT_SECRET=${JWT_SECRET}" >> .env
CMD ["npm", "run", "docker:start"]
+30
View File
@@ -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}`)
})
-3
View File
@@ -1,3 +0,0 @@
export default {
presets: [['@babel/preset-env', { targets: { node: 'current' } }]],
}
+10
View File
@@ -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,
},
}
+7 -21
View File
@@ -4,14 +4,13 @@
"description": "A basic Express application with PostgreSQL integration.",
"main": "app.js",
"scripts": {
"dev": "nodemon src/app.js",
"start": "NODE_ENV=production node src/app.js",
"docker:start": "NODE_ENV=production node --env-file=.env src/app.js",
"start": "node app.js",
"dev": "nodemon app.js",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"migrate": "node-pg-migrate -m ./src/migrations",
"test": "jest --forceExit"
"format": "prettier --config prettier.config.js --write ."
},
"type": "module",
"keywords": [
"express",
"node.js",
@@ -22,30 +21,17 @@
"author": "Your Name",
"license": "MIT",
"dependencies": {
"bcryptjs": "^3.0.3",
"body-parser": "^2.2.2",
"cors": "^2.8.6",
"csv-parser": "^3.0.0",
"dotenv": "^16.0.3",
"express": "^4.22.2",
"express-validator": "^7.3.2",
"jsonwebtoken": "^9.0.3",
"node-pg": "^1.0.1",
"p-limit": "^7.3.0",
"pg": "^8.20.0"
"express": "^4.18.2",
"pg": "^8.8.0"
},
"devDependencies": {
"@babel/core": "^7.29.0",
"@babel/preset-env": "^7.29.5",
"babel-jest": "^30.4.1",
"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",
"supertest": "^7.2.2"
"prettier": "^3.8.3"
}
}
-48
View File
@@ -1,48 +0,0 @@
import { loadEnvFile } from 'node:process' // load envs asap
import express from 'express'
import path from 'path'
import cors from 'cors'
import healthCheckRoutes from './routes/healthcheck/index.js'
import authRoutes from './routes/auth/index.js'
import userRoutes from './routes/users/index.js'
import roleRoutes from './routes/roles/index.js'
import locationRoutes from './routes/locations/index.js'
import imageRoutes from './routes/images/index.js'
import collectionRoutes from './routes/collections/index.js'
import setRoutes from './routes/sets/index.js'
import itemRoutes from './routes/items/index.js'
if (process.env.NODE_ENV !== 'test') {
loadEnvFile()
}
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
app.use(cors())
// Mount Routes
app.use('/healthcheck', healthCheckRoutes)
app.use('/auth', authRoutes)
app.use('/users', userRoutes)
app.use('/roles', roleRoutes)
app.use('/locations', locationRoutes)
app.use('/images', imageRoutes)
app.use('/collections', collectionRoutes)
app.use('/sets', setRoutes)
app.use('/items', itemRoutes)
// 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}`)
})
export default app
-16
View File
@@ -1,16 +0,0 @@
// config/index.js
import { Pool } from 'pg'
const database = {
host: process.env.DB_HOST,
user: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD,
database: process.env.DB_DATABASE,
port: parseInt(process.env.DB_PORT, 10),
}
export const pool = new Pool(database)
export const jwtEnv = {
secret: process.env.JWT_SECRET,
}
-17
View File
@@ -1,17 +0,0 @@
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;
`)
}
@@ -1,18 +0,0 @@
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;
`)
}
-25
View File
@@ -1,25 +0,0 @@
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;
`)
}
@@ -1,16 +0,0 @@
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;
`)
}
-22
View File
@@ -1,22 +0,0 @@
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;
`)
}
-16
View File
@@ -1,16 +0,0 @@
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;
`)
}
-20
View File
@@ -1,20 +0,0 @@
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;
`)
}
@@ -1,18 +0,0 @@
export const up = (pgm) => {
pgm.sql(`
ALTER TABLE "items"
ADD COLUMN "set_id" UUID,
ADD CONSTRAINT "fk_set_id"
FOREIGN KEY ("set_id")
REFERENCES "sets"("id")
ON DELETE CASCADE;
`)
}
export const down = (pgm) => {
pgm.sql(`
ALTER TABLE "items"
DROP COLUMN "set_id",
DROP CONSTRAINT "fk_set_id";
`)
}
@@ -1,29 +0,0 @@
export const up = (pgm) => {
pgm.sql(`
ALTER TABLE collections
ADD COLUMN "user_id" UUID,
ADD CONSTRAINT fk_collections_user_id
FOREIGN KEY (user_id)
REFERENCES users(id) ON DELETE CASCADE ON UPDATE CASCADE;
`)
pgm.sql(`
ALTER TABLE sets
ADD COLUMN "user_id" UUID,
ADD CONSTRAINT fk_sets_user_id
FOREIGN KEY (user_id)
REFERENCES users(id) ON DELETE CASCADE ON UPDATE CASCADE;
`)
}
export const down = (pgm) => {
pgm.sql(`
ALTER TABLE collections
DROP COLUMN "user_id",
DROP CONSTRAINT fk_collections_user_id;
`)
pgm.sql(`
ALTER TABLE sets
DROP COLUMN "user_id",
DROP CONSTRAINT fk_sets_user_id;
`)
}
@@ -1,75 +0,0 @@
export const up = (pgm) => {
pgm.sql(`
ALTER TABLE roles
ALTER COLUMN id DROP DEFAULT,
ALTER COLUMN id SET DATA TYPE UUID USING (gen_random_uuid()),
ALTER COLUMN id SET DEFAULT gen_random_uuid();
`)
pgm.sql(`
ALTER TABLE images
ALTER COLUMN id DROP DEFAULT,
ALTER COLUMN id SET DATA TYPE UUID USING (gen_random_uuid()),
ALTER COLUMN id SET DEFAULT gen_random_uuid();
`)
pgm.sql(`
ALTER TABLE collections
ALTER COLUMN id DROP DEFAULT,
ALTER COLUMN id SET DATA TYPE UUID USING (gen_random_uuid()),
ALTER COLUMN id SET DEFAULT gen_random_uuid();
`)
pgm.sql(`
ALTER TABLE items
ALTER COLUMN id DROP DEFAULT,
ALTER COLUMN id SET DATA TYPE UUID USING (gen_random_uuid()),
ALTER COLUMN id SET DEFAULT gen_random_uuid();
`)
pgm.sql(`
ALTER TABLE locations
ALTER COLUMN id DROP DEFAULT,
ALTER COLUMN id SET DATA TYPE UUID USING (gen_random_uuid()),
ALTER COLUMN id SET DEFAULT gen_random_uuid();
`)
pgm.sql(`
ALTER TABLE sets
ALTER COLUMN id DROP DEFAULT,
ALTER COLUMN id SET DATA TYPE UUID USING (gen_random_uuid()),
ALTER COLUMN id SET DEFAULT gen_random_uuid();
`)
pgm.sql(`
ALTER TABLE users
ALTER COLUMN id DROP DEFAULT,
ALTER COLUMN id SET DATA TYPE UUID USING (gen_random_uuid()),
ALTER COLUMN id SET DEFAULT gen_random_uuid();
`)
}
export const down = (pmg) => {
pgm.sql(`
ALTER TABLE roles
ALTER COLUMN id DROP DEFAULT;
`)
pgm.sql(`
ALTER TABLE images
ALTER COLUMN id DROP DEFAULT;
`)
pgm.sql(`
ALTER TABLE collections
ALTER COLUMN id DROP DEFAULT;
`)
pgm.sql(`
ALTER TABLE items
ALTER COLUMN id DROP DEFAULT;
`)
pgm.sql(`
ALTER TABLE locations
ALTER COLUMN id DROP DEFAULT;
`)
pgm.sql(`
ALTER TABLE sets
ALTER COLUMN id DROP DEFAULT;
`)
pgm.sql(`
ALTER TABLE users
ALTER COLUMN id DROP DEFAULT;
`)
}
-70
View File
@@ -1,70 +0,0 @@
import express from 'express'
import jwt from 'jsonwebtoken'
import bcrypt from 'bcryptjs'
import { pool, jwtEnv } from '../../config/index.js'
const router = express.Router()
// Route to handle login and issue JWT token
router.post('/login', async (req, res) => {
const { username, password } = req.body
try {
const client = await pool.connect()
const queryText = 'SELECT * FROM users WHERE username = $1'
const result = await client.query(queryText, [username])
if (!result.rows.length) {
return res.status(401).json({ message: 'Invalid credentials' })
}
const user = result.rows[0]
// Compare passwords
const passwordMatch = await bcrypt.compare(password, user.password)
if (!passwordMatch) {
return res.status(401).json({ message: 'Invalid credentials' })
}
// Issue JWT token
const token = jwt.sign(
{ id: user.id, username: user.username },
jwtEnv.secret,
{ expiresIn: '1h' }
)
res.json({ token })
} catch (error) {
console.error(error)
res.status(500).json({ message: 'Internal server error' })
}
})
// Route to refresh JWT token
router.post('/refresh-token', async (req, res) => {
const { refreshToken } = req.body
if (!refreshToken) {
return res.status(401).json({ message: 'Refresh token is required' })
}
try {
// Verify the refresh token
const decoded = jwt.verify(refreshToken, jwtEnv.secret)
// Create a new access token (JWT)
const token = jwt.sign(
{ id: decoded.id, username: decoded.username },
jwtEnv.secret,
{ expiresIn: '1h' }
)
res.json({ token })
} catch (error) {
console.error(error)
res.status(403).json({ message: 'Invalid refresh token' })
}
})
export default router
-106
View File
@@ -1,106 +0,0 @@
import express from 'express'
import { pool } from '../../config/index.js'
import { check, validationResult } from 'express-validator'
const router = express.Router()
// Get all collections
router.get('/', async (req, res) => {
try {
const result = await pool.query('SELECT * FROM collections')
res.json(result.rows)
} catch (error) {
console.error(error)
res.status(500).send('Server error')
}
})
// Get a single collection by id
router.get('/:id', async (req, res) => {
const { id } = req.params
try {
const result = await pool.query('SELECT * FROM collections WHERE id = $1', [
id,
])
if (result.rows.length === 0) {
return res.status(404).send('Collection not found')
}
res.json(result.rows[0])
} catch (error) {
console.error(error)
res.status(500).send('Server error')
}
})
// Create a new collection
router.post(
'/',
[
check('name', 'Name is required').not().isEmpty(),
check('image_id', 'Image id is required').not().isEmpty(),
],
async (req, res) => {
const errors = validationResult(req)
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() })
}
const { name, image_id } = req.body
try {
const result = await pool.query(
'INSERT INTO collections (name, image_id, created_at, updated_at) VALUES ($1, $2, NOW(), NOW()) RETURNING *',
[name, image_id]
)
res.status(201).json(result.rows[0])
} catch (error) {
console.error(error)
res.status(500).send('Server error')
}
}
)
// Update an existing collection
router.put(
'/:id',
[
check('name', 'Name is required').not().isEmpty(),
check('image_id', 'Image id is required').not().isEmpty(),
],
async (req, res) => {
const errors = validationResult(req)
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() })
}
const { name, image_id } = req.body
const { id } = req.params
try {
await pool.query(
'UPDATE collections SET name = $1, image_id = $2, updated_at = NOW() WHERE id = $3',
[name, image_id, id]
)
res.send('Collection updated')
} catch (error) {
console.error(error)
res.status(500).send('Server error')
}
}
)
// Delete an existing collection
router.delete('/:id', async (req, res) => {
const { id } = req.params
try {
await pool.query('DELETE FROM collections WHERE id = $1', [id])
res.send('Collection deleted')
} catch (error) {
console.error(error)
res.status(500).send('Server error')
}
})
export default router
-7
View File
@@ -1,7 +0,0 @@
import express from 'express'
const router = express.Router()
router.get('/', (req, res) => res.json({ status: 'UP' }))
export default router
-19
View File
@@ -1,19 +0,0 @@
// src/routes/healthcheck/test.js
import request from 'supertest'
import app from '../../app'
describe('Health Check Endpoint', () => {
it('should respond with status up', async () => {
const response = await request(app).get('/healthcheck')
expect(response.status).toBe(200)
expect(response.body).toEqual({ status: 'UP' })
})
it('should return a JSON response', async () => {
const response = await request(app).get('/healthcheck')
expect(response.type).toBe('application/json')
})
})
-124
View File
@@ -1,124 +0,0 @@
import express from 'express'
import { pool } from '../../config/index.js'
import { check, validationResult } from 'express-validator'
const router = express.Router()
// CREATE AN IMAGE
router.post(
'/',
[check('file').isString(), check('image').isBase64()],
async (req, res) => {
try {
const errors = validationResult(req)
if (!errors.isEmpty())
return res.status(400).json({ errors: errors.array() })
const { file, image } = req.body
const client = await pool.connect()
const result = await client.query(
'INSERT INTO images (file, image, created_at, updated_at) VALUES ($1, $2, NOW(), NOW()) RETURNING *',
[file, image]
)
client.release()
return res.status(201).json(result.rows[0])
} catch (err) {
console.error(err.stack)
return res.status(500).json({ error: 'Server error' })
}
}
)
// READ AN IMAGE
router.get('/:id', async (req, res) => {
try {
const { id } = req.params
const client = await pool.connect()
const result = await client.query('SELECT * FROM images WHERE id = $1', [
id,
])
client.release()
if (result.rows.length === 0)
return res.status(404).json({ error: 'Image not found' })
return res.status(200).json(result.rows[0])
} catch (err) {
console.error(err.stack)
return res.status(500).json({ error: 'Server error' })
}
})
// UPDATE AN IMAGE
router.put(
'/:id',
[check('file').optional().isString(), check('image').optional().isBase64()],
async (req, res) => {
try {
const errors = validationResult(req)
if (!errors.isEmpty())
return res.status(400).json({ errors: errors.array() })
const { id } = req.params
const { file, image } = req.body
const client = await pool.connect()
let query = 'UPDATE images SET'
const values = []
if (file) {
query += ' file = $1,'
values.push(file)
}
if (image) {
query += ' image = $2,'
values.push(image)
}
// Remove trailing comma and space from query string
query = query.slice(0, -1)
query += ', updated_at = NOW() WHERE id = $3 RETURNING *'
values.push(id)
const result = await client.query(query, values)
client.release()
if (result.rows.length === 0)
return res.status(404).json({ error: 'Image not found' })
return res.status(200).json(result.rows[0])
} catch (err) {
console.error(err.stack)
return res.status(500).json({ error: 'Server error' })
}
}
)
// DELETE AN IMAGE
router.delete('/:id', async (req, res) => {
try {
const { id } = req.params
const client = await pool.connect()
const result = await client.query(
'DELETE FROM images WHERE id = $1 RETURNING *',
[id]
)
client.release()
if (result.rows.length === 0)
return res.status(404).json({ error: 'Image not found' })
return res.status(200).json(result.rows[0])
} catch (err) {
console.error(err.stack)
return res.status(500).json({ error: 'Server error' })
}
})
export default router
-218
View File
@@ -1,218 +0,0 @@
import express from 'express'
import { pool } from '../../config/index.js'
import { check, validationResult } from 'express-validator'
const router = express.Router()
// Get all items
router.get('/', async (req, res) => {
const client = await pool.connect()
try {
const result = await client.query('SELECT * FROM items')
res.json(result.rows)
} finally {
client.release()
}
})
// Get a single item by id
router.get('/:id', async (req, res) => {
const { id } = req.params
const client = await pool.connect()
try {
const result = await client.query('SELECT * FROM items WHERE id = $1', [id])
if (result.rows.length === 0) {
return res.status(404).json({ message: 'Item not found' })
}
res.json(result.rows[0])
} finally {
client.release()
}
})
// Create a new item
router.post(
'/',
[
check('collection_id').not().isEmpty(),
check('image_id').not().isEmpty(),
check('productId').not().isEmpty(),
check('name').not().isEmpty(),
check('cleanName').not().isEmpty(),
check('extCardText').not().isEmpty(),
check('marketPrice').not().isEmpty(),
check('extRarity').not().isEmpty(),
check('set_id').not().isEmpty(), // Add validation for set_id
],
async (req, res) => {
const errors = validationResult(req)
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() })
}
const {
collection_id,
image_id,
productId,
name,
cleanName,
extCardText,
marketPrice,
extRarity,
set_id,
} = req.body
const client = await pool.connect()
try {
const result = await client.query(
'INSERT INTO items (collection_id, image_id, productId, name, cleanName, extCardText, marketPrice, extRarity, set_id, created_at, updated_at) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, NOW(), NOW()) RETURNING *',
[
collection_id,
image_id,
productId,
name,
cleanName,
extCardText,
marketPrice,
extRarity,
set_id,
]
)
res.status(201).json(result.rows[0])
} finally {
client.release()
}
}
)
// Update an existing item
router.put('/:id', async (req, res) => {
const { id } = req.params
const updates = {}
if (req.body.collection_id) updates.collection_id = req.body.collection_id
if (req.body.image_id) updates.image_id = req.body.image_id
if (req.body.productId) updates.productId = req.body.productId
if (req.body.name) updates.name = req.body.name
if (req.body.cleanName) updates.cleanName = req.body.cleanName
if (req.body.extCardText) updates.extCardText = req.body.extCardText
if (req.body.marketPrice) updates.marketPrice = req.body.marketPrice
if (req.body.extRarity) updates.extRarity = req.body.extRarity
const client = await pool.connect()
try {
if (Object.keys(updates).length > 0) {
const updateFields = Object.keys(updates)
.map((key) => `${key} = $${Object.keys(updates).indexOf(key) + 2}`)
.join(', ')
updates.updated_at = 'NOW()'
const result = await client.query(
`UPDATE items SET ${updateFields}, updated_at = $${Object.keys(updates).length + 1} WHERE id = $1 RETURNING *`,
[id, ...Object.values(updates)]
)
return res.json(result.rows[0])
}
if (result.rows.length === 0) {
return res.status(404).json({ message: 'Item not found' })
} else {
return res.status(204).json()
}
} finally {
client.release()
}
})
// Delete an item
router.delete('/:id', async (req, res) => {
const { id } = req.params
const client = await pool.connect()
try {
const result = await client.query('DELETE FROM items WHERE id = $1', [id])
if (result.rows.length === 0) {
return res.status(404).json({ message: 'Item not found' })
}
res.json({ message: 'Item deleted' })
} finally {
client.release()
}
})
// Create a new item from CSV
router.post(
'/csv',
[
check('collection_id').not().isEmpty(),
check('set_id').not().isEmpty(), // Add validation for set_id
],
async (req, res) => {
const errors = validationResult(req)
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() })
}
const { collection_id, set_id } = req.body
const csvBuffer = req.files.file.buffer.toString('utf8')
const csvLines = csvBuffer
.split('\n')
.filter((line) => line.trim().length > 0)
const client = await pool.connect()
try {
// Insert each row into the items table
for (const line of csvLines) {
const [
productId,
name,
cleanName,
imageUrl,
categoryId,
groupId,
url,
modifiedOn,
imageCount,
extCardText,
extUPC,
lowPrice,
midPrice,
highPrice,
marketPrice,
directLowPrice,
subTypeName,
extNumber,
extRarity,
extCardType,
extHP,
extStage,
extAttack1,
extWeakness,
extRetreatCost,
extAttack2,
extResistance,
] = line.split(',').map((value) => value.trim())
// Fetch the image from imageUrl
const imageResponse = await fetch(imageUrl)
const imageBuffer = await imageResponse.buffer()
// Insert the image into the images table
const insertImageResult = await client.query(
'INSERT INTO images (file, image) VALUES ($1::TEXT, $2::BYTEA) RETURNING id',
[`${productId}.jpg`, imageBuffer]
)
await client.query(
'INSERT INTO items (collection_id, image_id, productId, name, cleanName, extCardText, marketPrice, extRarity, set_id, created_at, updated_at) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, NOW(), NOW())',
[
collection_id,
insertImageResult.rows[0].id,
productId,
name,
cleanName,
extCardText,
marketPrice,
extRarity,
set_id,
]
)
}
res.status(201).json({ message: 'Items created' })
} finally {
client.release()
}
}
)
export default router
-90
View File
@@ -1,90 +0,0 @@
import express from 'express'
import { pool } from '../../config/index.js'
import { check, validationResult } from 'express-validator'
const router = express.Router()
// Create a new location
router.post(
'/',
[check('name', 'Name is required').not().isEmpty()],
async (req, res) => {
const errors = validationResult(req)
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() })
}
try {
const { name } = req.body
await pool.query(
'INSERT INTO locations (name, created_at, updated_at) VALUES ($1, NOW(), NOW())',
[name]
)
res.send({ message: 'Location created' })
} catch (err) {
console.error(err.message)
res.status(500).send('Server error')
}
}
)
// Get all locations
router.get('/', async (req, res) => {
try {
const result = await pool.query('SELECT * FROM locations')
res.send(result.rows)
} catch (err) {
console.error(err.message)
res.status(500).send('Server error')
}
})
// Get a single location by ID
router.get('/:id', async (req, res) => {
try {
const result = await pool.query('SELECT * FROM locations WHERE id = $1', [
req.params.id,
])
if (result.rows.length === 0)
return res.status(404).send('Location not found')
res.send(result.rows[0])
} catch (err) {
console.error(err.message)
res.status(500).send('Server error')
}
})
// Update a location
router.put(
'/:id',
[check('name', 'Name is required').not().isEmpty()],
async (req, res) => {
const errors = validationResult(req)
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() })
}
try {
const { name } = req.body
await pool.query(
'UPDATE locations SET name = $1, updated_at = NOW() WHERE id = $2',
[name, req.params.id]
)
res.send({ message: 'Location updated' })
} catch (err) {
console.error(err.message)
res.status(500).send('Server error')
}
}
)
// Delete a location
router.delete('/:id', async (req, res) => {
try {
await pool.query('DELETE FROM locations WHERE id = $1', [req.params.id])
res.send({ message: 'Location deleted' })
} catch (err) {
console.error(err.message)
res.status(500).send('Server error')
}
})
export default router
-88
View File
@@ -1,88 +0,0 @@
import express from 'express'
import { pool } from '../../config/index.js'
const router = express.Router()
// Create a new role
router.post('/', async (req, res) => {
const { name } = req.body
const client = await pool.connect()
try {
await client.query('BEGIN')
const result = await client.query(
'INSERT INTO roles (name, created_at, updated_at) VALUES ($1, NOW(), NOW()) RETURNING *',
[name]
)
await client.query('COMMIT')
res.status(201).json(result.rows[0])
} catch (err) {
await client.query('ROLLBACK')
res.status(500).json({ error: err.message })
}
})
// Get all roles
router.get('/', async (req, res) => {
try {
const result = await pool.query('SELECT * FROM roles')
res.json(result.rows)
} catch (err) {
res.status(500).json({ error: err.message })
}
})
// Get a single role by id
router.get('/:id', async (req, res) => {
const { id } = req.params
try {
const result = await pool.query('SELECT * FROM roles WHERE id = $1', [id])
if (result.rows.length === 0)
return res.status(404).json({ error: 'Role not found' })
res.json(result.rows[0])
} catch (err) {
res.status(500).json({ error: err.message })
}
})
// Update a role by id
router.put('/:id', async (req, res) => {
const { id } = req.params
const { name } = req.body
const client = await pool.connect()
try {
await client.query('BEGIN')
const result = await client.query(
'UPDATE roles SET name = $1, updated_at = NOW() WHERE id = $2 RETURNING *',
[name, id]
)
if (result.rows.length === 0)
return res.status(404).json({ error: 'Role not found' })
await client.query('COMMIT')
res.json(result.rows[0])
} catch (err) {
await client.query('ROLLBACK')
res.status(500).json({ error: err.message })
}
})
// Delete a role by id
router.delete('/:id', async (req, res) => {
const { id } = req.params
const client = await pool.connect()
try {
await client.query('BEGIN')
const result = await client.query('DELETE FROM roles WHERE id = $1', [id])
if (result.rows.length === 0)
return res.status(404).json({ error: 'Role not found' })
await client.query('COMMIT')
res.json(result.rows[0])
} catch (err) {
await client.query('ROLLBACK')
res.status(500).json({ error: err.message })
}
})
export default router
-107
View File
@@ -1,107 +0,0 @@
import express from 'express'
import { pool } from '../../config/index.js'
import { check, validationResult } from 'express-validator'
const router = express.Router()
// Get all sets
router.get('/', async (req, res) => {
try {
const result = await pool.query('SELECT * FROM sets')
res.json(result.rows)
} catch (error) {
res.status(500).json({ message: error.message })
}
})
// Create a new set
router.post(
'/',
[
check('collection_id').not().isEmpty(),
check('name').not().isEmpty(),
check('location_id').not().isEmpty(),
check('image_id').not().isEmpty(),
],
async (req, res) => {
const errors = validationResult(req)
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() })
}
try {
const result = await pool.query(
'INSERT INTO sets (collection_id, name, location_id, image_id, created_at, updated_at) VALUES ($1, $2, $3, $4, NOW(), NOW()) RETURNING *',
[
req.body.collection_id,
req.body.name,
req.body.location_id,
req.body.image_id,
]
)
res.status(201).json(result.rows[0])
} catch (error) {
res.status(500).json({ message: error.message })
}
}
)
// Get a single set by ID
router.get('/:id', async (req, res) => {
try {
const result = await pool.query('SELECT * FROM sets WHERE id = $1', [
req.params.id,
])
if (result.rows.length === 0) {
return res.status(404).json({ message: 'Set not found' })
}
res.json(result.rows[0])
} catch (error) {
res.status(500).json({ message: error.message })
}
})
// Update a set by ID
router.put('/:id', [check('name').not().isEmpty()], async (req, res) => {
const errors = validationResult(req)
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() })
}
try {
const result = await pool.query(
'UPDATE sets SET name = $1, updated_at = NOW() WHERE id = $2 RETURNING *',
[req.body.name, req.params.id]
)
if (result.rows.length === 0) {
return res.status(404).json({ message: 'Set not found' })
}
res.json(result.rows[0])
} catch (error) {
res.status(500).json({ message: error.message })
}
})
// Delete a set by ID
router.delete('/:id', async (req, res) => {
try {
const result = await pool.query(
'DELETE FROM sets WHERE id = $1 RETURNING *',
[req.params.id]
)
if (result.rows.length === 0) {
return res.status(404).json({ message: 'Set not found' })
}
res.json(result.rows[0])
} catch (error) {
res.status(500).json({ message: error.message })
}
})
export default router
-151
View File
@@ -1,151 +0,0 @@
import express from 'express'
import bcrypt from 'bcryptjs'
import { pool } from '../../config/index.js'
const router = express.Router()
/**
* Get all users.
*/
router.get('/', async (req, res) => {
const { rows } = await pool.query('SELECT * FROM users')
res.json(rows)
})
/**
* Create a new user.
*
* @param {string} name - The user's name
* @param {string} email - The user's email address
* @param {string} password - The user's password (must be hashed)
* @param {string} role_name - The name of the role to assign to the user
*/
router.post('/', async (req, res) => {
const { username, email, password, role_name } = req.body
if (!password) {
return res.status(400).send('Password is required')
}
try {
// Hash the password using bcrypt
const hashedPassword = await bcrypt.hash(password, 10)
// Query for the UUID of the role by name
const roleResult = await pool.query(
'SELECT id FROM roles WHERE name = $1',
[role_name]
)
if (roleResult.rows.length === 0) {
throw new Error(`Role ${role_name} not found`)
}
const roleId = roleResult.rows[0].id
// Insert the new user into the database
const result = await pool.query(
'INSERT INTO users (username, email, password, role_id, created_at, updated_at) VALUES ($1, $2, $3, $4, NOW(), NOW()) RETURNING *',
[username, email, hashedPassword, roleId]
)
// Return the newly created user
res.status(201).json(result.rows[0])
} catch (err) {
res.status(500).send(err)
}
})
/**
* Update an existing user.
*
* @param {number} id - The ID of the user to update
* @param {string} name - The new name for the user
* @param {string} email - The new email address for the user (optional)
* @param {string} password - The new password for the user (optional, must be hashed)
* @param {string} role_name - The name of the new role to assign to the user (optional)
*/
router.put('/:id', async (req, res) => {
const { id } = req.params
const { name, email, password, role_name } = req.body
const client = await pool.connect()
try {
await client.query('BEGIN')
// Query for the UUID of the role by name if provided
let roleId = null
if (role_name) {
const roleResult = await client.query(
'SELECT id FROM roles WHERE name = $1',
[role_name]
)
if (roleResult.rows.length === 0) {
throw new Error(`Role ${role_name} not found`)
}
roleId = roleResult.rows[0].id
}
// Update the user in the database
let queryText = 'UPDATE users SET name = $1'
const values = [name]
if (email) {
queryText += ', email = $2'
values.push(email)
}
if (password) {
queryText += ', password = $3'
values.push(await bcrypt.hash(password, 10))
}
if (roleId !== null) {
queryText += ', role_id = $4'
values.push(roleId)
}
queryText += ' , updated_at = NOW() WHERE id = $5 RETURNING *'
values.push(id)
const result = await client.query(queryText, values)
// Return the updated user
res.json(result.rows[0])
await client.query('COMMIT')
} catch (err) {
await client.query('ROLLBACK')
res.status(500).send(err)
} finally {
client.release()
}
})
/**
* Delete an existing user.
*
* @param {number} id - The ID of the user to delete
*/
router.delete('/:id', async (req, res) => {
const { id } = req.params
const client = await pool.connect()
try {
await client.query('BEGIN')
// Delete the user from the database
const result = await client.query(
'DELETE FROM users WHERE id = $1 RETURNING *',
[id]
)
// Return the deleted user
res.json(result.rows[0])
await client.query('COMMIT')
} catch (err) {
await client.query('ROLLBACK')
res.status(500).send(err)
} finally {
client.release()
}
})
export default router