From fe9949c70d4a1e2b068497a97718e7e79a17640b Mon Sep 17 00:00:00 2001 From: Sanjib Kumar Sen Date: Tue, 7 Jan 2025 16:46:50 +0600 Subject: [PATCH] elysia-base --- Dockerfile | 31 +++++++++++ bun.lockb | Bin 0 -> 39216 bytes docker-compose.yaml | 48 +++++++++++++++++ package.json | 6 ++- src/index.ts | 20 +++++-- src/routes/note.ts | 91 +++++++++++++++++++++++++++++++ src/routes/user.ts | 129 ++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 320 insertions(+), 5 deletions(-) create mode 100644 Dockerfile create mode 100755 bun.lockb create mode 100644 docker-compose.yaml create mode 100644 src/routes/note.ts create mode 100644 src/routes/user.ts diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..36cc29e --- /dev/null +++ b/Dockerfile @@ -0,0 +1,31 @@ +# Build stage +FROM oven/bun:1 AS builder + +WORKDIR /app + +# Copy package files +COPY package.json . +COPY bun.lockb . + +# Install dependencies +RUN bun install --frozen-lockfile + +# Copy source code +COPY . . + +# Build the application +RUN bun build ./src/index.ts --compile --outfile server + +# Production stage +FROM debian:bookworm-slim + +WORKDIR /app + +# Copy only the compiled binary from builder +COPY --from=builder /app/server . + +# Expose the port your app runs on +EXPOSE 3000 + +# Run the binary +CMD ["./server"] \ No newline at end of file diff --git a/bun.lockb b/bun.lockb new file mode 100755 index 0000000000000000000000000000000000000000..bc19a66a0facaa33847a1cf2e99155d2cd6a7b7b GIT binary patch literal 39216 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�a{okMJXg%9G*pFtGH jB=6l5@3bwvU2oztfDz7t%`SY6ftc-h-yrzk_y7L`tFp=a literal 0 HcmV?d00001 diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..048a584 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,48 @@ +services: + api: + build: + context: . + dockerfile: Dockerfile + ports: + - "3000:3000" + environment: + - NODE_ENV=production + - OTEL_EXPORTER_OTLP_ENDPOINT=http://tracing:4318 + - OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf + networks: + - api-network + depends_on: + - tracing + + tracing: + image: jaegertracing/all-in-one:latest + environment: + - COLLECTOR_ZIPKIN_HOST_PORT=:9411 + - COLLECTOR_OTLP_ENABLED=true + ports: + # UI + - "16686:16686" + # Zipkin compatible endpoint + - "9411:9411" + # OTLP gRPC + - "4317:4317" + # OTLP HTTP + - "4318:4318" + # Jaeger gRPC + - "14250:14250" + # Jaeger HTTP + - "14268:14268" + # Admin HTTP + - "14269:14269" + # Agent configs + - "5778:5778" + # Thrift compact + - "6831:6831/udp" + # Thrift binary + - "6832:6832/udp" + networks: + - api-network + +networks: + api-network: + driver: bridge diff --git a/package.json b/package.json index c427aa5..1dc1017 100644 --- a/package.json +++ b/package.json @@ -3,9 +3,13 @@ "version": "1.0.50", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", - "dev": "bun run --watch src/index.ts" + "dev": "bun run --watch src/index.ts", + "build": "bun build --compile --minify-whitespace --minify-syntax --target bun --outfile server ./src/index.ts" }, "dependencies": { + "@elysiajs/opentelemetry": "^1.2.0", + "@elysiajs/server-timing": "^1.2.0", + "@elysiajs/swagger": "^1.2.0", "elysia": "latest" }, "devDependencies": { diff --git a/src/index.ts b/src/index.ts index 9c1f7a1..5ad5897 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,19 @@ import { Elysia } from "elysia"; +import { swagger } from "@elysiajs/swagger"; +import { opentelemetry } from "@elysiajs/opentelemetry"; +import { serverTiming } from "@elysiajs/server-timing"; -const app = new Elysia().get("/", () => "Hello Elysia").listen(3000); +import { note } from "./routes/note"; +import { user } from "./routes/user"; -console.log( - `🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}` -); +const app = new Elysia() + .use(opentelemetry()) + .use(swagger()) + .use(serverTiming()) + .onError(({ error, code }) => { + if (code === "NOT_FOUND") return "Not Found :("; + console.error(error); + }) + .use(user) + .use(note) + .listen(3000); diff --git a/src/routes/note.ts b/src/routes/note.ts new file mode 100644 index 0000000..3ba2538 --- /dev/null +++ b/src/routes/note.ts @@ -0,0 +1,91 @@ +import { Elysia, t } from "elysia"; +import { getUserId, userService } from "./user"; + +const memo = t.Object({ + data: t.String(), + author: t.String(), +}); + +type Memo = typeof memo.static; + +class Note { + constructor( + public data: Memo[] = [ + { + data: "Moonhalo", + author: "saltyaom", + }, + ] + ) {} + + add(note: Memo) { + this.data.push(note); + + return this.data; + } + + remove(index: number) { + return this.data.splice(index, 1); + } + + update(index: number, note: Partial) { + return (this.data[index] = { ...this.data[index], ...note }); + } +} + +export const note = new Elysia({ prefix: "/note" }) + .use(userService) + .decorate("note", new Note()) + .model({ + memo: t.Omit(memo, ["author"]), + }) + .onTransform(function log({ body, params, path, request: { method } }) { + console.log(`${method} ${path}`, { + body, + params, + }); + }) + .get("/", ({ note }) => note.data) + .use(getUserId) + .put( + "/", + ({ note, body: { data }, username }) => + note.add({ data, author: username }), + { + body: "memo", + } + ) + .get( + "/:index", + ({ note, params: { index }, error }) => { + return note.data[index] ?? error(404, "Not Found :("); + }, + { + params: t.Object({ + index: t.Number(), + }), + } + ) + .guard({ + params: t.Object({ + index: t.Number(), + }), + }) + .delete("/:index", ({ note, params: { index }, error }) => { + if (index in note.data) return note.remove(index); + + return error(422); + }) + .patch( + "/:index", + ({ note, params: { index }, body: { data }, error, username }) => { + if (index in note.data) + return note.update(index, { data, author: username }); + + return error(422); + }, + { + isSignIn: true, + body: "memo", + } + ); diff --git a/src/routes/user.ts b/src/routes/user.ts new file mode 100644 index 0000000..b486ac4 --- /dev/null +++ b/src/routes/user.ts @@ -0,0 +1,129 @@ +import { Elysia, t } from "elysia"; + +export const userService = new Elysia({ name: "user/service" }) + .state({ + user: {} as Record, + session: {} as Record, + }) + .model({ + signIn: t.Object({ + username: t.String({ minLength: 1 }), + password: t.String({ minLength: 8 }), + }), + session: t.Cookie( + { + token: t.Number(), + }, + { + secrets: "seia", + } + ), + optionalSession: t.Optional(t.Ref("session")), + }) + .macro({ + isSignIn(enabled: boolean) { + if (!enabled) return; + + return { + beforeHandle({ error, cookie: { token }, store: { session } }) { + if (!token.value) + return error(401, { + success: false, + message: "Unauthorized", + }); + + const username = session[token.value as unknown as number]; + + if (!username) + return error(401, { + success: false, + message: "Unauthorized", + }); + }, + }; + }, + }); + +export const getUserId = new Elysia() + .use(userService) + .guard({ + isSignIn: true, + cookie: "session", + }) + .resolve(({ store: { session }, cookie: { token } }) => ({ + username: session[token.value], + })) + .as("plugin"); + +export const user = new Elysia({ prefix: "/user" }) + .use(userService) + .put( + "/sign-up", + async ({ body: { username, password }, store, error }) => { + if (store.user[username]) + return error(400, { + success: false, + message: "User already exists", + }); + + store.user[username] = await Bun.password.hash(password); + + return { + success: true, + message: "User created", + }; + }, + { + body: "signIn", + } + ) + .post( + "/sign-in", + async ({ + store: { user, session }, + error, + body: { username, password }, + cookie: { token }, + }) => { + if ( + !user[username] || + !(await Bun.password.verify(password, user[username])) + ) + return error(400, { + success: false, + message: "Invalid username or password", + }); + + const key = crypto.getRandomValues(new Uint32Array(1))[0]; + session[key] = username; + token.value = key; + + return { + success: true, + message: `Signed in as ${username}`, + }; + }, + { + body: "signIn", + cookie: "optionalSession", + } + ) + .get( + "/sign-out", + ({ cookie: { token } }) => { + token.remove(); + + return { + success: true, + message: "Signed out", + }; + }, + { + cookie: "optionalSession", + } + ) + .use(getUserId) + .get("/profile", ({ username }) => ({ + success: true, + username, + }));