stuff
This commit is contained in:
parent
14f8e77e6a
commit
a2f001392b
11 changed files with 241 additions and 2 deletions
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -4,6 +4,13 @@
|
|||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||
<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%
|
||||
</head>
|
||||
<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>
|
||||
<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