stuff
This commit is contained in:
parent
14f8e77e6a
commit
a2f001392b
11 changed files with 241 additions and 2 deletions
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
|
@ -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">
|
||||||
|
|
11
Foxnouns.Frontend/src/lib/api/meta.ts
Normal file
11
Foxnouns.Frontend/src/lib/api/meta.ts
Normal 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;
|
||||||
|
}
|
9
Foxnouns.Frontend/src/lib/api/user.ts
Normal file
9
Foxnouns.Frontend/src/lib/api/user.ts
Normal 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[];
|
||||||
|
};
|
34
Foxnouns.Frontend/src/lib/nav/Logo.svelte
Normal file
34
Foxnouns.Frontend/src/lib/nav/Logo.svelte
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 7.9 KiB |
57
Foxnouns.Frontend/src/lib/nav/Navbar.svelte
Normal file
57
Foxnouns.Frontend/src/lib/nav/Navbar.svelte
Normal 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>
|
72
Foxnouns.Frontend/src/lib/request.ts
Normal file
72
Foxnouns.Frontend/src/lib/request.ts
Normal 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();
|
||||||
|
}
|
13
Foxnouns.Frontend/src/routes/+layout.server.ts
Normal file
13
Foxnouns.Frontend/src/routes/+layout.server.ts
Normal 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 };
|
||||||
|
}
|
11
Foxnouns.Frontend/src/routes/+layout.svelte
Normal file
11
Foxnouns.Frontend/src/routes/+layout.svelte
Normal 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 />
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in a new issue