-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
16 changed files
with
624 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
{ | ||
"name": "@echo-webkom/ragger", | ||
"scripts": { | ||
"dev": "pnpm with-env tsx watch src/index.ts", | ||
"start": "pnpm with-env tsx src/index.ts", | ||
"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": { | ||
"@types/node": "20.11.17", | ||
"dotenv-cli": "7.4.2", | ||
"tsx": "4.7.1" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import { 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 | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import { serve } from "@hono/node-server"; | ||
|
||
import app from "./routes"; | ||
|
||
const PORT = 4444; | ||
console.log(`Server is running on port ${PORT}`); | ||
|
||
serve({ | ||
fetch: app.fetch, | ||
port: PORT, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import type { Happening, User } from "@echo-webkom/db/schemas"; | ||
|
||
import { getHappeningsFromDateToDate, getStrike } from "./queries"; | ||
|
||
export const BAN_LENGTH = 3; | ||
|
||
export const isUserBanned = async (user: User, bedpres: Happening) => { | ||
if (bedpres.type !== "bedpres") { | ||
return false; | ||
} | ||
|
||
if (!bedpres.date) { | ||
return false; | ||
} | ||
|
||
if (!user.year) { | ||
return false; | ||
} | ||
|
||
if (!user.bannedFromStrike || !user.isBanned) { | ||
return false; | ||
} | ||
|
||
const strike = await getStrike(user.bannedFromStrike); | ||
const dateBanned = strike?.strikeInfo.createdAt; | ||
if (!dateBanned) { | ||
return false; | ||
} | ||
|
||
const happenings = await getHappeningsFromDateToDate(dateBanned, bedpres.date); | ||
|
||
const available = happenings | ||
.filter((happening) => | ||
happening.spotRanges.some( | ||
(spotRange) => user.year! >= spotRange.minYear && user.year! <= spotRange.maxYear, | ||
), | ||
) | ||
.filter((happening) => happening !== bedpres); | ||
|
||
return available.length < BAN_LENGTH; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import type { User } from "@echo-webkom/db/schemas"; | ||
|
||
type CompleteUser = User & { | ||
degreeId: string; | ||
year: number; | ||
hasReadTerms: boolean; | ||
}; | ||
|
||
export const isUserComplete = (user: User): user is CompleteUser => { | ||
if (!user.degreeId || !user.year || !user.hasReadTerms) { | ||
return false; | ||
} | ||
|
||
return true; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
/** | ||
* Returns the intersection of two lists. | ||
* | ||
* @example | ||
* ```ts | ||
* intersection([1, 2, 3], [2, 3, 4]); // [2, 3] | ||
* ``` | ||
* | ||
* @param a list a | ||
* @param b list b | ||
* @returns the elements that are in both lists | ||
*/ | ||
export const intersection = <T>(a: Array<T>, b: Array<T>): Array<T> => { | ||
return a.filter((value) => b.includes(value)); | ||
}; | ||
|
||
/** | ||
* Checks if two lists have any elements in common. | ||
* | ||
* @param a list a | ||
* @param b list b | ||
* @returns true if the lists have any elements in common, false otherwise | ||
*/ | ||
export const doesIntersect = <T>(a: Array<T>, b: Array<T>): boolean => { | ||
return intersection(a, b).length > 0; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
export const logger = { | ||
info: (message: string, meta?: Record<string, unknown>) => { | ||
console.log("ℹ️ [INFO]", message, JSON.stringify(meta)); | ||
}, | ||
error: (message: string, meta?: Record<string, unknown>) => { | ||
console.error("❌ [ERROR]", message, JSON.stringify(meta)); | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import type { MiddlewareHandler } from "hono"; | ||
|
||
import type { Bindings } from "./app"; | ||
|
||
/** | ||
* Check if the request authorization header matches the secret | ||
* | ||
* @param c hono context | ||
* @param next next handler | ||
* @returns the next handler | ||
*/ | ||
export const auth: MiddlewareHandler<{ Bindings: Bindings }> = async (c, next) => { | ||
if (!c.env.ADMIN_KEY) { | ||
return next(); | ||
} | ||
|
||
const token = c.req.header("Authorization"); | ||
if (token !== `Bearer ${c.env.ADMIN_KEY}`) { | ||
return c.text("Unauthorized", { status: 401 }); | ||
} | ||
|
||
return next(); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
import { and, eq, gte, lte, or, sql } from "drizzle-orm"; | ||
|
||
import { db } from "@echo-webkom/db"; | ||
import { AnswerInsert, answers, registrations, SpotRange, users } from "@echo-webkom/db/schemas"; | ||
|
||
import { RegisterJson } from "./schema"; | ||
|
||
export const getUser = async (userId: string) => { | ||
return await db.query.users.findFirst({ | ||
where: (row, { eq }) => eq(row.id, userId), | ||
with: { | ||
memberships: { | ||
with: { | ||
group: true, | ||
}, | ||
}, | ||
}, | ||
}); | ||
}; | ||
|
||
export const getHappening = async (happeningId: string) => { | ||
return await db.query.happenings.findFirst({ | ||
where: (row, { eq }) => eq(row.id, happeningId), | ||
with: { | ||
questions: true, | ||
spotRanges: true, | ||
}, | ||
}); | ||
}; | ||
|
||
export const getStrike = async (strikeId: number) => { | ||
return await db.query.strikes.findFirst({ | ||
where: (row, { eq }) => eq(row.id, strikeId), | ||
with: { | ||
strikeInfo: true, | ||
}, | ||
}); | ||
}; | ||
|
||
export const getHappeningsFromDateToDate = async (fromDate: Date, toDate: Date) => { | ||
return await db.query.happenings.findMany({ | ||
where: (happening, { and, eq, gt, lt }) => | ||
and(eq(happening.type, "bedpres"), gt(happening.date, fromDate), lt(happening.date, toDate)), | ||
with: { | ||
spotRanges: true, | ||
}, | ||
orderBy: (happening, { asc }) => [asc(happening.date)], | ||
}); | ||
}; | ||
|
||
export const getExisitingRegistration = async (userId: string, happeningId: string) => { | ||
return await db.query.registrations.findFirst({ | ||
where: (registration, { and, eq, or }) => | ||
and( | ||
eq(registration.userId, userId), | ||
eq(registration.happeningId, happeningId), | ||
or(eq(registration.status, "registered"), eq(registration.status, "waiting")), | ||
), | ||
}); | ||
}; | ||
|
||
export const getHostGroups = async (id: string) => { | ||
return await db.query.happeningsToGroups | ||
.findMany({ | ||
where: (happeningToGroup, { eq }) => eq(happeningToGroup.happeningId, id), | ||
}) | ||
.then((groups) => groups.map((group) => group.groupId)); | ||
}; | ||
|
||
export const registerUserToHappening = async ( | ||
userId: string, | ||
happeningId: string, | ||
spotRange: SpotRange, | ||
) => { | ||
return await db.transaction( | ||
async (tx) => { | ||
await tx.execute(sql`LOCK TABLE ${registrations} IN EXCLUSIVE MODE`); | ||
|
||
const regs = await tx | ||
.select() | ||
.from(registrations) | ||
.where( | ||
and( | ||
eq(registrations.happeningId, happeningId), | ||
lte(users.year, spotRange.maxYear), | ||
gte(users.year, spotRange.minYear), | ||
or(eq(registrations.status, "registered"), eq(registrations.status, "waiting")), | ||
), | ||
) | ||
.leftJoin(users, eq(registrations.userId, users.id)); | ||
|
||
const isInfiniteSpots = spotRange.spots === 0; | ||
const isWaitlisted = !isInfiniteSpots && regs.length >= spotRange.spots; | ||
|
||
const registration = await tx | ||
.insert(registrations) | ||
.values({ | ||
status: isWaitlisted ? "waiting" : "registered", | ||
happeningId, | ||
userId, | ||
changedBy: null, | ||
}) | ||
.returning() | ||
.onConflictDoUpdate({ | ||
target: [registrations.happeningId, registrations.userId], | ||
set: { | ||
status: isWaitlisted ? "waiting" : "registered", | ||
}, | ||
}) | ||
.then((res) => res[0] ?? null); | ||
|
||
return { | ||
registration, | ||
isWaitlisted, | ||
}; | ||
}, | ||
{ | ||
isolationLevel: "read committed", | ||
}, | ||
); | ||
}; | ||
|
||
export const insertAnswers = async ( | ||
happeningId: string, | ||
userId: string, | ||
questions: RegisterJson["questions"], | ||
) => { | ||
const answersToInsert = questions.map( | ||
(question) => | ||
({ | ||
happeningId: happeningId, | ||
userId: userId, | ||
questionId: question.questionId, | ||
answer: question.answer | ||
? { | ||
answer: question.answer, | ||
} | ||
: null, | ||
}) satisfies AnswerInsert, | ||
); | ||
|
||
if (answersToInsert.length > 0) { | ||
await db.insert(answers).values(answersToInsert).onConflictDoNothing(); | ||
} | ||
}; |
Oops, something went wrong.