Skip to content

Commit

Permalink
Add dedicated api app
Browse files Browse the repository at this point in the history
  • Loading branch information
omfj committed Sep 19, 2024
1 parent 6a038f4 commit 2464111
Show file tree
Hide file tree
Showing 41 changed files with 651 additions and 292 deletions.
26 changes: 26 additions & 0 deletions apps/api/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"name": "@echo-webkom/api",
"version": "1.0.0",
"scripts": {
"dev": "pnpm with-env tsx --watch ./src/index.ts",
"with-env": "dotenv -e ../../.env --"
},
"dependencies": {
"@echo-webkom/auth": "workspace:*",
"@echo-webkom/db": "workspace:*",
"@echo-webkom/email": "workspace:*",
"@echo-webkom/lib": "workspace:*",
"@echo-webkom/sanity": "workspace:*",
"@hono/node-server": "1.13.0",
"hono": "4.6.2",
"zod": "3.23.8"
},
"devDependencies": {
"@echo-webkom/tsconfig": "workspace:*",
"@types/node": "20.16.5",
"dotenv-cli": "7.4.2",
"tsx": "4.19.1",
"typescript": "5.5.4",
"vitest": "2.0.5"
}
}
22 changes: 22 additions & 0 deletions apps/api/src/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Hono } from "hono";
import { cors } from "hono/cors";
import { logger } from "hono/logger";

import adminApp from "./services/admin";
import feedbackApp from "./services/feedback";
import happeningApp from "./services/happening";
import healthApp from "./services/health";
import shoppingApp from "./services/shopping-list";

const app = new Hono();

app.use(logger());
app.use(cors());

app.route("/", healthApp);
app.route("/", adminApp);
app.route("/", happeningApp);
app.route("/", feedbackApp);
app.route("/", shoppingApp);

export default app;
15 changes: 15 additions & 0 deletions apps/api/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { serve } from "@hono/node-server";

import app from "./app";

const PORT = 8000;

serve(
{
fetch: app.fetch,
port: PORT,
},
(info) => {
console.log(`Listening on http://localhost:${info.port}`);
},
);
13 changes: 13 additions & 0 deletions apps/api/src/middleware/admin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { createMiddleware } from "hono/factory";

export const admin = () => {
return createMiddleware(async (c, next) => {
const bearerToken = c.req.header("Authorization");

if (bearerToken !== "Bearer admin") {
return c.json({ error: "Unauthorized" }, 401);
}

return await next();
});
};
63 changes: 63 additions & 0 deletions apps/api/src/services/admin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { Hono } from "hono";
import { z } from "zod";

import { db } from "@echo-webkom/db";
import { comments } from "@echo-webkom/db/schemas";

import { admin } from "../middleware/admin";
import { parseJson } from "../utils/json";

const app = new Hono();

app.post("/admin/register", admin(), async (c) => {
return c.text("Registered");
});

app.get("/admin/comments/:id", admin(), async (c) => {
const { id } = c.req.param();

const comments = await db.query.comments.findMany({
where: (comment, { eq }) => eq(comment.postId, id),
orderBy: (comment, { desc }) => [desc(comment.createdAt)],
with: {
user: {
columns: {
id: true,
name: true,
image: true,
},
},
},
});

return c.json(comments);
});

app.post("/admin/comments", admin(), async (c) => {
const { ok, json } = await parseJson(
c,
z.object({
content: z.string(),
postId: z.string(),
userId: z.string(),
parentCommentId: z.string().optional(),
}),
);

if (!ok) {
return c.json({ error: "Invalid data" }, 400);
}

const { content, postId, userId, parentCommentId } = json;

await db.insert(comments).values({
content,
postId,
userId,
parentCommentId,
});

return c.json({ success: true });
});

export default app;
27 changes: 27 additions & 0 deletions apps/api/src/services/feedback.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Hono } from "hono";

import { db } from "@echo-webkom/db";

import { admin } from "../middleware/admin";

const app = new Hono();

app.get("/feedbacks", admin(), async (c) => {
const feedbacks = await db.query.siteFeedback.findMany({
orderBy: (row, { desc }) => [desc(row.createdAt)],
});

return c.json(feedbacks);
});

app.get("/feedback/:id", admin(), async (c) => {
const { id } = c.req.param();

const feedback = await db.query.siteFeedback.findFirst({
where: (row, { eq }) => eq(row.id, id),
});

return c.json(feedback);
});

export default app;
97 changes: 97 additions & 0 deletions apps/api/src/services/happening.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { Hono } from "hono";

import { db } from "@echo-webkom/db";

import { admin } from "../middleware/admin";

const app = new Hono();

app.get("/happenings", async (c) => {
const happenings = await db.query.happenings.findMany();

return c.json(happenings);
});

app.get("/happening/:id", async (c) => {
const { id } = c.req.param();

const happening = await db.query.happenings.findFirst({
where: (row, { eq }) => eq(row.id, id),
});

if (!happening) {
return c.json({ error: "Not found" }, 404);
}

return c.json(happening);
});

app.get("/happening/:id/registrations/count", async (c) => {
const { id } = c.req.param();

const spotRanges = await db.query.spotRanges.findMany({
where: (row, { eq }) => eq(row.happeningId, id),
});

const reigstrations = await db.query.registrations.findMany({
where: (row, { eq }) => eq(row.happeningId, id),
});

const max = spotRanges.reduce((acc, range) => acc + range.spots, 0);

const grouped = reigstrations.reduce(
(acc, registration) => {
const { status } = registration;

if (status === "waiting") {
acc.waiting++;
} else if (status === "registered") {
acc.registered++;
}

return acc;
},
{
waiting: 0,
registered: 0,
max,
},
);

return c.json(grouped);
});

app.get("/happening/:id/registrations", admin(), async (c) => {
const { id } = c.req.param();

const registrations = await db.query.registrations.findMany({
where: (row, { eq }) => eq(row.happeningId, id),
with: {
user: true,
},
});

return c.json(registrations);
});

app.get("/happening/:id/spot-ranges", admin(), async (c) => {
const { id } = c.req.param();

const spotRanges = await db.query.spotRanges.findMany({
where: (row, { eq }) => eq(row.happeningId, id),
});

return c.json(spotRanges);
});

app.get("/happening/:id/questions", async (c) => {
const { id } = c.req.param();

const questions = await db.query.questions.findMany({
where: (row, { eq }) => eq(row.happeningId, id),
});

return c.json(questions);
});

export default app;
9 changes: 9 additions & 0 deletions apps/api/src/services/health.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Hono } from "hono";

const app = new Hono();

app.get("/", async (c) => {
return c.text("OK");
});

export default app;
19 changes: 19 additions & 0 deletions apps/api/src/services/shopping-list.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Hono } from "hono";

import { db } from "@echo-webkom/db";

const app = new Hono();

app.get("/shopping", async (c) => {
const items = await db.query.shoppingListItems
.findMany({
with: { likes: true, user: true },
})
.catch(() => {
return [];
});

return c.json(items);
});

export default app;
34 changes: 34 additions & 0 deletions apps/api/src/utils/json.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Context } from "hono";
import { z } from "zod";

const parseData = <TSchema extends z.ZodSchema>(
data: unknown,
schema: TSchema,
): z.infer<TSchema> => {
try {
return schema.parse(data);
} catch (e) {
throw new Error("Invalid data");
}
};

export const parseJson = async <TSchema extends z.ZodSchema>(
c: Context,
schema: TSchema,
): Promise<
| {
ok: true;
json: z.infer<TSchema>;
}
| {
ok: false;
json?: undefined;
}
> => {
try {
const data = await c.req.json();
return { ok: true, json: parseData(data, schema) };
} catch (e) {
return { ok: false };
}
};
12 changes: 12 additions & 0 deletions apps/api/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "Bundler",
"strict": true,
"skipLibCheck": true,
"types": ["node"],
"jsx": "react-jsx",
"jsxImportSource": "hono/jsx"
}
}
16 changes: 15 additions & 1 deletion apps/cms/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,7 @@
"value": {
"type": "string"
},
"optional": true
"optional": false
},
"title": {
"type": "objectAttribute",
Expand Down Expand Up @@ -1265,6 +1265,13 @@
},
"optional": false
},
"weight": {
"type": "objectAttribute",
"value": {
"type": "number"
},
"optional": false
},
"locations": {
"type": "objectAttribute",
"value": {
Expand Down Expand Up @@ -1395,6 +1402,13 @@
"type": "boolean"
},
"optional": true
},
"PHD": {
"type": "objectAttribute",
"value": {
"type": "boolean"
},
"optional": true
}
}
},
Expand Down
2 changes: 1 addition & 1 deletion apps/cms/schemas/objects/question.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export default defineType({
},
initialValue: () => nanoid(),
validation: (Rule) =>
Rule.custom(async (input, context) => {
Rule.required().custom(async (input, context) => {
if (!input) {
return "ID er påkrevd";
}
Expand Down
Loading

0 comments on commit 2464111

Please sign in to comment.