you know what let's just change frontend framework again
This commit is contained in:
parent
c8cd483d20
commit
0d47f1fb01
115 changed files with 4407 additions and 10824 deletions
54
Foxnouns.Frontend/src/lib/api/error.ts
Normal file
54
Foxnouns.Frontend/src/lib/api/error.ts
Normal file
|
@ -0,0 +1,54 @@
|
|||
export default class ApiError {
|
||||
raw?: RawApiError;
|
||||
code: ErrorCode;
|
||||
|
||||
constructor(err?: RawApiError, code?: ErrorCode) {
|
||||
this.raw = err;
|
||||
this.code = err?.code || code || ErrorCode.InternalServerError;
|
||||
}
|
||||
|
||||
get obj(): RawApiError {
|
||||
return this.toObject();
|
||||
}
|
||||
|
||||
toObject(): RawApiError {
|
||||
return {
|
||||
status: this.raw?.status || 500,
|
||||
code: this.code,
|
||||
message: this.raw?.message || "Internal server error",
|
||||
errors: this.raw?.errors,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export type RawApiError = {
|
||||
status: number;
|
||||
message: string;
|
||||
code: ErrorCode;
|
||||
errors?: Array<{ key: string; errors: ValidationError[] }>;
|
||||
};
|
||||
|
||||
export enum ErrorCode {
|
||||
InternalServerError = "INTERNAL_SERVER_ERROR",
|
||||
Forbidden = "FORBIDDEN",
|
||||
BadRequest = "BAD_REQUEST",
|
||||
AuthenticationError = "AUTHENTICATION_ERROR",
|
||||
AuthenticationRequired = "AUTHENTICATION_REQUIRED",
|
||||
MissingScopes = "MISSING_SCOPES",
|
||||
GenericApiError = "GENERIC_API_ERROR",
|
||||
UserNotFound = "USER_NOT_FOUND",
|
||||
MemberNotFound = "MEMBER_NOT_FOUND",
|
||||
AccountAlreadyLinked = "ACCOUNT_ALREADY_LINKED",
|
||||
LastAuthMethod = "LAST_AUTH_METHOD",
|
||||
// This code isn't actually returned by the API
|
||||
Non204Response = "(non 204 response)",
|
||||
}
|
||||
|
||||
export type ValidationError = {
|
||||
message: string;
|
||||
min_length?: number;
|
||||
max_length?: number;
|
||||
actual_length?: number;
|
||||
allowed_values?: any[];
|
||||
actual_value?: any;
|
||||
};
|
92
Foxnouns.Frontend/src/lib/api/index.ts
Normal file
92
Foxnouns.Frontend/src/lib/api/index.ts
Normal file
|
@ -0,0 +1,92 @@
|
|||
import { PUBLIC_API_BASE } from "$env/static/public";
|
||||
import type { Cookies } from "@sveltejs/kit";
|
||||
import ApiError, { ErrorCode } from "./error";
|
||||
import { TOKEN_COOKIE_NAME } from "$lib";
|
||||
import log from "$lib/log";
|
||||
|
||||
export type Method = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
||||
|
||||
export type RequestArgs = {
|
||||
token?: string;
|
||||
isInternal?: boolean;
|
||||
body?: any;
|
||||
fetch?: typeof fetch;
|
||||
cookies?: Cookies;
|
||||
};
|
||||
|
||||
/**
|
||||
* Makes a raw request to the API.
|
||||
* @param method The HTTP method for this request
|
||||
* @param path The path for this request, without the /api/v2 prefix, starting with a slash.
|
||||
* @param args Optional arguments to the request function.
|
||||
* @returns A Promise object.
|
||||
*/
|
||||
export async function baseRequest(
|
||||
method: Method,
|
||||
path: string,
|
||||
args: RequestArgs = {},
|
||||
): Promise<Response> {
|
||||
const token = args.token ?? args.cookies?.get(TOKEN_COOKIE_NAME);
|
||||
|
||||
const fetchFn = args.fetch ?? fetch;
|
||||
const url = `${PUBLIC_API_BASE}/${args.isInternal ? "internal" : "v2"}${path}`;
|
||||
|
||||
log.debug("Sending request to %s %s", method, url);
|
||||
|
||||
const headers = {
|
||||
...(args.body ? { "Content-Type": "application/json; charset=utf-8" } : {}),
|
||||
...(token ? { Authorization: token } : {}),
|
||||
};
|
||||
|
||||
return await fetchFn(url, {
|
||||
method,
|
||||
headers,
|
||||
body: args.body ? JSON.stringify(args.body) : undefined,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a request to the API and parses the returned object.
|
||||
* @param method The HTTP method for this request
|
||||
* @param path The path for this request, without the /api/v2 prefix, starting with a slash.
|
||||
* @param args Optional arguments to the request function.
|
||||
* @returns The response deserialized as `T`.
|
||||
*/
|
||||
export async function apiRequest<T>(
|
||||
method: Method,
|
||||
path: string,
|
||||
args: RequestArgs = {},
|
||||
): Promise<T> {
|
||||
const resp = await baseRequest(method, path, args);
|
||||
|
||||
if (resp.status < 200 || resp.status > 299) {
|
||||
const err = await resp.json();
|
||||
if ("code" in err) throw new ApiError(err);
|
||||
else throw new ApiError();
|
||||
}
|
||||
return (await resp.json()) as T;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a request without reading the body (unless the API returns an error).
|
||||
* @param method The HTTP method for this request
|
||||
* @param path The path for this request, without the /api/v2 prefix, starting with a slash.
|
||||
* @param args Optional arguments to the request function.
|
||||
* @param enforce204 Whether to throw an error on a non-204 status code.
|
||||
*/
|
||||
export async function fastRequest(
|
||||
method: Method,
|
||||
path: string,
|
||||
args: RequestArgs = {},
|
||||
enforce204: boolean = false,
|
||||
): Promise<void> {
|
||||
const resp = await baseRequest(method, path, args);
|
||||
|
||||
if (resp.status < 200 || resp.status > 299) {
|
||||
const err = await resp.json();
|
||||
if ("code" in err) throw new ApiError(err);
|
||||
else throw new ApiError();
|
||||
}
|
||||
|
||||
if (enforce204 && resp.status !== 204) throw new ApiError(undefined, ErrorCode.Non204Response);
|
||||
}
|
23
Foxnouns.Frontend/src/lib/api/models/auth.ts
Normal file
23
Foxnouns.Frontend/src/lib/api/models/auth.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
import type { User } from "./user";
|
||||
|
||||
export type AuthResponse = {
|
||||
user: User;
|
||||
token: string;
|
||||
expires_at: string;
|
||||
};
|
||||
|
||||
export type CallbackResponse = {
|
||||
has_account: boolean;
|
||||
ticket?: string;
|
||||
remote_username?: string;
|
||||
user?: User;
|
||||
token?: string;
|
||||
expires_at?: string;
|
||||
};
|
||||
|
||||
export type AuthUrls = {
|
||||
email_enabled: boolean;
|
||||
discord?: string;
|
||||
google?: string;
|
||||
tumblr?: string;
|
||||
};
|
4
Foxnouns.Frontend/src/lib/api/models/index.ts
Normal file
4
Foxnouns.Frontend/src/lib/api/models/index.ts
Normal file
|
@ -0,0 +1,4 @@
|
|||
export * from "./meta";
|
||||
export * from "./user";
|
||||
export * from "./member";
|
||||
export * from "./auth";
|
9
Foxnouns.Frontend/src/lib/api/models/member.ts
Normal file
9
Foxnouns.Frontend/src/lib/api/models/member.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
import type { Field, PartialMember, PartialUser, PrideFlag } from "./user";
|
||||
|
||||
export type Member = PartialMember & {
|
||||
fields: Field[];
|
||||
flags: PrideFlag[];
|
||||
links: string[];
|
||||
|
||||
user: PartialUser;
|
||||
};
|
19
Foxnouns.Frontend/src/lib/api/models/meta.ts
Normal file
19
Foxnouns.Frontend/src/lib/api/models/meta.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
export type Meta = {
|
||||
repository: string;
|
||||
version: string;
|
||||
hash: string;
|
||||
users: {
|
||||
total: number;
|
||||
active_month: number;
|
||||
active_week: number;
|
||||
active_day: number;
|
||||
};
|
||||
members: number;
|
||||
limits: Limits;
|
||||
};
|
||||
|
||||
export type Limits = {
|
||||
member_count: number;
|
||||
bio_length: number;
|
||||
custom_preferences: number;
|
||||
};
|
139
Foxnouns.Frontend/src/lib/api/models/user.ts
Normal file
139
Foxnouns.Frontend/src/lib/api/models/user.ts
Normal file
|
@ -0,0 +1,139 @@
|
|||
export type PartialUser = {
|
||||
id: string;
|
||||
username: string;
|
||||
display_name: string | null;
|
||||
avatar_url: string | null;
|
||||
custom_preferences: Record<string, CustomPreference>;
|
||||
};
|
||||
|
||||
export type User = PartialUser & {
|
||||
bio: string | null;
|
||||
member_title: string | null;
|
||||
links: string[];
|
||||
names: FieldEntry[];
|
||||
pronouns: Pronoun[];
|
||||
fields: Field[];
|
||||
flags: PrideFlag[];
|
||||
role: "USER" | "MODERATOR" | "ADMIN";
|
||||
};
|
||||
|
||||
export type MeUser = UserWithMembers & {
|
||||
auth_methods: AuthMethod[];
|
||||
member_list_hidden: boolean;
|
||||
last_active: string;
|
||||
last_sid_reroll: string;
|
||||
};
|
||||
|
||||
export type UserWithMembers = User & { members: PartialMember[] };
|
||||
|
||||
export type UserWithHiddenFields = User & {
|
||||
auth_methods?: unknown[];
|
||||
member_list_hidden: boolean;
|
||||
last_active: string;
|
||||
};
|
||||
|
||||
export type UserSettings = {
|
||||
dark_mode: boolean | null;
|
||||
};
|
||||
|
||||
export type PartialMember = {
|
||||
id: string;
|
||||
name: string;
|
||||
display_name: string;
|
||||
bio: string | null;
|
||||
avatar_url: string | null;
|
||||
names: FieldEntry[];
|
||||
pronouns: Pronoun[];
|
||||
unlisted: boolean | null;
|
||||
};
|
||||
|
||||
export type FieldEntry = {
|
||||
value: string;
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type Pronoun = FieldEntry & { display_text: string | null };
|
||||
|
||||
export type Field = {
|
||||
name: string;
|
||||
entries: FieldEntry[];
|
||||
};
|
||||
|
||||
export type PrideFlag = {
|
||||
id: string;
|
||||
image_url: string;
|
||||
name: string;
|
||||
description: string | null;
|
||||
};
|
||||
|
||||
export type AuthMethod = {
|
||||
id: string;
|
||||
type: "DISCORD" | "GOOGLE" | "TUMBLR" | "FEDIVERSE" | "EMAIL";
|
||||
remote_id: string;
|
||||
remote_username?: string;
|
||||
};
|
||||
|
||||
export type CustomPreference = {
|
||||
icon: string;
|
||||
tooltip: string;
|
||||
muted: boolean;
|
||||
favourite: boolean;
|
||||
size: PreferenceSize;
|
||||
};
|
||||
|
||||
export enum PreferenceSize {
|
||||
Large = "LARGE",
|
||||
Normal = "NORMAL",
|
||||
Small = "SMALL",
|
||||
}
|
||||
|
||||
export function mergePreferences(
|
||||
prefs: Record<string, CustomPreference>,
|
||||
): Record<string, CustomPreference> {
|
||||
return Object.assign({}, defaultPreferences, prefs);
|
||||
}
|
||||
|
||||
export const defaultPreferences = Object.freeze({
|
||||
favourite: {
|
||||
icon: "heart-fill",
|
||||
tooltip: "Favourite",
|
||||
size: PreferenceSize.Large,
|
||||
muted: false,
|
||||
favourite: true,
|
||||
},
|
||||
okay: {
|
||||
icon: "hand-thumbs-up",
|
||||
tooltip: "Okay",
|
||||
size: PreferenceSize.Normal,
|
||||
muted: false,
|
||||
favourite: false,
|
||||
},
|
||||
jokingly: {
|
||||
icon: "emoji-laughing",
|
||||
tooltip: "Jokingly",
|
||||
size: PreferenceSize.Normal,
|
||||
muted: false,
|
||||
favourite: false,
|
||||
},
|
||||
friends_only: {
|
||||
icon: "people",
|
||||
tooltip: "Friends only",
|
||||
size: PreferenceSize.Normal,
|
||||
muted: false,
|
||||
favourite: false,
|
||||
},
|
||||
avoid: {
|
||||
icon: "hand-thumbs-down",
|
||||
tooltip: "Avoid",
|
||||
size: PreferenceSize.Small,
|
||||
muted: true,
|
||||
favourite: false,
|
||||
},
|
||||
missing: {
|
||||
icon: "question-lg",
|
||||
tooltip: "Unknown (missing)",
|
||||
size: PreferenceSize.Normal,
|
||||
muted: false,
|
||||
favourite: false,
|
||||
},
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue