This commit is contained in:
sam 2024-06-09 15:48:26 +02:00
parent 14f8e77e6a
commit a2f001392b
Signed by: sam
GPG key ID: B4EF20DDE721CAA1
11 changed files with 241 additions and 2 deletions

View file

@ -1,2 +1,5 @@
[*.cs] [*.cs]
# We use PostgresSQL which doesn't recommend more specific string types
resharper_entity_framework_model_validation_unlimited_string_length_highlighting = none 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

View file

@ -14,8 +14,13 @@ public class MetaController(DatabaseContext db) : ApiControllerBase
var userCount = await db.Users.CountAsync(); var userCount = await db.Users.CountAsync();
var memberCount = await db.Members.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);
} }

View file

@ -4,6 +4,13 @@
<meta charset="utf-8" /> <meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" /> <link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<script>
let theme = localStorage.getItem("pronounscc-theme");
if (!theme || theme === "auto")
theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
document.documentElement.setAttribute("data-bs-theme", theme);
</script>
%sveltekit.head% %sveltekit.head%
</head> </head>
<body data-sveltekit-preload-data="hover"> <body data-sveltekit-preload-data="hover">

View file

@ -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;
}

View file

@ -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[];
};

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.9 KiB

View file

@ -0,0 +1,57 @@
<script lang="ts">
import {
Navbar,
NavbarBrand,
NavbarToggler,
Collapse,
Nav,
Dropdown,
DropdownToggle,
DropdownMenu,
DropdownItem,
NavItem,
} from "@sveltestrap/sveltestrap";
import Logo from "./Logo.svelte";
import type { User } from "$lib/api/user";
export let user: User | undefined;
let showMenu = true;
const toggleMenu = () => {
showMenu = !showMenu;
};
let userMenuOpen = false;
</script>
<Navbar>
<NavbarBrand href="/" aria-label="pronouns.cc landing page">
<Logo />
</NavbarBrand>
<NavbarToggler on:click={toggleMenu} aria-label="Toggle menu" />
<Collapse isOpen={showMenu} navbar expand="lg">
<Nav class="ms-auto" navbar>
{#if user}
<NavItem>
<Dropdown nav isOpen={userMenuOpen} toggle={() => (userMenuOpen = !userMenuOpen)}>
<DropdownToggle nav caret>@{user.username}</DropdownToggle>
<DropdownMenu>
<DropdownItem href="/@{user.username}">View profile</DropdownItem>
<DropdownItem href="/settings">Settings</DropdownItem>
<DropdownItem divider />
<DropdownItem href="/logout">Log out</DropdownItem>
</DropdownMenu>
</Dropdown>
</NavItem>
{:else}
<NavItem>
<Dropdown nav isOpen={userMenuOpen} toggle={() => (userMenuOpen = !userMenuOpen)}>
<DropdownToggle nav caret>Not logged in</DropdownToggle>
<DropdownMenu>
<DropdownItem href="/login">Log in</DropdownItem>
</DropdownMenu>
</Dropdown>
</NavItem>
{/if}
</Nav>
</Collapse>
</Navbar>

View file

@ -0,0 +1,72 @@
import { PUBLIC_API_BASE } from "$env/static/public";
export type RequestParams = {
token?: string;
body?: any;
headers?: Record<string, string>;
};
/**
* 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<T>(
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<void> {
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();
}

View file

@ -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<Meta>(fetch, "GET", "/meta");
let user: User | undefined;
try {
user = await request<User>(fetch, "GET", "/users/@me");
} catch {}
return { meta, user };
}

View file

@ -0,0 +1,11 @@
<script lang="ts">
import Navbar from "$lib/nav/Navbar.svelte";
import "bootstrap/dist/css/bootstrap.min.css";
import type { LayoutData } from "./$types";
export let data: LayoutData;
</script>
<Navbar user={data.user} />
<slot />

View file

@ -1,2 +1,19 @@
<script lang="ts">
import type { PageData } from "./$types";
export let data: PageData;
</script>
<h1>Welcome to SvelteKit</h1> <h1>Welcome to SvelteKit</h1>
<p>Visit <a href="https://kit.svelte.dev">kit.svelte.dev</a> to read the documentation</p> <p>Visit <a href="https://kit.svelte.dev">kit.svelte.dev</a> to read the documentation</p>
<p>
are you logged in? {data.user !== undefined}
{#if data.user}
<br />hello, {data.user.username}!
<br />your ID: {data.user.id}
{/if}
</p>
<p>
stats: {data.meta.users.total} users, {data.meta.members} members
</p>