Initial commit: Meat Monster TypeScript Fullstack

This commit is contained in:
Quintine committed 2026-05-20 13:25:41 +10:00
commit eed1ef4266
41 files changed
+8319

No files matched your search

+5
View File
@@ -0,0 +1,5 @@
node_modules
# Keep environment variables out of version control
.env
/src/generated/prisma
+2869
View File
File diff suppressed because it is too large. Load diff
+29
View File
@@ -0,0 +1,29 @@
{
"name": "backend",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "node dist/server.js",
"dev": "ts-node-dev --respawn --transpile-only src/server.ts",
"build": "tsc"
},
"keywords": [],
"author": "",
"license": "ISC",
"type": "commonjs",
"dependencies": {
"@prisma/client": "^7.8.0",
"cors": "^2.8.6",
"dotenv": "^17.4.2",
"express": "^5.2.1"
},
"devDependencies": {
"@types/cors": "^2.8.19",
"@types/express": "^5.0.6",
"@types/node": "^25.9.1",
"prisma": "^7.8.0",
"ts-node-dev": "^2.0.0",
"typescript": "^6.0.3"
}
}
+14
View File
@@ -0,0 +1,14 @@
// This file was generated by Prisma, and assumes you have installed the following:
// npm install --save-dev prisma dotenv
import "dotenv/config";
import { defineConfig } from "prisma/config";
export default defineConfig({
schema: "prisma/schema.prisma",
migrations: {
path: "prisma/migrations",
},
datasource: {
url: process.env["DATABASE_URL"],
},
});
@@ -0,0 +1,31 @@
-- CreateTable
CREATE TABLE "StockItem" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"name" TEXT NOT NULL,
"category" TEXT NOT NULL,
"price" REAL NOT NULL,
"unit" TEXT NOT NULL DEFAULT 'kg',
"available" BOOLEAN NOT NULL DEFAULT true,
"description" TEXT NOT NULL,
"bulk1Threshold" REAL NOT NULL DEFAULT 0,
"bulk1Price" REAL NOT NULL DEFAULT 0,
"bulk2Threshold" REAL NOT NULL DEFAULT 0,
"bulk2Price" REAL NOT NULL DEFAULT 0
);
-- CreateTable
CREATE TABLE "Order" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"name" TEXT NOT NULL,
"phone" TEXT NOT NULL,
"totalWeight" REAL NOT NULL,
"estimatedTotal" REAL NOT NULL,
"timestamp" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"items" TEXT NOT NULL
);
-- CreateTable
CREATE TABLE "Config" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"adminCode" TEXT NOT NULL DEFAULT '1234'
);
@@ -0,0 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (e.g., Git)
provider = "sqlite"
+39
View File
@@ -0,0 +1,39 @@
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "sqlite"
}
model StockItem {
id Int @id @default(autoincrement())
name String
category String
price Float
unit String @default("kg")
available Boolean @default(true)
description String
bulk1Threshold Float @default(0)
bulk1Price Float @default(0)
bulk2Threshold Float @default(0)
bulk2Price Float @default(0)
}
model Order {
id Int @id @default(autoincrement())
name String
phone String
totalWeight Float
estimatedTotal Float
timestamp DateTime @default(now())
items String // Storing items as a JSON string for simplicity with SQLite
}
model Config {
id Int @id @default(autoincrement())
adminCode String @default("1234")
}
+169
View File
@@ -0,0 +1,169 @@
import express from 'express';
import cors from 'cors';
import { PrismaClient } from '@prisma/client';
import path from 'path';
const prisma = new PrismaClient();
const app = express();
const PORT = process.env.PORT || 3000;
app.use(cors());
app.use(express.json());
// API Routes
// Fetch all data (Stock, Orders, Config)
app.get('/api/data', async (req, res) => {
try {
const stock = await prisma.stockItem.findMany();
const orders = await prisma.order.findMany();
const config = await prisma.config.findFirst();
// Parse order items from string to JSON
const parsedOrders = orders.map(order => ({
...order,
items: JSON.parse(order.items)
}));
res.json({ stock, orders: parsedOrders, config });
} catch (error) {
console.error(error);
res.status(500).json({ error: 'Failed to fetch data' });
}
});
// Submit a new order
app.post('/api/orders', async (req, res) => {
try {
const { name, phone, items, totalWeight, estimatedTotal } = req.body;
const order = await prisma.order.create({
data: {
name,
phone,
totalWeight,
estimatedTotal,
items: JSON.stringify(items)
}
});
res.json(order);
} catch (error) {
console.error(error);
res.status(500).json({ error: 'Failed to submit order' });
}
});
// Delete an order
app.delete('/api/orders/:id', async (req, res) => {
try {
const { id } = req.params;
await prisma.order.delete({ where: { id: parseInt(id) } });
res.sendStatus(200);
} catch (error) {
console.error(error);
res.status(500).json({ error: 'Failed to delete order' });
}
});
// Clear all orders
app.delete('/api/orders', async (req, res) => {
try {
await prisma.order.deleteMany();
res.sendStatus(200);
} catch (error) {
console.error(error);
res.status(500).json({ error: 'Failed to clear orders' });
}
});
// Update or Create Stock Item
app.post('/api/stock', async (req, res) => {
try {
const item = req.body;
if (item.id) {
const updated = await prisma.stockItem.update({
where: { id: item.id },
data: {
...item,
id: undefined // Don't update ID
}
});
res.json(updated);
} else {
const created = await prisma.stockItem.create({ data: item });
res.json(created);
}
} catch (error) {
console.error(error);
res.status(500).json({ error: 'Failed to update stock' });
}
});
// Delete Stock Item
app.delete('/api/stock/:id', async (req, res) => {
try {
const { id } = req.params;
await prisma.stockItem.delete({ where: { id: parseInt(id) } });
res.sendStatus(200);
} catch (error) {
console.error(error);
res.status(500).json({ error: 'Failed to delete stock item' });
}
});
// Update Config
app.post('/api/config', async (req, res) => {
try {
const { adminCode } = req.body;
const config = await prisma.config.findFirst();
if (config) {
await prisma.config.update({
where: { id: config.id },
data: { adminCode }
});
} else {
await prisma.config.create({ data: { adminCode } });
}
res.sendStatus(200);
} catch (error) {
console.error(error);
res.status(500).json({ error: 'Failed to update config' });
}
});
// Reset Database to defaults
app.post('/api/reset', async (req, res) => {
try {
await prisma.order.deleteMany();
await prisma.stockItem.deleteMany();
await prisma.config.deleteMany();
// Seed default admin code
await prisma.config.create({ data: { adminCode: '1234' } });
// Seed some default stock items
await prisma.stockItem.createMany({
data: [
{ name: 'Beef Brisket', category: 'Beef', price: 45, unit: 'kg', description: 'Low and slow smoked brisket', available: true },
{ name: 'Pork Shoulder', category: 'Pork', price: 35, unit: 'kg', description: 'Pulled pork perfection', available: true }
]
});
res.sendStatus(200);
} catch (error) {
console.error(error);
res.status(500).json({ error: 'Failed to reset database' });
}
});
// Serve Static Files in Production
const frontendPath = path.join(__dirname, '../../frontend/dist');
app.use(express.static(frontendPath));
app.get('*', (req, res) => {
if (req.path.startsWith('/api')) return;
res.sendFile(path.join(frontendPath, 'index.html'));
});
app.listen(PORT, () => {
console.log(`Server is running on http://localhost:${PORT}`);
});
+46
View File
@@ -0,0 +1,46 @@
{
// Visit https://aka.ms/tsconfig to read more about this file
"compilerOptions": {
// File Layout
"rootDir": "src",
"outDir": "dist",
// Environment Settings
// See also https://aka.ms/tsconfig/module
"module": "commonjs",
"target": "es2020",
"types": [],
// For nodejs:
// "lib": ["esnext"],
// "types": ["node"],
// and npm install -D @types/node
// Other Outputs
"sourceMap": true,
"declaration": true,
"declarationMap": true,
// Stricter Typechecking Options
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
// Style Options
// "noImplicitReturns": true,
// "noImplicitOverride": true,
// "noUnusedLocals": true,
// "noUnusedParameters": true,
// "noFallthroughCasesInSwitch": true,
// "noPropertyAccessFromIndexSignature": true,
// Recommended Options
"strict": true,
"jsx": "react-jsx",
"verbatimModuleSyntax": true,
"isolatedModules": true,
"noUncheckedSideEffectImports": true,
"moduleDetection": "force",
"skipLibCheck": true,
"esModuleInterop": true,
}
}