diff --git a/databases/init/students.sql b/databases/init/students.sql new file mode 100644 index 0000000..b503221 --- /dev/null +++ b/databases/init/students.sql @@ -0,0 +1,14 @@ +create table students ( + userId text primary key, + firstName text, + lastName text, + mail text, + promo integer, + foreign key(promo) references promo(id) +); + +create table promo ( + id integer, + endyear integer, + current integer +); \ No newline at end of file diff --git a/defaults/interfaces.ts b/defaults/interfaces.ts index 9b417e2..3189dfe 100644 --- a/defaults/interfaces.ts +++ b/defaults/interfaces.ts @@ -1,3 +1,5 @@ +import { type RegularTagNode, type TextNode } from "@melvdouc/xml-parser"; + export interface AppProperties { name: string; icon: string; @@ -6,4 +8,43 @@ export interface AppProperties { hint: string; } +export interface CasTagNode extends RegularTagNode { + children: [TextNode]; +} + +export interface CasGroupNode extends RegularTagNode { + children: CasTagNode[]; +} + +export interface CasResponse extends RegularTagNode { + children: [TextNode, CasGroupNode]; +} + +export interface CasContent { + amuCampus: string; + amuComposante: string; + amuDateValidation: string; + coGroup: string; + eduPersonPrimaryAffiliation: string; + eduPersonPrincipalName: string; + mail: string; + displayName: string; + givenName: string; + memberOf: string[]; + sn: string; + supannCivilite: string; + supannEntiteAffectation: string; + supannEtuAnneeInscription: string; + supannEtuEtape: string; + uid: string; +} + +export interface LoginJWT { + iss: "PolyMPR"; + iat: number; + exp: number; + aud: "PolyMPR"; + user: CasContent; +} + export type EmptyObject = Record; diff --git a/defaults/makeIndex.ts b/defaults/makeIndex.ts index 4205b03..f4ebd11 100644 --- a/defaults/makeIndex.ts +++ b/defaults/makeIndex.ts @@ -1,10 +1,12 @@ -import { EmptyObject } from "$root/defaults/interfaces.ts"; +import { FreshContext } from "$fresh/server.ts"; +import { State } from "$root/routes/_middleware.ts"; -export default function makeIndex< - IndexProps = EmptyObject, ->(basePath: string) { - return async function Index(props: IndexProps) { +export default function makeIndex(basePath: string) { + return async function Index( + request: Request, + context: FreshContext, + ) { const index = (await import(`${basePath}/partials/index.tsx`)).Index; - return index(props); + return index(request, context); }; } diff --git a/defaults/makePartials.tsx b/defaults/makePartials.tsx index 783c20e..e6b81af 100644 --- a/defaults/makePartials.tsx +++ b/defaults/makePartials.tsx @@ -1,6 +1,7 @@ import { JSX } from "preact"; import { Partial } from "$fresh/runtime.ts"; -import { RouteConfig } from "$fresh/server.ts"; +import { FreshContext, RouteConfig } from "$fresh/server.ts"; +import { State } from "$root/routes/_middleware.ts"; export function getPartialsConfig(): RouteConfig { return { @@ -9,14 +10,19 @@ export function getPartialsConfig(): RouteConfig { }; } -// deno-lint-ignore no-explicit-any -export function makePartials( - page: (props: Props) => JSX.Element, +export function makePartials( + page: ( + request: Request, + context: FreshContext, + ) => Promise, ) { - return function WrappedElements(props: Props): JSX.Element { + return async function WrappedElements( + request: Request, + context: FreshContext, + ): Promise { return ( - {page(props)} + {await page(request, context)} ); }; diff --git a/fresh.gen.ts b/fresh.gen.ts index 0c4ae9b..a01fa4e 100644 --- a/fresh.gen.ts +++ b/fresh.gen.ts @@ -14,6 +14,9 @@ import * as $_apps_notes_partials_admin_courses from "./routes/(apps)/notes/part import * as $_apps_notes_partials_admin_students from "./routes/(apps)/notes/partials/(admin)/students.tsx"; import * as $_apps_notes_partials_index from "./routes/(apps)/notes/partials/index.tsx"; import * as $_apps_notes_partials_notes from "./routes/(apps)/notes/partials/notes.tsx"; +import * as $_apps_students_api_example from "./routes/(apps)/students/api/example.ts"; +import * as $_apps_students_index from "./routes/(apps)/students/index.tsx"; +import * as $_apps_students_partials_index from "./routes/(apps)/students/partials/index.tsx"; import * as $_404 from "./routes/_404.tsx"; import * as $_app from "./routes/_app.tsx"; import * as $_middleware from "./routes/_middleware.ts"; @@ -47,6 +50,10 @@ const manifest = { $_apps_notes_partials_admin_students, "./routes/(apps)/notes/partials/index.tsx": $_apps_notes_partials_index, "./routes/(apps)/notes/partials/notes.tsx": $_apps_notes_partials_notes, + "./routes/(apps)/students/api/example.ts": $_apps_students_api_example, + "./routes/(apps)/students/index.tsx": $_apps_students_index, + "./routes/(apps)/students/partials/index.tsx": + $_apps_students_partials_index, "./routes/_404.tsx": $_404, "./routes/_app.tsx": $_app, "./routes/_middleware.ts": $_middleware, diff --git a/routes/(apps)/students/(_props)/props.ts b/routes/(apps)/students/(_props)/props.ts new file mode 100644 index 0000000..0a7a522 --- /dev/null +++ b/routes/(apps)/students/(_props)/props.ts @@ -0,0 +1,14 @@ +import { AppProperties } from "$root/defaults/interfaces.ts"; + +const properties: AppProperties = { + name: "Students", + icon: "badge", + pages: { + index: "Homepage", + upload: "Upload students", + }, + adminOnly: ["upload"], + hint: "See student information", +}; + +export default properties; diff --git a/routes/(apps)/students/api/example.ts b/routes/(apps)/students/api/example.ts new file mode 100644 index 0000000..9f04cd1 --- /dev/null +++ b/routes/(apps)/students/api/example.ts @@ -0,0 +1,22 @@ +import { Handlers } from "$fresh/server.ts"; + +export const handler: Handlers = { + async POST(request, context) { + if (request.headers.get("content-type") != "application/json") { + return new Response(null, { + status: 400, + }); + } + + const responseBody = { + requestBody: await request.json(), + context, + }; + + return new Response(JSON.stringify(responseBody), { + headers: { + "content-type": "application/json", + }, + }); + }, +}; diff --git a/routes/(apps)/students/index.tsx b/routes/(apps)/students/index.tsx new file mode 100644 index 0000000..1d82f7f --- /dev/null +++ b/routes/(apps)/students/index.tsx @@ -0,0 +1,2 @@ +import makeIndex from "$root/defaults/makeIndex.ts"; +export default makeIndex(import.meta.dirname!); diff --git a/routes/(apps)/students/partials/index.tsx b/routes/(apps)/students/partials/index.tsx new file mode 100644 index 0000000..2971e0e --- /dev/null +++ b/routes/(apps)/students/partials/index.tsx @@ -0,0 +1,14 @@ +import { + getPartialsConfig, + makePartials, +} from "$root/defaults/makePartials.tsx"; +import { FreshContext } from "$fresh/server.ts"; +import { State } from "$root/routes/_middleware.ts"; + +// deno-lint-ignore require-await +export async function Index(_request: Request, context: FreshContext) { + return

Welcome to {context.state.session?.displayName}.

; +} + +export const config = getPartialsConfig(); +export default makePartials(Index); diff --git a/routes/_middleware.ts b/routes/_middleware.ts index 281a9c0..3601da1 100644 --- a/routes/_middleware.ts +++ b/routes/_middleware.ts @@ -1,7 +1,7 @@ import { FreshContext } from "$fresh/server.ts"; import { getCookies } from "$std/http/cookie.ts"; import { getJwtPayload, isJwtValid } from "@popov/jwt"; -import { LoginJWT } from "$root/routes/login.tsx"; +import { CasContent, LoginJWT } from "$root/defaults/interfaces.ts"; const PUBLIC_ROUTES = [ "/", @@ -16,6 +16,7 @@ const jwtKeyCache: Record = {}; export interface State { isAuthenticated: boolean; + session: CasContent; } function isRoutePublic(route: string) { @@ -44,12 +45,16 @@ export const handler = [ } const content = getJwtPayload(cookies["sessionToken"]) as LoginJWT; - const key = getKey(content.user.uid as string); + const key = getKey(content.user.uid); context.state.isAuthenticated = await isJwtValid( cookies["sessionToken"], key, ); + const session: CasContent = + (getJwtPayload(cookies["sessionToken"]) as LoginJWT).user; + + context.state.session = session; return await context.next(); }, diff --git a/routes/login.tsx b/routes/login.tsx index 284fc5e..66dfcb0 100644 --- a/routes/login.tsx +++ b/routes/login.tsx @@ -1,10 +1,12 @@ import { Handlers } from "$fresh/server.ts"; import { State } from "$root/routes/_middleware.ts"; +import { parse, type RegularTagNode } from "@melvdouc/xml-parser"; import { - parse, - type RegularTagNode, - type TextNode, -} from "@melvdouc/xml-parser"; + CasContent, + CasResponse, + CasTagNode, + LoginJWT, +} from "$root/defaults/interfaces.ts"; import { createJwt } from "@popov/jwt"; import { setCookie } from "$std/http/cookie.ts"; import { getKey } from "$root/routes/_middleware.ts"; @@ -12,26 +14,6 @@ import { getKey } from "$root/routes/_middleware.ts"; const SERVICE = "https://localhost/login"; const CAS = "https://ident.univ-amu.fr/cas"; -interface CasTagNode extends RegularTagNode { - children: [TextNode]; -} - -interface CasGroupNode extends RegularTagNode { - children: CasTagNode[]; -} - -interface CasResponse extends RegularTagNode { - children: [TextNode, CasGroupNode]; -} - -export interface LoginJWT { - iss: "PolyMPR"; - iat: number; - exp: number; - aud: "PolyMPR"; - user: Record; -} - function getTag(tag: CasTagNode): [string, string] { return [ tag.tagName.replace("cas:", ""), @@ -43,11 +25,13 @@ function createUserJWT(casResponse: CasResponse): Promise { const nodes = casResponse.children[1].children.map(getTag); const fullUserInfos: Record = {}; - nodes.forEach(([key, value]) => { - if (fullUserInfos[key] && Array.isArray(fullUserInfos[key])) { + nodes.forEach(([key, value]: [string, string]) => { + if (typeof fullUserInfos[key] == "string") { + fullUserInfos[key] = [fullUserInfos[key]]; + } + + if (Array.isArray(fullUserInfos[key])) { fullUserInfos[key].push(value); - } else if (fullUserInfos[key]) { - fullUserInfos[key] = [fullUserInfos[key], value]; } else { fullUserInfos[key] = value; } @@ -56,12 +40,12 @@ function createUserJWT(casResponse: CasResponse): Promise { const now = Math.floor(Date.now() / 1000); const oneHour = 60 * 60; - const payload = { + const payload: LoginJWT = { iss: "PolyMPR", iat: now, exp: now + oneHour, aud: "PolyMPR", - user: fullUserInfos, + user: fullUserInfos as unknown as CasContent, }; const key = getKey(fullUserInfos.uid as string); diff --git a/toolbox/module/create.ts b/toolbox/module/create.ts index f289d98..aa93463 100644 --- a/toolbox/module/create.ts +++ b/toolbox/module/create.ts @@ -77,15 +77,14 @@ function getIndexContent(_name: string) { function getPartialIndexContent(name: string) { return ` - import { EmptyObject } from "$root/defaults/interfaces.ts"; import { getPartialsConfig, makePartials, } from "$root/defaults/makePartials.tsx"; + import { FreshContext } from "$fresh/server.ts"; + import { State } from "$root/routes/_middleware.ts"; - type ${name}IndexProps = EmptyObject; - - export function Index(_props: ${name}IndexProps) { + export async function Index(request: Request, context: FreshContext) { return

Welcome to ${name}.

; }