diff --git a/.editorconfig b/.editorconfig index 2a1f655..0229143 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,2 +1,5 @@ [*.cs] +# We use PostgresSQL which doesn't recommend more specific string types resharper_entity_framework_model_validation_unlimited_string_length_highlighting = none +# This is raised for every single property of records returned by endpoints +resharper_not_accessed_positional_property_local_highlighting = none diff --git a/Foxnouns.Backend/Controllers/MetaController.cs b/Foxnouns.Backend/Controllers/MetaController.cs index d43749e..451960e 100644 --- a/Foxnouns.Backend/Controllers/MetaController.cs +++ b/Foxnouns.Backend/Controllers/MetaController.cs @@ -14,8 +14,13 @@ public class MetaController(DatabaseContext db) : ApiControllerBase var userCount = await db.Users.CountAsync(); var memberCount = await db.Members.CountAsync(); - return Ok(new MetaResponse(userCount, memberCount, BuildInfo.Version, BuildInfo.Hash)); + return Ok(new MetaResponse( + BuildInfo.Version, BuildInfo.Hash, memberCount, + new UserInfo(userCount, 0, 0, 0)) + ); } - private record MetaResponse(int Users, int Members, string Version, string Hash); + private record MetaResponse(string Version, string Hash, int Members, UserInfo Users); + + private record UserInfo(int Total, int ActiveMonth, int ActiveWeek, int ActiveDay); } \ No newline at end of file diff --git a/Foxnouns.Frontend/src/app.html b/Foxnouns.Frontend/src/app.html index 77a5ff5..562d998 100644 --- a/Foxnouns.Frontend/src/app.html +++ b/Foxnouns.Frontend/src/app.html @@ -4,6 +4,13 @@ + %sveltekit.head% diff --git a/Foxnouns.Frontend/src/lib/api/meta.ts b/Foxnouns.Frontend/src/lib/api/meta.ts new file mode 100644 index 0000000..89f1aa3 --- /dev/null +++ b/Foxnouns.Frontend/src/lib/api/meta.ts @@ -0,0 +1,11 @@ +export default interface Meta { + version: string; + hash: string; + users: { + total: number; + active_month: number; + active_week: number; + active_day: number; + }; + members: number; +} diff --git a/Foxnouns.Frontend/src/lib/api/user.ts b/Foxnouns.Frontend/src/lib/api/user.ts new file mode 100644 index 0000000..3832872 --- /dev/null +++ b/Foxnouns.Frontend/src/lib/api/user.ts @@ -0,0 +1,9 @@ +export type User = { + id: string; + username: string; + display_name: string | null; + bio: string | null; + member_title: string | null; + avatar_url: string | null; + links: string[]; +}; diff --git a/Foxnouns.Frontend/src/lib/nav/Logo.svelte b/Foxnouns.Frontend/src/lib/nav/Logo.svelte new file mode 100644 index 0000000..9da9d99 --- /dev/null +++ b/Foxnouns.Frontend/src/lib/nav/Logo.svelte @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + diff --git a/Foxnouns.Frontend/src/lib/nav/Navbar.svelte b/Foxnouns.Frontend/src/lib/nav/Navbar.svelte new file mode 100644 index 0000000..588bb44 --- /dev/null +++ b/Foxnouns.Frontend/src/lib/nav/Navbar.svelte @@ -0,0 +1,57 @@ + + + + + + + + + + + diff --git a/Foxnouns.Frontend/src/lib/request.ts b/Foxnouns.Frontend/src/lib/request.ts new file mode 100644 index 0000000..9536ca9 --- /dev/null +++ b/Foxnouns.Frontend/src/lib/request.ts @@ -0,0 +1,72 @@ +import { PUBLIC_API_BASE } from "$env/static/public"; + +export type RequestParams = { + token?: string; + body?: any; + headers?: Record; +}; + +/** + * Fetch a path from the API and parse the response. + * To make sure the request is authenticated in load functions, + * pass `fetch` from the request object into opts. + * + * @param fetchFn A function like `fetch`, such as from the `load` function + * @param method The HTTP method, i.e. GET, POST, PATCH + * @param path The path to request, minus the leading `/api/v2` + * @param params Extra options for this request + * @returns T + * @throws APIError + */ +export default async function request( + fetchFn: typeof fetch, + method: string, + path: string, + params: RequestParams = {}, +) { + const url = `${PUBLIC_API_BASE}/v2${path}`; + const resp = await fetchFn(url, { + method, + body: params.body ? JSON.stringify(params.body) : undefined, + headers: { + ...params.headers, + ...(params.token ? { Authorization: params.token } : {}), + "Content-Type": "application/json", + }, + }); + + if (resp.status < 200 || resp.status >= 400) throw await resp.json(); + return (await resp.json()) as T; +} + +/** + * Fetch a path from the API and discard the response. + * To make sure the request is authenticated in load functions, + * pass `fetch` from the request object into opts. + * + * @param fetchFn A function like `fetch`, such as from the `load` function + * @param method The HTTP method, i.e. GET, POST, PATCH + * @param path The path to request, minus the leading `/api/v2` + * @param params Extra options for this request + * @returns T + * @throws APIError + */ +export async function fastRequest( + fetchFn: typeof fetch, + method: string, + path: string, + params: RequestParams = {}, +): Promise { + const url = `${PUBLIC_API_BASE}/v2${path}`; + const resp = await fetchFn(url, { + method, + body: params.body ? JSON.stringify(params.body) : undefined, + headers: { + ...params.headers, + ...(params.token ? { Authorization: params.token } : {}), + "Content-Type": "application/json", + }, + }); + + if (resp.status < 200 || resp.status >= 400) throw await resp.json(); +} diff --git a/Foxnouns.Frontend/src/routes/+layout.server.ts b/Foxnouns.Frontend/src/routes/+layout.server.ts new file mode 100644 index 0000000..2d1e4ba --- /dev/null +++ b/Foxnouns.Frontend/src/routes/+layout.server.ts @@ -0,0 +1,13 @@ +import type Meta from "$lib/api/meta"; +import type { User } from "$lib/api/user"; +import request from "$lib/request"; + +export async function load({ fetch }) { + const meta = await request(fetch, "GET", "/meta"); + let user: User | undefined; + try { + user = await request(fetch, "GET", "/users/@me"); + } catch {} + + return { meta, user }; +} diff --git a/Foxnouns.Frontend/src/routes/+layout.svelte b/Foxnouns.Frontend/src/routes/+layout.svelte new file mode 100644 index 0000000..64a204f --- /dev/null +++ b/Foxnouns.Frontend/src/routes/+layout.svelte @@ -0,0 +1,11 @@ + + + + + diff --git a/Foxnouns.Frontend/src/routes/+page.svelte b/Foxnouns.Frontend/src/routes/+page.svelte index 5982b0a..0f5264b 100644 --- a/Foxnouns.Frontend/src/routes/+page.svelte +++ b/Foxnouns.Frontend/src/routes/+page.svelte @@ -1,2 +1,19 @@ + +

Welcome to SvelteKit

Visit kit.svelte.dev to read the documentation

+ +

+ are you logged in? {data.user !== undefined} + {#if data.user} +
hello, {data.user.username}! +
your ID: {data.user.id} + {/if} +

+ +

+ stats: {data.meta.users.total} users, {data.meta.members} members +