Initial project setup: monorepo, API, Postgres, events CRUD
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "calendar-api",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "tsx watch src/server.ts",
|
||||
"build": "tsc",
|
||||
"start": "node dist/index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fastify/cors": "^11.0.0",
|
||||
"@fastify/multipart": "^9.0.0",
|
||||
"@fastify/static": "^8.0.0",
|
||||
"fastify": "^5.0.0",
|
||||
"pg": "^8.20.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.0.0",
|
||||
"@types/pg": "^8.20.0",
|
||||
"tsx": "^4.20.0",
|
||||
"typescript": "^5.8.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import pg from "pg";
|
||||
|
||||
const { Pool } = pg;
|
||||
|
||||
export const pool = new Pool({
|
||||
host: "localhost",
|
||||
port: 5432,
|
||||
user: "postgres",
|
||||
password: "postgres",
|
||||
database: "church_platform",
|
||||
});
|
||||
@@ -0,0 +1,147 @@
|
||||
import { FastifyInstance } from "fastify";
|
||||
import { pool } from "../../db";
|
||||
|
||||
export async function calendarRoutes(app: FastifyInstance) {
|
||||
app.get("/events", async () => {
|
||||
const result = await pool.query(
|
||||
"SELECT * FROM events ORDER BY starts_at ASC"
|
||||
);
|
||||
return result.rows;
|
||||
});
|
||||
|
||||
app.post(
|
||||
"/events",
|
||||
{
|
||||
schema: {
|
||||
body: {
|
||||
type: "object",
|
||||
required: ["title", "starts_at"],
|
||||
properties: {
|
||||
title: { type: "string", minLength: 1 },
|
||||
starts_at: { type: "string", format: "date-time" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (request, reply) => {
|
||||
const { title, starts_at } = request.body as {
|
||||
title: string;
|
||||
starts_at: string;
|
||||
};
|
||||
|
||||
const result = await pool.query(
|
||||
"INSERT INTO events (title, starts_at) VALUES ($1, $2) RETURNING *",
|
||||
[title, starts_at]
|
||||
);
|
||||
|
||||
reply.code(201);
|
||||
return result.rows[0];
|
||||
}
|
||||
);
|
||||
|
||||
app.get(
|
||||
"/events/:id",
|
||||
{
|
||||
schema: {
|
||||
params: {
|
||||
type: "object",
|
||||
required: ["id"],
|
||||
properties: {
|
||||
id: { type: "string" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (request, reply) => {
|
||||
const { id } = request.params as { id: string };
|
||||
|
||||
const result = await pool.query("SELECT * FROM events WHERE id = $1", [
|
||||
id,
|
||||
]);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
reply.code(404);
|
||||
return { error: "Event not found" };
|
||||
}
|
||||
|
||||
return result.rows[0];
|
||||
}
|
||||
);
|
||||
|
||||
app.delete(
|
||||
"/events/:id",
|
||||
{
|
||||
schema: {
|
||||
params: {
|
||||
type: "object",
|
||||
required: ["id"],
|
||||
properties: {
|
||||
id: { type: "string" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (request, reply) => {
|
||||
const { id } = request.params as { id: string };
|
||||
|
||||
const result = await pool.query(
|
||||
"DELETE FROM events WHERE id = $1 RETURNING *",
|
||||
[id]
|
||||
);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
reply.code(404);
|
||||
return { error: "Event not found" };
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
}
|
||||
);
|
||||
|
||||
app.put(
|
||||
"/events/:id",
|
||||
{
|
||||
schema: {
|
||||
params: {
|
||||
type: "object",
|
||||
required: ["id"],
|
||||
properties: {
|
||||
id: { type: "string" },
|
||||
},
|
||||
},
|
||||
body: {
|
||||
type: "object",
|
||||
required: ["title", "starts_at"],
|
||||
properties: {
|
||||
title: { type: "string", minLength: 1 },
|
||||
starts_at: { type: "string", format: "date-time" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (request, reply) => {
|
||||
const { id } = request.params as { id: string };
|
||||
const { title, starts_at } = request.body as {
|
||||
title: string;
|
||||
starts_at: string;
|
||||
};
|
||||
|
||||
const result = await pool.query(
|
||||
`UPDATE events
|
||||
SET title = $1,
|
||||
starts_at = $2,
|
||||
updated_at = NOW()
|
||||
WHERE id = $3
|
||||
RETURNING *`,
|
||||
[title, starts_at, id]
|
||||
);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
reply.code(404);
|
||||
return { error: "Event not found" };
|
||||
}
|
||||
|
||||
return result.rows[0];
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import Fastify from "fastify";
|
||||
import { pool } from "./db/index.js";
|
||||
import { calendarRoutes } from "./modules/calendar/routes.js";
|
||||
|
||||
const app = Fastify();
|
||||
|
||||
pool
|
||||
.query("SELECT NOW()")
|
||||
.then((res) => console.log("DB connected:", res.rows[0]))
|
||||
.catch((err) => console.error("DB connection failed:", err));
|
||||
|
||||
app.register(calendarRoutes);
|
||||
|
||||
app.get("/health", async () => {
|
||||
return { ok: true };
|
||||
});
|
||||
|
||||
app.listen({ port: 3000, host: "0.0.0.0" }).then(() => {
|
||||
console.log("API running on port 3000");
|
||||
});
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"outDir": "dist",
|
||||
"rootDir": "src",
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"types": ["node"]
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
Reference in New Issue
Block a user