initial
This commit is contained in:
1
api/.eslintrc.cjs
Normal file
1
api/.eslintrc.cjs
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = { root: true, env: { node: true }, extends: ['eslint:recommended'] };
|
||||
3
api/.gitignore
vendored
Normal file
3
api/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
node_modules
|
||||
.next
|
||||
styles/tailwind.build.css
|
||||
21
api/Dockerfile
Normal file
21
api/Dockerfile
Normal file
@@ -0,0 +1,21 @@
|
||||
FROM node:20-alpine AS deps
|
||||
WORKDIR /app
|
||||
COPY package.json .
|
||||
RUN npm install
|
||||
|
||||
FROM node:20-alpine AS builder
|
||||
WORKDIR /app
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
COPY . .
|
||||
RUN npx prisma generate && npm run build
|
||||
|
||||
FROM node:20-alpine AS runner
|
||||
RUN apk add --no-cache openssl
|
||||
ENV NODE_ENV=production
|
||||
WORKDIR /app
|
||||
COPY --from=builder /app/node_modules ./node_modules
|
||||
COPY --from=builder /app/dist ./dist
|
||||
COPY --from=builder /app/package.json ./package.json
|
||||
COPY --from=builder /app/prisma ./prisma
|
||||
EXPOSE 4000
|
||||
CMD ["node", "dist/src/index.js"]
|
||||
31
api/package.json
Normal file
31
api/package.json
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"name": "ci-electrical-api",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "ts-node-dev --respawn --transpile-only src/index.ts",
|
||||
"build": "tsc -p tsconfig.json",
|
||||
"start": "node dist/index.js",
|
||||
"lint": "eslint .",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"seed": "node --loader ts-node/esm prisma/seed.ts",
|
||||
"prisma:generate": "prisma generate",
|
||||
"prisma:migrate": "prisma migrate dev"
|
||||
},
|
||||
"dependencies": {
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.4.5",
|
||||
"express": "^4.19.2",
|
||||
"zod": "3.23.8",
|
||||
"@prisma/client": "5.17.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/cors": "^2.8.17",
|
||||
"@types/express": "^4.17.21",
|
||||
"eslint": "^8.57.0",
|
||||
"typescript": "^5.5.4",
|
||||
"ts-node": "^10.9.2",
|
||||
"ts-node-dev": "^2.0.0",
|
||||
"prisma": "5.17.0"
|
||||
}
|
||||
}
|
||||
27
api/prisma/schema.prisma
Normal file
27
api/prisma/schema.prisma
Normal file
@@ -0,0 +1,27 @@
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
binaryTargets = ["native", "linux-musl-openssl-3.0.x"]
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
model Listing {
|
||||
id String @id @default(cuid())
|
||||
title String
|
||||
slug String @unique
|
||||
image String?
|
||||
summary String
|
||||
createdAt DateTime @default(now())
|
||||
}
|
||||
|
||||
model Testimonial {
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
area String
|
||||
text String
|
||||
rating Int @default(5)
|
||||
createdAt DateTime @default(now())
|
||||
}
|
||||
48
api/prisma/seed.ts
Normal file
48
api/prisma/seed.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function main() {
|
||||
await prisma.listing.upsert({
|
||||
where: { slug: 'retail-lighting-retrofit-south-side' },
|
||||
update: {},
|
||||
create: {
|
||||
title: 'Retail Lighting Retrofit — South Side',
|
||||
slug: 'retail-lighting-retrofit-south-side',
|
||||
image: '/images/project-1.jpg',
|
||||
summary: 'LED conversion for 5,000 sq ft retail space; 35% energy savings.'
|
||||
}
|
||||
});
|
||||
await prisma.listing.upsert({
|
||||
where: { slug: 'panel-upgrade-ocean-drive' },
|
||||
update: {},
|
||||
create: {
|
||||
title: 'Residential Panel Upgrade — Ocean Drive',
|
||||
slug: 'panel-upgrade-ocean-drive',
|
||||
image: '/images/project-2.jpg',
|
||||
summary: '100A → 200A service upgrade with AFCI breakers and EV-ready outlet.'
|
||||
}
|
||||
});
|
||||
await prisma.listing.upsert({
|
||||
where: { slug: 'office-buildout-downtown' },
|
||||
update: {},
|
||||
create: {
|
||||
title: 'Office Build-Out — Downtown',
|
||||
slug: 'office-buildout-downtown',
|
||||
image: '/images/project-3.jpg',
|
||||
summary: 'Complete tenant build-out: power distribution, LED lighting, data wiring.'
|
||||
}
|
||||
});
|
||||
|
||||
await prisma.testimonial.createMany({
|
||||
data: [
|
||||
{ name: 'Maria S.', area: 'Ocean Drive', text: 'Panel upgrade done fast. No more tripping breakers!', rating: 5 },
|
||||
{ name: 'David R.', area: 'Downtown', text: 'Office build-out finished on time. Great team.', rating: 5 },
|
||||
{ name: 'Jennifer L.', area: 'Flour Bluff', text: 'Emergency repair on Sunday. Reliable service.', rating: 5 }
|
||||
],
|
||||
skipDuplicates: true
|
||||
});
|
||||
|
||||
console.log('Seed complete');
|
||||
}
|
||||
|
||||
main().finally(() => prisma.$disconnect());
|
||||
5
api/src/env.ts
Normal file
5
api/src/env.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import 'dotenv/config';
|
||||
export const env = {
|
||||
PORT: Number(process.env.PORT || 4000),
|
||||
DATABASE_URL: process.env.DATABASE_URL || 'postgresql://postgres:postgres@localhost:5432/cielectrical?schema=public'
|
||||
};
|
||||
24
api/src/index.ts
Normal file
24
api/src/index.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import express from 'express';
|
||||
import cors from 'cors';
|
||||
import { env } from './env.js';
|
||||
import listings from './routes/listings.js';
|
||||
import { prisma } from './prisma.js';
|
||||
|
||||
const app = express();
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
|
||||
app.get('/health', async (_req, res) => {
|
||||
try {
|
||||
await prisma.$queryRaw`SELECT 1`;
|
||||
res.json({ ok: true });
|
||||
} catch (e) {
|
||||
res.status(500).json({ ok: false });
|
||||
}
|
||||
});
|
||||
|
||||
app.use('/listings', listings);
|
||||
|
||||
app.listen(env.PORT, () => {
|
||||
console.log(`API on :${env.PORT}`);
|
||||
});
|
||||
2
api/src/prisma.ts
Normal file
2
api/src/prisma.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
export const prisma = new PrismaClient();
|
||||
10
api/src/routes/listings.ts
Normal file
10
api/src/routes/listings.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { Router } from 'express';
|
||||
import { prisma } from '../prisma.js';
|
||||
const router = Router();
|
||||
|
||||
router.get('/', async (_req, res) => {
|
||||
const listings = await prisma.listing.findMany({ orderBy: { createdAt: 'desc' }, take: 10 });
|
||||
res.json(listings);
|
||||
});
|
||||
|
||||
export default router;
|
||||
19
api/tsconfig.json
Normal file
19
api/tsconfig.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"lib": [
|
||||
"ES2022"
|
||||
],
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"outDir": "dist",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
},
|
||||
"include": [
|
||||
"src",
|
||||
"prisma/**/*.ts"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user