Skip to content

Commit

Permalink
Add ragger
Browse files Browse the repository at this point in the history
  • Loading branch information
omfj committed Aug 30, 2024
1 parent d4b79db commit 3f22c3a
Show file tree
Hide file tree
Showing 33 changed files with 1,224 additions and 447 deletions.
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ NEXTAUTH_URL="http://localhost:3000"
# --- echogram (can be found in the nano repository)
#NEXT_PUBLIC_ECHOGRAM_URL="http://localhost:8001"

# --- ars
NEXT_PUBLIC_ARS_URL="http://localhost:4444"

# --- Testing
# Set to true to enable testing mode
#TESTING=true
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ env:
ADMIN_KEY: foobar
DATABASE_URL: postgres://postgres:postgres@localhost:5432/echo-web
NEXT_PUBLIC_SANITY_DATASET: "testing"
NEXT_PUBLIC_ARS_URL: "http://localhost:4444"
FEIDE_CLIENT_ID: ${{ secrets.FEIDE_CLIENT_ID }}
FEIDE_CLIENT_SECRET: ${{ secrets.FEIDE_CLIENT_SECRET }}
NEXTAUTH_SECRET: ${{ secrets.NEXTAUTH_SECRET }}
Expand Down
30 changes: 30 additions & 0 deletions apps/ars/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# dev
.yarn/
!.yarn/releases
.vscode/*
!.vscode/launch.json
!.vscode/*.code-snippets
.idea/workspace.xml
.idea/usage.statistics.xml
.idea/shelf

# deps
node_modules/

# env
.env
.env.production

.turbo/

# logs
logs/
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

# misc
.DS_Store
28 changes: 28 additions & 0 deletions apps/ars/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# dev
.yarn/
!.yarn/releases
.vscode/*
!.vscode/launch.json
!.vscode/*.code-snippets
.idea/workspace.xml
.idea/usage.statistics.xml
.idea/shelf

# deps
node_modules/

# env
.env
.env.production

# logs
logs/
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

# misc
.DS_Store
42 changes: 42 additions & 0 deletions apps/ars/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# syntax=docker/dockerfile:1

# Stage 1: Base with Node.js and PNPM installed
ARG NODE_VERSION=20.12.1
FROM node:${NODE_VERSION}-slim as base

LABEL fly_launch_runtime="Node.js"

WORKDIR /app

ENV NODE_ENV=production

ARG PNPM_VERSION=9.0.4
RUN npm install -g pnpm@$PNPM_VERSION

# Stage 2: Build Stage
FROM base as build

ENV NODE_ENV=development

RUN apt-get update -qq && \
apt-get install --no-install-recommends -y build-essential node-gyp pkg-config python-is-python3 && \
rm -rf /var/lib/apt/lists/*

COPY --link . .

RUN pnpm --filter=ars deploy pruned-ars && \
cd pruned-ars && \
pnpm install && \
pnpm run build && \
pnpm prune --prod

ENV NODE_ENV=production

# Stage 3: Production Image
FROM base

COPY --from=build /app/pruned-ars /app

EXPOSE 4444

CMD ["pnpm", "run", "start"]
1 change: 1 addition & 0 deletions apps/ars/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# ARS (Advanced Registration System)
22 changes: 22 additions & 0 deletions apps/ars/fly.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# fly.toml app configuration file generated for ragger-snowy-sound-6162 on 2024-08-30T01:38:21+02:00
#
# See https://fly.io/docs/reference/configuration/ for information about how to use this file.
#

app = 'ragger-snowy-sound-6162'
primary_region = 'arn'

[build]

[http_service]
internal_port = 4444
force_https = true
auto_stop_machines = 'stop'
auto_start_machines = true
min_machines_running = 0
processes = ['app']

[[vm]]
memory = '1gb'
cpu_kind = 'shared'
cpus = 1
30 changes: 30 additions & 0 deletions apps/ars/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"name": "@echo-webkom/ars",
"scripts": {
"start": "pnpm with-env node dist/index.js",
"build": "pnpm with-env esbuild src/**/*.ts --bundle --platform=node --outdir=dist",
"dev": "pnpm with-env tsx watch src/index.ts",
"test:unit": "vitest",
"clean": "rm -rf .turbo node_modules",
"lint": "eslint",
"typecheck": "tsc --noEmit",
"with-env": "dotenv -e ../../.env --"
},
"dependencies": {
"@echo-webkom/db": "workspace:*",
"@hono/node-server": "1.12.2",
"date-fns": "3.6.0",
"drizzle-orm": "0.33.0",
"hono": "4.5.9",
"zod": "3.23.8"
},
"devDependencies": {
"@flydotio/dockerfile": "0.5.8",
"@types/node": "20.16.1",
"dotenv-cli": "7.4.2",
"esbuild": "0.23.1",
"tsx": "4.17.0",
"typescript": "5.5.4",
"vitest": "2.0.5"
}
}
170 changes: 170 additions & 0 deletions apps/ars/src/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import { isFuture, isPast } from "date-fns";

import { getCorrectSpotrange } from "./correct-spot-range";
import { createApp } from "./create-app";
import { isUserBanned } from "./is-user-banned";
import { isUserComplete } from "./is-user-complete";
import { doesIntersect } from "./list";
import { logger } from "./logger";
import { auth } from "./middleware";
import {
getExisitingRegistration,
getHappening,
getHostGroups,
getUser,
registerUserToHappening,
} from "./queries";
import { registerJsonSchema } from "./schema";

const app = createApp();

app.get("/", (c) => {
return c.json({
message: "OK",
});
});

app.post("/", auth, async (c) => {
const json = await c.req.json().catch(() => {});
const { success, data } = registerJsonSchema.safeParse(json);

if (!success) {
logger.error("Invalid JSON", json);
return c.json({ success: false, message: "Ugyldig JSON" });
}

const { userId, happeningId, questions } = data;

const user = await getUser(userId);

if (!user) {
logger.error("User not found", {
userId,
});
return c.json({ success: false, message: "Brukeren finnes ikke" });
}

const happening = await getHappening(happeningId);

if (!happening) {
logger.error("Happening not found", {
happeningId,
});
return c.json({ success: false, message: "Arrangementet finnes ikke" });
}

if (!isUserComplete(user)) {
logger.error("User is not complete", {
userId,
});
return c.json({ success: false, message: "Brukeren er ikke fullført" });
}

const isBanned = await isUserBanned(user, happening);
if (isBanned) {
logger.error("User is banned", {
userId,
});
return c.json({ success: false, message: "Brukeren er utestengt" });
}

const exisitingRegistration = await getExisitingRegistration(userId, happeningId);

if (exisitingRegistration) {
logger.error("Registration already exists", {
userId,
happeningId,
});

const status =
exisitingRegistration.status === "registered"
? "Du er allerede påmeldt dette arrangementet"
: "Du er allerede på venteliste til dette arrangementet";

return c.json({
success: false,
message: status,
});
}

const canEarlyRegister = doesIntersect(
happening.registrationGroups ?? [],
user.memberships.map((membership) => membership.groupId),
);

if (!canEarlyRegister && happening.registrationStart && isFuture(happening.registrationStart)) {
logger.error("Registration is not open", {
userId,
happeningId,
});
return c.json({ success: false, message: "Påmeldingen har ikke startet" });
}

if (!canEarlyRegister && !happening.registrationStart) {
logger.error("Registration is not open", {
userId,
happeningId,
});
return c.json({ success: false, message: "Påmelding er bare for inviterte undergrupper" });
}

if (happening.registrationEnd && isPast(happening.registrationEnd)) {
logger.error("Registration is closed", {
userId,
happeningId,
});
return c.json({ success: false, message: "Påmeldingen har allerede stengt" });
}

const hostGroups = await getHostGroups(happeningId);

const canSkipSpotRange = doesIntersect(
hostGroups,
user.memberships.map((membership) => membership.groupId),
);

const userSpotRange = getCorrectSpotrange(user.year, happening.spotRanges, canSkipSpotRange);

if (!userSpotRange) {
logger.error("User is not in any spot range", {
userId,
happeningId,
});
return c.json({ success: false, message: "Brukeren er ikke i en plassering" });
}

const allQuestionsAnswered = happening.questions.every((question) => {
const questionExists = questions.find((q) => q.questionId === question.id);
const questionAnswer = questionExists?.answer;

return question.required ? !!questionAnswer : true;
});

if (!allQuestionsAnswered) {
logger.error("Not all questions are answered", {
userId,
happeningId,
});
return c.json({ success: false, message: "Du må svare på alle spørsmålene" });
}

const status = await registerUserToHappening(userId, happeningId, userSpotRange);

if (status === "error") {
logger.error("Failed to update registration", {
userId,
happeningId,
});
return c.json({ success: false, message: "Noe gikk galt" });
}

const message =
status === "waitlisted" ? "Du er nå på venteliste" : "Du er nå påmeldt arrangementet";

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

export default app;
17 changes: 17 additions & 0 deletions apps/ars/src/correct-spot-range.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { type SpotRange } from "@echo-webkom/db/schemas";

export const getCorrectSpotrange = (
year: number,
spotRanges: Array<SpotRange>,
canSkipSpotRange: boolean,
) => {
return (
spotRanges.find((spotRange) => {
if (canSkipSpotRange) {
return true;
}

return year >= spotRange.minYear && year <= spotRange.maxYear;
}) ?? null
);
};
13 changes: 13 additions & 0 deletions apps/ars/src/create-app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Hono } from "hono";

export type Bindings = {
ADMIN_KEY: string;
};

export const createApp = () => {
const app = new Hono<{
Bindings: Bindings;
}>();

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

import app from "./app";

const PORT = 4444;
console.log(`Server is running on port ${PORT}`);

serve({
fetch: app.fetch,
port: PORT,
});
Loading

0 comments on commit 3f22c3a

Please sign in to comment.