summaryrefslogtreecommitdiff
path: root/client/src
diff options
context:
space:
mode:
Diffstat (limited to 'client/src')
-rw-r--r--client/src/app.d.ts14
-rw-r--r--client/src/app.html18
-rw-r--r--client/src/hooks.server.ts29
-rw-r--r--client/src/lib/ListPrevNext.svelte10
-rw-r--r--client/src/lib/api/schema.d.ts2608
-rw-r--r--client/src/lib/config.ts54
-rw-r--r--client/src/lib/fetch-client.ts10
-rw-r--r--client/src/routes/(app)/+error.svelte5
-rw-r--r--client/src/routes/(app)/+layout.svelte12
-rw-r--r--client/src/routes/(app)/+layout.ts6
-rw-r--r--client/src/routes/(app)/+page.svelte25
-rw-r--r--client/src/routes/(app)/+page.ts38
-rw-r--r--client/src/routes/+page.svelte2
-rw-r--r--client/src/routes/login/+page.server.ts36
-rw-r--r--client/src/routes/login/+page.svelte22
-rw-r--r--client/src/routes/login/+page.ts7
16 files changed, 1574 insertions, 1322 deletions
diff --git a/client/src/app.d.ts b/client/src/app.d.ts
index d76242a..520c421 100644
--- a/client/src/app.d.ts
+++ b/client/src/app.d.ts
@@ -1,13 +1,13 @@
// See https://svelte.dev/docs/kit/types#app.d.ts
// for information about these interfaces
declare global {
- namespace App {
- // interface Error {}
- // interface Locals {}
- // interface PageData {}
- // interface PageState {}
- // interface Platform {}
- }
+ namespace App {
+ // interface Error {}
+ // interface Locals {}
+ // interface PageData {}
+ // interface PageState {}
+ // interface Platform {}
+ }
}
export {};
diff --git a/client/src/app.html b/client/src/app.html
index ecd5efc..84ffad1 100644
--- a/client/src/app.html
+++ b/client/src/app.html
@@ -1,12 +1,12 @@
<!doctype html>
<html lang="en">
- <head>
- <meta charset="utf-8" />
- <link rel="icon" href="%sveltekit.assets%/favicon.png" />
- <meta name="viewport" content="width=device-width, initial-scale=1" />
- %sveltekit.head%
- </head>
- <body data-sveltekit-preload-data="hover">
- <div style="display: contents">%sveltekit.body%</div>
- </body>
+ <head>
+ <meta charset="utf-8" />
+ <link rel="icon" href="%sveltekit.assets%/favicon.png" />
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
+ %sveltekit.head%
+ </head>
+ <body data-sveltekit-preload-data="hover">
+ <div style="display: contents">%sveltekit.body%</div>
+ </body>
</html>
diff --git a/client/src/hooks.server.ts b/client/src/hooks.server.ts
new file mode 100644
index 0000000..4a7a5cb
--- /dev/null
+++ b/client/src/hooks.server.ts
@@ -0,0 +1,29 @@
+import { base } from '$app/paths';
+import { redirect, type Handle, type HandleFetch } from '@sveltejs/kit';
+
+export const handleFetch: HandleFetch = async ({ event, request, fetch }) => {
+ return fetch(request).then((resp) => {
+ if (resp.status == 401) {
+ const url = new URL(event.request.url);
+ const ret = url.pathname + url.search;
+ redirect(307, base + '/login?return=' + encodeURIComponent(ret));
+ }
+ return resp;
+ });
+};
+
+export const handle: Handle = async ({ event, resolve }) => {
+ const response = await resolve(event, {
+ filterSerializedResponseHeaders: (name) => {
+ switch (name) {
+ // used by openapi-fetch
+ case 'content-length':
+ return true;
+ default:
+ return false;
+ }
+ }
+ });
+
+ return response;
+};
diff --git a/client/src/lib/ListPrevNext.svelte b/client/src/lib/ListPrevNext.svelte
new file mode 100644
index 0000000..89a5c14
--- /dev/null
+++ b/client/src/lib/ListPrevNext.svelte
@@ -0,0 +1,10 @@
+<script lang="ts">
+ let { list, query_offset = 'offset' } = $props();
+</script>
+
+{#if list.offset > 0}
+ <a href="?{query_offset}={Math.max(0, list.offset - list.limit)}">Previous</a>
+{/if}
+{#if list.more}
+ <a href="?{query_offset}={list.offset + list.limit}">Next</a>
+{/if}
diff --git a/client/src/lib/api/schema.d.ts b/client/src/lib/api/schema.d.ts
index f69f227..ef99389 100644
--- a/client/src/lib/api/schema.d.ts
+++ b/client/src/lib/api/schema.d.ts
@@ -4,1419 +4,1419 @@
*/
export interface paths {
- "/healthcheck": {
- parameters: {
- query?: never;
- header?: never;
- path?: never;
- cookie?: never;
- };
- get: operations["healthcheck"];
- put?: never;
- post?: never;
- delete?: never;
- options?: never;
- head?: never;
- patch?: never;
- trace?: never;
+ '/healthcheck': {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
};
- "/login": {
- parameters: {
- query?: never;
- header?: never;
- path?: never;
- cookie?: never;
- };
- get?: never;
- put?: never;
- post: operations["login"];
- delete?: never;
- options?: never;
- head?: never;
- patch?: never;
- trace?: never;
+ get: operations['healthcheck'];
+ put?: never;
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ '/login': {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
};
- "/logout": {
- parameters: {
- query?: never;
- header?: never;
- path?: never;
- cookie?: never;
- };
- get: operations["logout"];
- put?: never;
- post?: never;
- delete?: never;
- options?: never;
- head?: never;
- patch?: never;
- trace?: never;
+ get?: never;
+ put?: never;
+ post: operations['login'];
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ '/logout': {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
};
- "/project/{projectid}": {
- parameters: {
- query?: never;
- header?: never;
- path?: never;
- cookie?: never;
- };
- get: operations["project"];
- put?: never;
- post: operations["project_update"];
- delete?: never;
- options?: never;
- head?: never;
- patch?: never;
- trace?: never;
+ get: operations['logout'];
+ put?: never;
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ '/project/{projectid}': {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
};
- "/project/{projectid}/new": {
- parameters: {
- query?: never;
- header?: never;
- path?: never;
- cookie?: never;
- };
- get?: never;
- put?: never;
- post: operations["project_new"];
- delete?: never;
- options?: never;
- head?: never;
- patch?: never;
- trace?: never;
+ get: operations['project'];
+ put?: never;
+ post: operations['project_update'];
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ '/project/{projectid}/new': {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
};
- "/project/{projectid}/reviews": {
- parameters: {
- query?: never;
- header?: never;
- path?: never;
- cookie?: never;
- };
- get: operations["reviews"];
- put?: never;
- post?: never;
- delete?: never;
- options?: never;
- head?: never;
- patch?: never;
- trace?: never;
+ get?: never;
+ put?: never;
+ post: operations['project_new'];
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ '/project/{projectid}/reviews': {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
};
- "/project/{projectid}/translations": {
- parameters: {
- query?: never;
- header?: never;
- path?: never;
- cookie?: never;
- };
- get: operations["translation_reviews"];
- put?: never;
- post?: never;
- delete?: never;
- options?: never;
- head?: never;
- patch?: never;
- trace?: never;
+ get: operations['reviews'];
+ put?: never;
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ '/project/{projectid}/translations': {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
};
- "/project/{projectid}/user/{userid}": {
- parameters: {
- query?: never;
- header?: never;
- path?: never;
- cookie?: never;
- };
- get?: never;
- put?: never;
- post: operations["project_user_update"];
- delete: operations["project_user_del"];
- options?: never;
- head?: never;
- patch?: never;
- trace?: never;
+ get: operations['translation_reviews'];
+ put?: never;
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ '/project/{projectid}/user/{userid}': {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
};
- "/project/{projectid}/user/{userid}/new": {
- parameters: {
- query?: never;
- header?: never;
- path?: never;
- cookie?: never;
- };
- get?: never;
- put?: never;
- post: operations["project_user_add"];
- delete?: never;
- options?: never;
- head?: never;
- patch?: never;
- trace?: never;
+ get?: never;
+ put?: never;
+ post: operations['project_user_update'];
+ delete: operations['project_user_del'];
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ '/project/{projectid}/user/{userid}/new': {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
};
- "/projects": {
- parameters: {
- query?: never;
- header?: never;
- path?: never;
- cookie?: never;
- };
- get: operations["projects"];
- put?: never;
- post?: never;
- delete?: never;
- options?: never;
- head?: never;
- patch?: never;
- trace?: never;
+ get?: never;
+ put?: never;
+ post: operations['project_user_add'];
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ '/projects': {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
};
- "/review/{projectid}": {
- parameters: {
- query?: never;
- header?: never;
- path?: never;
- cookie?: never;
- };
- get: operations["review_id"];
- put?: never;
- post?: never;
- delete: operations["review_id_del"];
- options?: never;
- head?: never;
- patch?: never;
- trace?: never;
+ get: operations['projects'];
+ put?: never;
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ '/review/{projectid}': {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
};
- "/review/{projectid}/{branch}": {
- parameters: {
- query?: never;
- header?: never;
- path?: never;
- cookie?: never;
- };
- get: operations["review"];
- put?: never;
- post?: never;
- delete: operations["review_del"];
- options?: never;
- head?: never;
- patch?: never;
- trace?: never;
+ get: operations['review_id'];
+ put?: never;
+ post?: never;
+ delete: operations['review_id_del'];
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ '/review/{projectid}/{branch}': {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
};
- "/status": {
- parameters: {
- query?: never;
- header?: never;
- path?: never;
- cookie?: never;
- };
- get: operations["status"];
- put?: never;
- post?: never;
- delete?: never;
- options?: never;
- head?: never;
- patch?: never;
- trace?: never;
+ get: operations['review'];
+ put?: never;
+ post?: never;
+ delete: operations['review_del'];
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ '/status': {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
};
- "/translation/{projectid}/new": {
- parameters: {
- query?: never;
- header?: never;
- path?: never;
- cookie?: never;
- };
- get?: never;
- put?: never;
- post: operations["translation_review_new"];
- delete?: never;
- options?: never;
- head?: never;
- patch?: never;
- trace?: never;
+ get: operations['status'];
+ put?: never;
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ '/translation/{projectid}/new': {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
};
- "/translation/{translation_reviewid}/strings": {
- parameters: {
- query?: never;
- header?: never;
- path?: never;
- cookie?: never;
- };
- get: operations["translation_review_strings"];
- put?: never;
- post?: never;
- delete?: never;
- options?: never;
- head?: never;
- patch?: never;
- trace?: never;
+ get?: never;
+ put?: never;
+ post: operations['translation_review_new'];
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ '/translation/{translation_reviewid}/strings': {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
};
- "/user/keys": {
- parameters: {
- query?: never;
- header?: never;
- path?: never;
- cookie?: never;
- };
- get: operations["user_keys"];
- put?: never;
- post?: never;
- delete?: never;
- options?: never;
- head?: never;
- patch?: never;
- trace?: never;
+ get: operations['translation_review_strings'];
+ put?: never;
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ '/user/keys': {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
};
- "/user/keys/add": {
- parameters: {
- query?: never;
- header?: never;
- path?: never;
- cookie?: never;
- };
- get?: never;
- put?: never;
- post: operations["user_key_add"];
- delete?: never;
- options?: never;
- head?: never;
- patch?: never;
- trace?: never;
+ get: operations['user_keys'];
+ put?: never;
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ '/user/keys/add': {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
};
- "/user/keys/{id}": {
- parameters: {
- query?: never;
- header?: never;
- path?: never;
- cookie?: never;
- };
- get: operations["user_key_get"];
- put?: never;
- post?: never;
- delete: operations["user_key_del"];
- options?: never;
- head?: never;
- patch?: never;
- trace?: never;
+ get?: never;
+ put?: never;
+ post: operations['user_key_add'];
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ '/user/keys/{id}': {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
};
- "/users": {
- parameters: {
- query?: never;
- header?: never;
- path?: never;
- cookie?: never;
- };
- get: operations["users"];
- put?: never;
- post?: never;
- delete?: never;
- options?: never;
- head?: never;
- patch?: never;
- trace?: never;
+ get: operations['user_key_get'];
+ put?: never;
+ post?: never;
+ delete: operations['user_key_del'];
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ '/users': {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
};
+ get: operations['users'];
+ put?: never;
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
}
export type webhooks = Record<string, never>;
export interface components {
- schemas: {
- LocalizationPlaceholder: {
- /** @example %1$d */
- content: string;
- /** @example 42 */
- example: string;
- /** @example NAME */
- id: string;
+ schemas: {
+ LocalizationPlaceholder: {
+ /** @example %1$d */
+ content: string;
+ /** @example 42 */
+ example: string;
+ /** @example NAME */
+ id: string;
+ };
+ LocalizationString: {
+ /** @example Generic greating */
+ description: string;
+ /** @example strings/strings.grd */
+ file: string;
+ /** @example IDS_GENERIC_WELCOME */
+ id: string;
+ /** @example This should be a positive greating */
+ meaning: string;
+ placeholder_offset: number[];
+ placeholders: components['schemas']['LocalizationPlaceholder'][];
+ /** @example Hello! */
+ source: string;
+ translations: components['schemas']['TranslationString'][];
+ };
+ LocalizationStrings: {
+ /**
+ * Format: int32
+ * @example 10
+ */
+ limit: number;
+ /** @example true */
+ more: boolean;
+ /**
+ * Format: int32
+ * @example 0
+ */
+ offset: number;
+ strings: components['schemas']['LocalizationString'][];
+ /**
+ * Format: int32
+ * @example 42
+ */
+ total_count: number;
+ };
+ Login: {
+ password: string;
+ username: string;
+ };
+ Project: {
+ /** @example Example project */
+ description: string;
+ /** @example fake */
+ id: string;
+ /** @example main */
+ main_branch: string;
+ /** @example ssh://git.example.org/srv/git/ */
+ remote: string;
+ /** @example b3BlbNNz...AQIDBA== */
+ remote_key_abbrev: string;
+ /** @example FAKE: Features All Kids Erase */
+ title: string;
+ users: components['schemas']['ProjectUserEntry'][];
+ };
+ ProjectData: {
+ /** @example Example project */
+ description?: string | null;
+ /** @example main */
+ main_branch?: string | null;
+ /** @example ssh://git.example.org/srv/git/ */
+ remote?: string | null;
+ /** @example b3BlbNNz...AQIDBA== */
+ remote_key?: string | null;
+ /** @example FAKE: Features All Kids Erase */
+ title?: string | null;
+ };
+ ProjectEntry: {
+ /** @example fake */
+ id: string;
+ /** @example FAKE: Features All Kids Erase */
+ title: string;
+ };
+ ProjectUserEntry: {
+ default_role: components['schemas']['UserReviewRole'];
+ /** @example false */
+ maintainer: boolean;
+ user: components['schemas']['User'];
+ };
+ ProjectUserEntryData: {
+ default_role?: null | components['schemas']['UserReviewRole'];
+ /** @example false */
+ maintainer?: boolean | null;
+ };
+ Projects: {
+ /**
+ * Format: int32
+ * @example 10
+ */
+ limit: number;
+ /** @example false */
+ more: boolean;
+ /**
+ * Format: int32
+ * @example 0
+ */
+ offset: number;
+ projects: components['schemas']['ProjectEntry'][];
+ /**
+ * Format: int32
+ * @example 1
+ */
+ total_count: number;
+ };
+ Review: {
+ /** @example false */
+ archived: boolean;
+ /** @example r/user/TASK-123456 */
+ branch: string;
+ /** @example We're adding more features because features are what we want. */
+ description: string;
+ /**
+ * Format: int64
+ * @example 1000
+ */
+ id: number;
+ owner: components['schemas']['User'];
+ /**
+ * Format: float
+ * @example 37.5
+ */
+ progress: number;
+ state: components['schemas']['ReviewState'];
+ /** @example FAKE-512: Add more features */
+ title: string;
+ users: components['schemas']['ReviewUserEntry'][];
+ };
+ ReviewEntry: {
+ /** @example r/user/TASK-123456 */
+ branch: string;
+ /**
+ * Format: int64
+ * @example 1000
+ */
+ id: number;
+ owner: components['schemas']['User'];
+ /**
+ * Format: float
+ * @example 37.5
+ */
+ progress: number;
+ state: components['schemas']['ReviewState'];
+ /** @example FAKE-512: Add more features */
+ title: string;
+ };
+ /** @enum {string} */
+ ReviewState: 'Draft' | 'Open' | 'Dropped' | 'Closed';
+ ReviewUserEntry: {
+ role: components['schemas']['UserReviewRole'];
+ user: components['schemas']['User'];
+ };
+ Reviews: {
+ /**
+ * Format: int32
+ * @example 10
+ */
+ limit: number;
+ /** @example true */
+ more: boolean;
+ /**
+ * Format: int32
+ * @example 0
+ */
+ offset: number;
+ reviews: components['schemas']['ReviewEntry'][];
+ /**
+ * Format: int32
+ * @example 42
+ */
+ total_count: number;
+ };
+ StatusResponse: {
+ ok: boolean;
+ };
+ TranslationReview: {
+ /** @example false */
+ archived: boolean;
+ /** @example d7c502b9c6b833060576a0c4da0287933d603011 */
+ base: string;
+ /** @example New translations */
+ description: string;
+ /** @example 2cecdec660a30bf3964cee645d9cee03640ef8dc */
+ head: string;
+ /**
+ * Format: int64
+ * @example 1
+ */
+ id: number;
+ owner: components['schemas']['User'];
+ /**
+ * Format: float
+ * @example 37.5
+ */
+ progress: number;
+ state: components['schemas']['ReviewState'];
+ /** @example FAKE-512: Update translations */
+ title: string;
+ users: components['schemas']['ReviewUserEntry'][];
+ };
+ TranslationReviewData: {
+ /** @example d7c502b9c6b833060576a0c4da0287933d603011 */
+ base?: string | null;
+ /** @example New translations */
+ description: string;
+ /** @example FAKE-512: Update translations */
+ title: string;
+ };
+ TranslationReviewEntry: {
+ /** @example d7c502b9c6b833060576a0c4da0287933d603011 */
+ base: string;
+ /** @example 2cecdec660a30bf3964cee645d9cee03640ef8dc */
+ head: string;
+ /**
+ * Format: int64
+ * @example 1
+ */
+ id: number;
+ owner: components['schemas']['User'];
+ /**
+ * Format: float
+ * @example 37.5
+ */
+ progress: number;
+ state: components['schemas']['ReviewState'];
+ /** @example FAKE-512: Update translations */
+ title: string;
+ };
+ TranslationReviews: {
+ /**
+ * Format: int32
+ * @example 10
+ */
+ limit: number;
+ /** @example true */
+ more: boolean;
+ /**
+ * Format: int32
+ * @example 0
+ */
+ offset: number;
+ reviews: components['schemas']['TranslationReviewEntry'][];
+ /**
+ * Format: int32
+ * @example 42
+ */
+ total_count: number;
+ };
+ /** @enum {string} */
+ TranslationState: 'Unreviewed' | 'Unchanged' | 'Approved' | 'Revert' | 'Fix';
+ TranslationString: {
+ comment: string;
+ /** @example sv */
+ language: string;
+ placeholder_offset: number[];
+ reviewer?: null | components['schemas']['User'];
+ state: components['schemas']['TranslationState'];
+ /** @example Hej! */
+ translation: string;
+ };
+ User: {
+ /** @example true */
+ active: boolean;
+ /** @example jsmith */
+ id: string;
+ /** @example John Smith */
+ name: string;
+ };
+ UserKey: {
+ /** @example user@host 1970-01-01 */
+ comment: string;
+ /** @example AAAAfoobar== */
+ data: string;
+ /**
+ * Format: int64
+ * @example 1
+ */
+ id: number;
+ /** @example ssh-rsa */
+ kind: string;
+ };
+ UserKeyData: {
+ /** @example user@host 1970-01-01 */
+ comment?: string | null;
+ /** @example AAAAfoobar== */
+ data: string;
+ /** @example ssh-rsa */
+ kind: string;
+ };
+ UserKeys: {
+ keys: components['schemas']['UserKey'][];
+ /**
+ * Format: int32
+ * @example 10
+ */
+ limit: number;
+ /** @example false */
+ more: boolean;
+ /**
+ * Format: int32
+ * @example 0
+ */
+ offset: number;
+ /**
+ * Format: int32
+ * @example 2
+ */
+ total_count: number;
+ };
+ /** @enum {string} */
+ UserReviewRole: 'Reviewer' | 'Watcher' | 'None';
+ Users: {
+ /**
+ * Format: int32
+ * @example 10
+ */
+ limit: number;
+ /** @example true */
+ more: boolean;
+ /**
+ * Format: int32
+ * @example 0
+ */
+ offset: number;
+ /**
+ * Format: int32
+ * @example 42
+ */
+ total_count: number;
+ users: components['schemas']['User'][];
+ };
+ };
+ responses: never;
+ parameters: never;
+ requestBodies: never;
+ headers: never;
+ pathItems: never;
+}
+export type $defs = Record<string, never>;
+export interface operations {
+ healthcheck: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description All good */
+ 200: {
+ headers: {
+ [name: string]: unknown;
};
- LocalizationString: {
- /** @example Generic greating */
- description: string;
- /** @example strings/strings.grd */
- file: string;
- /** @example IDS_GENERIC_WELCOME */
- id: string;
- /** @example This should be a positive greating */
- meaning: string;
- placeholder_offset: number[];
- placeholders: components["schemas"]["LocalizationPlaceholder"][];
- /** @example Hello! */
- source: string;
- translations: components["schemas"]["TranslationString"][];
+ content?: never;
+ };
+ };
+ };
+ login: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody: {
+ content: {
+ 'application/x-www-form-urlencoded': components['schemas']['Login'];
+ };
+ };
+ responses: {
+ /** @description Login successful */
+ 200: {
+ headers: {
+ [name: string]: unknown;
};
- LocalizationStrings: {
- /**
- * Format: int32
- * @example 10
- */
- limit: number;
- /** @example true */
- more: boolean;
- /**
- * Format: int32
- * @example 0
- */
- offset: number;
- strings: components["schemas"]["LocalizationString"][];
- /**
- * Format: int32
- * @example 42
- */
- total_count: number;
+ content: {
+ /** @example {
+ * "ok": true
+ * } */
+ 'application/json': components['schemas']['StatusResponse'];
};
- Login: {
- password: string;
- username: string;
+ };
+ /** @description Login failed */
+ 401: {
+ headers: {
+ [name: string]: unknown;
};
- Project: {
- /** @example Example project */
- description: string;
- /** @example fake */
- id: string;
- /** @example main */
- main_branch: string;
- /** @example ssh://git.example.org/srv/git/ */
- remote: string;
- /** @example b3BlbNNz...AQIDBA== */
- remote_key_abbrev: string;
- /** @example FAKE: Features All Kids Erase */
- title: string;
- users: components["schemas"]["ProjectUserEntry"][];
+ content: {
+ /** @example {
+ * "error": "Unauthorized",
+ * "ok": false
+ * } */
+ 'application/json': components['schemas']['StatusResponse'];
};
- ProjectData: {
- /** @example Example project */
- description?: string | null;
- /** @example main */
- main_branch?: string | null;
- /** @example ssh://git.example.org/srv/git/ */
- remote?: string | null;
- /** @example b3BlbNNz...AQIDBA== */
- remote_key?: string | null;
- /** @example FAKE: Features All Kids Erase */
- title?: string | null;
+ };
+ };
+ };
+ logout: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description Logout successful */
+ 200: {
+ headers: {
+ [name: string]: unknown;
};
- ProjectEntry: {
- /** @example fake */
- id: string;
- /** @example FAKE: Features All Kids Erase */
- title: string;
+ content: {
+ /** @example {
+ * "ok": true
+ * } */
+ 'application/json': components['schemas']['StatusResponse'];
};
- ProjectUserEntry: {
- default_role: components["schemas"]["UserReviewRole"];
- /** @example false */
- maintainer: boolean;
- user: components["schemas"]["User"];
+ };
+ };
+ };
+ project: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path: {
+ projectid: string;
+ };
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description Get project */
+ 200: {
+ headers: {
+ [name: string]: unknown;
};
- ProjectUserEntryData: {
- default_role?: null | components["schemas"]["UserReviewRole"];
- /** @example false */
- maintainer?: boolean | null;
+ content: {
+ 'application/json': components['schemas']['Project'];
};
- Projects: {
- /**
- * Format: int32
- * @example 10
- */
- limit: number;
- /** @example false */
- more: boolean;
- /**
- * Format: int32
- * @example 0
- */
- offset: number;
- projects: components["schemas"]["ProjectEntry"][];
- /**
- * Format: int32
- * @example 1
- */
- total_count: number;
+ };
+ /** @description No such project */
+ 404: {
+ headers: {
+ [name: string]: unknown;
};
- Review: {
- /** @example false */
- archived: boolean;
- /** @example r/user/TASK-123456 */
- branch: string;
- /** @example We're adding more features because features are what we want. */
- description: string;
- /**
- * Format: int64
- * @example 1000
- */
- id: number;
- owner: components["schemas"]["User"];
- /**
- * Format: float
- * @example 37.5
- */
- progress: number;
- state: components["schemas"]["ReviewState"];
- /** @example FAKE-512: Add more features */
- title: string;
- users: components["schemas"]["ReviewUserEntry"][];
+ content?: never;
+ };
+ };
+ };
+ project_update: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path: {
+ projectid: string;
+ };
+ cookie?: never;
+ };
+ requestBody: {
+ content: {
+ 'application/json': components['schemas']['ProjectData'];
+ };
+ };
+ responses: {
+ /** @description Project updated */
+ 200: {
+ headers: {
+ [name: string]: unknown;
};
- ReviewEntry: {
- /** @example r/user/TASK-123456 */
- branch: string;
- /**
- * Format: int64
- * @example 1000
- */
- id: number;
- owner: components["schemas"]["User"];
- /**
- * Format: float
- * @example 37.5
- */
- progress: number;
- state: components["schemas"]["ReviewState"];
- /** @example FAKE-512: Add more features */
- title: string;
+ content?: never;
+ };
+ /** @description Not maintainer of project */
+ 401: {
+ headers: {
+ [name: string]: unknown;
};
- /** @enum {string} */
- ReviewState: "Draft" | "Open" | "Dropped" | "Closed";
- ReviewUserEntry: {
- role: components["schemas"]["UserReviewRole"];
- user: components["schemas"]["User"];
+ content?: never;
+ };
+ /** @description No such project */
+ 404: {
+ headers: {
+ [name: string]: unknown;
};
- Reviews: {
- /**
- * Format: int32
- * @example 10
- */
- limit: number;
- /** @example true */
- more: boolean;
- /**
- * Format: int32
- * @example 0
- */
- offset: number;
- reviews: components["schemas"]["ReviewEntry"][];
- /**
- * Format: int32
- * @example 42
- */
- total_count: number;
+ content?: never;
+ };
+ };
+ };
+ project_new: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path: {
+ projectid: string;
+ };
+ cookie?: never;
+ };
+ requestBody: {
+ content: {
+ 'application/json': components['schemas']['ProjectData'];
+ };
+ };
+ responses: {
+ /** @description Project created */
+ 200: {
+ headers: {
+ [name: string]: unknown;
};
- StatusResponse: {
- ok: boolean;
+ content: {
+ 'application/json': components['schemas']['Project'];
};
- TranslationReview: {
- /** @example false */
- archived: boolean;
- /** @example d7c502b9c6b833060576a0c4da0287933d603011 */
- base: string;
- /** @example New translations */
- description: string;
- /** @example 2cecdec660a30bf3964cee645d9cee03640ef8dc */
- head: string;
- /**
- * Format: int64
- * @example 1
- */
- id: number;
- owner: components["schemas"]["User"];
- /**
- * Format: float
- * @example 37.5
- */
- progress: number;
- state: components["schemas"]["ReviewState"];
- /** @example FAKE-512: Update translations */
- title: string;
- users: components["schemas"]["ReviewUserEntry"][];
+ };
+ /** @description Project with id already exists */
+ 409: {
+ headers: {
+ [name: string]: unknown;
};
- TranslationReviewData: {
- /** @example d7c502b9c6b833060576a0c4da0287933d603011 */
- base?: string | null;
- /** @example New translations */
- description: string;
- /** @example FAKE-512: Update translations */
- title: string;
+ content?: never;
+ };
+ };
+ };
+ reviews: {
+ parameters: {
+ query?: {
+ limit?: number;
+ offset?: number;
+ };
+ header?: never;
+ path: {
+ projectid: string;
+ };
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description Get all reviews for project */
+ 200: {
+ headers: {
+ [name: string]: unknown;
};
- TranslationReviewEntry: {
- /** @example d7c502b9c6b833060576a0c4da0287933d603011 */
- base: string;
- /** @example 2cecdec660a30bf3964cee645d9cee03640ef8dc */
- head: string;
- /**
- * Format: int64
- * @example 1
- */
- id: number;
- owner: components["schemas"]["User"];
- /**
- * Format: float
- * @example 37.5
- */
- progress: number;
- state: components["schemas"]["ReviewState"];
- /** @example FAKE-512: Update translations */
- title: string;
+ content: {
+ 'application/json': components['schemas']['Reviews'];
};
- TranslationReviews: {
- /**
- * Format: int32
- * @example 10
- */
- limit: number;
- /** @example true */
- more: boolean;
- /**
- * Format: int32
- * @example 0
- */
- offset: number;
- reviews: components["schemas"]["TranslationReviewEntry"][];
- /**
- * Format: int32
- * @example 42
- */
- total_count: number;
+ };
+ /** @description No such project */
+ 404: {
+ headers: {
+ [name: string]: unknown;
};
- /** @enum {string} */
- TranslationState: "Unreviewed" | "Unchanged" | "Approved" | "Revert" | "Fix";
- TranslationString: {
- comment: string;
- /** @example sv */
- language: string;
- placeholder_offset: number[];
- reviewer?: null | components["schemas"]["User"];
- state: components["schemas"]["TranslationState"];
- /** @example Hej! */
- translation: string;
+ content?: never;
+ };
+ };
+ };
+ translation_reviews: {
+ parameters: {
+ query?: {
+ limit?: number;
+ offset?: number;
+ };
+ header?: never;
+ path: {
+ projectid: string;
+ };
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description Get all translation reviews for project */
+ 200: {
+ headers: {
+ [name: string]: unknown;
};
- User: {
- /** @example true */
- active: boolean;
- /** @example jsmith */
- id: string;
- /** @example John Smith */
- name: string;
+ content: {
+ 'application/json': components['schemas']['TranslationReviews'];
};
- UserKey: {
- /** @example user@host 1970-01-01 */
- comment: string;
- /** @example AAAAfoobar== */
- data: string;
- /**
- * Format: int64
- * @example 1
- */
- id: number;
- /** @example ssh-rsa */
- kind: string;
+ };
+ /** @description No such project */
+ 404: {
+ headers: {
+ [name: string]: unknown;
};
- UserKeyData: {
- /** @example user@host 1970-01-01 */
- comment?: string | null;
- /** @example AAAAfoobar== */
- data: string;
- /** @example ssh-rsa */
- kind: string;
+ content?: never;
+ };
+ };
+ };
+ project_user_update: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path: {
+ projectid: string;
+ userid: string;
+ };
+ cookie?: never;
+ };
+ requestBody: {
+ content: {
+ 'application/json': components['schemas']['ProjectUserEntryData'];
+ };
+ };
+ responses: {
+ /** @description User updated in project */
+ 200: {
+ headers: {
+ [name: string]: unknown;
};
- UserKeys: {
- keys: components["schemas"]["UserKey"][];
- /**
- * Format: int32
- * @example 10
- */
- limit: number;
- /** @example false */
- more: boolean;
- /**
- * Format: int32
- * @example 0
- */
- offset: number;
- /**
- * Format: int32
- * @example 2
- */
- total_count: number;
+ content?: never;
+ };
+ /** @description Not maintainer of project */
+ 401: {
+ headers: {
+ [name: string]: unknown;
};
- /** @enum {string} */
- UserReviewRole: "Reviewer" | "Watcher" | "None";
- Users: {
- /**
- * Format: int32
- * @example 10
- */
- limit: number;
- /** @example true */
- more: boolean;
- /**
- * Format: int32
- * @example 0
- */
- offset: number;
- /**
- * Format: int32
- * @example 42
- */
- total_count: number;
- users: components["schemas"]["User"][];
+ content?: never;
+ };
+ /** @description No such project, no such user or user not in project */
+ 404: {
+ headers: {
+ [name: string]: unknown;
};
+ content?: never;
+ };
};
- responses: never;
- parameters: never;
- requestBodies: never;
- headers: never;
- pathItems: never;
-}
-export type $defs = Record<string, never>;
-export interface operations {
- healthcheck: {
- parameters: {
- query?: never;
- header?: never;
- path?: never;
- cookie?: never;
- };
- requestBody?: never;
- responses: {
- /** @description All good */
- 200: {
- headers: {
- [name: string]: unknown;
- };
- content?: never;
- };
- };
+ };
+ project_user_del: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path: {
+ projectid: string;
+ userid: string;
+ };
+ cookie?: never;
};
- login: {
- parameters: {
- query?: never;
- header?: never;
- path?: never;
- cookie?: never;
+ requestBody?: never;
+ responses: {
+ /** @description User removed from project */
+ 200: {
+ headers: {
+ [name: string]: unknown;
};
- requestBody: {
- content: {
- "application/x-www-form-urlencoded": components["schemas"]["Login"];
- };
+ content?: never;
+ };
+ /** @description Not maintainer of project */
+ 401: {
+ headers: {
+ [name: string]: unknown;
};
- responses: {
- /** @description Login successful */
- 200: {
- headers: {
- [name: string]: unknown;
- };
- content: {
- /** @example {
- * "ok": true
- * } */
- "application/json": components["schemas"]["StatusResponse"];
- };
- };
- /** @description Login failed */
- 401: {
- headers: {
- [name: string]: unknown;
- };
- content: {
- /** @example {
- * "error": "Unauthorized",
- * "ok": false
- * } */
- "application/json": components["schemas"]["StatusResponse"];
- };
- };
+ content?: never;
+ };
+ /** @description No such project, no such user or user not in project */
+ 404: {
+ headers: {
+ [name: string]: unknown;
};
+ content?: never;
+ };
};
- logout: {
- parameters: {
- query?: never;
- header?: never;
- path?: never;
- cookie?: never;
+ };
+ project_user_add: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path: {
+ projectid: string;
+ userid: string;
+ };
+ cookie?: never;
+ };
+ requestBody: {
+ content: {
+ 'application/json': components['schemas']['ProjectUserEntryData'];
+ };
+ };
+ responses: {
+ /** @description User added to project */
+ 200: {
+ headers: {
+ [name: string]: unknown;
};
- requestBody?: never;
- responses: {
- /** @description Logout successful */
- 200: {
- headers: {
- [name: string]: unknown;
- };
- content: {
- /** @example {
- * "ok": true
- * } */
- "application/json": components["schemas"]["StatusResponse"];
- };
- };
+ content?: never;
+ };
+ /** @description Not maintainer of project */
+ 401: {
+ headers: {
+ [name: string]: unknown;
};
- };
- project: {
- parameters: {
- query?: never;
- header?: never;
- path: {
- projectid: string;
- };
- cookie?: never;
+ content?: never;
+ };
+ /** @description No such project */
+ 404: {
+ headers: {
+ [name: string]: unknown;
};
- requestBody?: never;
- responses: {
- /** @description Get project */
- 200: {
- headers: {
- [name: string]: unknown;
- };
- content: {
- "application/json": components["schemas"]["Project"];
- };
- };
- /** @description No such project */
- 404: {
- headers: {
- [name: string]: unknown;
- };
- content?: never;
- };
+ content?: never;
+ };
+ /** @description User already in project */
+ 409: {
+ headers: {
+ [name: string]: unknown;
};
+ content?: never;
+ };
};
- project_update: {
- parameters: {
- query?: never;
- header?: never;
- path: {
- projectid: string;
- };
- cookie?: never;
- };
- requestBody: {
- content: {
- "application/json": components["schemas"]["ProjectData"];
- };
+ };
+ projects: {
+ parameters: {
+ query?: {
+ limit?: number;
+ offset?: number;
+ };
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description Get all projects */
+ 200: {
+ headers: {
+ [name: string]: unknown;
};
- responses: {
- /** @description Project updated */
- 200: {
- headers: {
- [name: string]: unknown;
- };
- content?: never;
- };
- /** @description Not maintainer of project */
- 401: {
- headers: {
- [name: string]: unknown;
- };
- content?: never;
- };
- /** @description No such project */
- 404: {
- headers: {
- [name: string]: unknown;
- };
- content?: never;
- };
+ content: {
+ 'application/json': components['schemas']['Projects'];
};
+ };
};
- project_new: {
- parameters: {
- query?: never;
- header?: never;
- path: {
- projectid: string;
- };
- cookie?: never;
+ };
+ review_id: {
+ parameters: {
+ query: {
+ reviewid: number;
+ };
+ header?: never;
+ path: {
+ projectid: string;
+ };
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description Get review */
+ 200: {
+ headers: {
+ [name: string]: unknown;
};
- requestBody: {
- content: {
- "application/json": components["schemas"]["ProjectData"];
- };
+ content: {
+ 'application/json': components['schemas']['Review'];
};
- responses: {
- /** @description Project created */
- 200: {
- headers: {
- [name: string]: unknown;
- };
- content: {
- "application/json": components["schemas"]["Project"];
- };
- };
- /** @description Project with id already exists */
- 409: {
- headers: {
- [name: string]: unknown;
- };
- content?: never;
- };
+ };
+ /** @description No such review */
+ 404: {
+ headers: {
+ [name: string]: unknown;
};
+ content?: never;
+ };
};
- reviews: {
- parameters: {
- query?: {
- limit?: number;
- offset?: number;
- };
- header?: never;
- path: {
- projectid: string;
- };
- cookie?: never;
- };
- requestBody?: never;
- responses: {
- /** @description Get all reviews for project */
- 200: {
- headers: {
- [name: string]: unknown;
- };
- content: {
- "application/json": components["schemas"]["Reviews"];
- };
- };
- /** @description No such project */
- 404: {
- headers: {
- [name: string]: unknown;
- };
- content?: never;
- };
- };
+ };
+ review_id_del: {
+ parameters: {
+ query: {
+ reviewid: number;
+ };
+ header?: never;
+ path: {
+ projectid: string;
+ };
+ cookie?: never;
};
- translation_reviews: {
- parameters: {
- query?: {
- limit?: number;
- offset?: number;
- };
- header?: never;
- path: {
- projectid: string;
- };
- cookie?: never;
- };
- requestBody?: never;
- responses: {
- /** @description Get all translation reviews for project */
- 200: {
- headers: {
- [name: string]: unknown;
- };
- content: {
- "application/json": components["schemas"]["TranslationReviews"];
- };
- };
- /** @description No such project */
- 404: {
- headers: {
- [name: string]: unknown;
- };
- content?: never;
- };
+ requestBody?: never;
+ responses: {
+ /** @description Remove deleted */
+ 200: {
+ headers: {
+ [name: string]: unknown;
};
- };
- project_user_update: {
- parameters: {
- query?: never;
- header?: never;
- path: {
- projectid: string;
- userid: string;
- };
- cookie?: never;
+ content?: never;
+ };
+ /** @description Review is open or closed */
+ 400: {
+ headers: {
+ [name: string]: unknown;
};
- requestBody: {
- content: {
- "application/json": components["schemas"]["ProjectUserEntryData"];
- };
+ content?: never;
+ };
+ /** @description Not owner of review or maintainer of project */
+ 401: {
+ headers: {
+ [name: string]: unknown;
};
- responses: {
- /** @description User updated in project */
- 200: {
- headers: {
- [name: string]: unknown;
- };
- content?: never;
- };
- /** @description Not maintainer of project */
- 401: {
- headers: {
- [name: string]: unknown;
- };
- content?: never;
- };
- /** @description No such project, no such user or user not in project */
- 404: {
- headers: {
- [name: string]: unknown;
- };
- content?: never;
- };
+ content?: never;
+ };
+ /** @description No such review */
+ 404: {
+ headers: {
+ [name: string]: unknown;
};
+ content?: never;
+ };
};
- project_user_del: {
- parameters: {
- query?: never;
- header?: never;
- path: {
- projectid: string;
- userid: string;
- };
- cookie?: never;
- };
- requestBody?: never;
- responses: {
- /** @description User removed from project */
- 200: {
- headers: {
- [name: string]: unknown;
- };
- content?: never;
- };
- /** @description Not maintainer of project */
- 401: {
- headers: {
- [name: string]: unknown;
- };
- content?: never;
- };
- /** @description No such project, no such user or user not in project */
- 404: {
- headers: {
- [name: string]: unknown;
- };
- content?: never;
- };
- };
+ };
+ review: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path: {
+ branch: string;
+ projectid: string;
+ };
+ cookie?: never;
};
- project_user_add: {
- parameters: {
- query?: never;
- header?: never;
- path: {
- projectid: string;
- userid: string;
- };
- cookie?: never;
+ requestBody?: never;
+ responses: {
+ /** @description Get review */
+ 200: {
+ headers: {
+ [name: string]: unknown;
};
- requestBody: {
- content: {
- "application/json": components["schemas"]["ProjectUserEntryData"];
- };
+ content: {
+ 'application/json': components['schemas']['Review'];
};
- responses: {
- /** @description User added to project */
- 200: {
- headers: {
- [name: string]: unknown;
- };
- content?: never;
- };
- /** @description Not maintainer of project */
- 401: {
- headers: {
- [name: string]: unknown;
- };
- content?: never;
- };
- /** @description No such project */
- 404: {
- headers: {
- [name: string]: unknown;
- };
- content?: never;
- };
- /** @description User already in project */
- 409: {
- headers: {
- [name: string]: unknown;
- };
- content?: never;
- };
+ };
+ /** @description No such review */
+ 404: {
+ headers: {
+ [name: string]: unknown;
};
+ content?: never;
+ };
};
- projects: {
- parameters: {
- query?: {
- limit?: number;
- offset?: number;
- };
- header?: never;
- path?: never;
- cookie?: never;
- };
- requestBody?: never;
- responses: {
- /** @description Get all projects */
- 200: {
- headers: {
- [name: string]: unknown;
- };
- content: {
- "application/json": components["schemas"]["Projects"];
- };
- };
- };
+ };
+ review_del: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path: {
+ branch: string;
+ projectid: string;
+ };
+ cookie?: never;
};
- review_id: {
- parameters: {
- query: {
- reviewid: number;
- };
- header?: never;
- path: {
- projectid: string;
- };
- cookie?: never;
+ requestBody?: never;
+ responses: {
+ /** @description Review deleted */
+ 200: {
+ headers: {
+ [name: string]: unknown;
};
- requestBody?: never;
- responses: {
- /** @description Get review */
- 200: {
- headers: {
- [name: string]: unknown;
- };
- content: {
- "application/json": components["schemas"]["Review"];
- };
- };
- /** @description No such review */
- 404: {
- headers: {
- [name: string]: unknown;
- };
- content?: never;
- };
+ content?: never;
+ };
+ /** @description Review is open or closed */
+ 400: {
+ headers: {
+ [name: string]: unknown;
};
- };
- review_id_del: {
- parameters: {
- query: {
- reviewid: number;
- };
- header?: never;
- path: {
- projectid: string;
- };
- cookie?: never;
+ content?: never;
+ };
+ /** @description Not owner of review or maintainer of project */
+ 401: {
+ headers: {
+ [name: string]: unknown;
};
- requestBody?: never;
- responses: {
- /** @description Remove deleted */
- 200: {
- headers: {
- [name: string]: unknown;
- };
- content?: never;
- };
- /** @description Review is open or closed */
- 400: {
- headers: {
- [name: string]: unknown;
- };
- content?: never;
- };
- /** @description Not owner of review or maintainer of project */
- 401: {
- headers: {
- [name: string]: unknown;
- };
- content?: never;
- };
- /** @description No such review */
- 404: {
- headers: {
- [name: string]: unknown;
- };
- content?: never;
- };
+ content?: never;
+ };
+ /** @description No such review */
+ 404: {
+ headers: {
+ [name: string]: unknown;
};
+ content?: never;
+ };
+ };
+ };
+ status: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
};
- review: {
- parameters: {
- query?: never;
- header?: never;
- path: {
- branch: string;
- projectid: string;
- };
- cookie?: never;
+ requestBody?: never;
+ responses: {
+ /** @description Current status */
+ 200: {
+ headers: {
+ [name: string]: unknown;
};
- requestBody?: never;
- responses: {
- /** @description Get review */
- 200: {
- headers: {
- [name: string]: unknown;
- };
- content: {
- "application/json": components["schemas"]["Review"];
- };
- };
- /** @description No such review */
- 404: {
- headers: {
- [name: string]: unknown;
- };
- content?: never;
- };
+ content: {
+ /** @example {
+ * "ok": true
+ * } */
+ 'application/json': components['schemas']['StatusResponse'];
};
- };
- review_del: {
- parameters: {
- query?: never;
- header?: never;
- path: {
- branch: string;
- projectid: string;
- };
- cookie?: never;
+ };
+ /** @description Not authorized */
+ 401: {
+ headers: {
+ [name: string]: unknown;
};
- requestBody?: never;
- responses: {
- /** @description Review deleted */
- 200: {
- headers: {
- [name: string]: unknown;
- };
- content?: never;
- };
- /** @description Review is open or closed */
- 400: {
- headers: {
- [name: string]: unknown;
- };
- content?: never;
- };
- /** @description Not owner of review or maintainer of project */
- 401: {
- headers: {
- [name: string]: unknown;
- };
- content?: never;
- };
- /** @description No such review */
- 404: {
- headers: {
- [name: string]: unknown;
- };
- content?: never;
- };
+ content: {
+ /** @example {
+ * "error": "Unauthorized",
+ * "ok": false
+ * } */
+ 'application/json': components['schemas']['StatusResponse'];
};
+ };
+ };
+ };
+ translation_review_new: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path: {
+ projectid: string;
+ };
+ cookie?: never;
+ };
+ requestBody: {
+ content: {
+ 'application/json': components['schemas']['TranslationReviewData'];
+ };
};
- status: {
- parameters: {
- query?: never;
- header?: never;
- path?: never;
- cookie?: never;
+ responses: {
+ /** @description Translation review created */
+ 200: {
+ headers: {
+ [name: string]: unknown;
};
- requestBody?: never;
- responses: {
- /** @description Current status */
- 200: {
- headers: {
- [name: string]: unknown;
- };
- content: {
- /** @example {
- * "ok": true
- * } */
- "application/json": components["schemas"]["StatusResponse"];
- };
- };
- /** @description Not authorized */
- 401: {
- headers: {
- [name: string]: unknown;
- };
- content: {
- /** @example {
- * "error": "Unauthorized",
- * "ok": false
- * } */
- "application/json": components["schemas"]["StatusResponse"];
- };
- };
+ content: {
+ 'application/json': components['schemas']['TranslationReview'];
};
+ };
};
- translation_review_new: {
- parameters: {
- query?: never;
- header?: never;
- path: {
- projectid: string;
- };
- cookie?: never;
+ };
+ translation_review_strings: {
+ parameters: {
+ query?: {
+ limit?: number;
+ offset?: number;
+ };
+ header?: never;
+ path: {
+ translation_reviewid: number;
+ };
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description Get all strings for a translation review */
+ 200: {
+ headers: {
+ [name: string]: unknown;
};
- requestBody: {
- content: {
- "application/json": components["schemas"]["TranslationReviewData"];
- };
+ content: {
+ 'application/json': components['schemas']['LocalizationStrings'];
};
- responses: {
- /** @description Translation review created */
- 200: {
- headers: {
- [name: string]: unknown;
- };
- content: {
- "application/json": components["schemas"]["TranslationReview"];
- };
- };
+ };
+ /** @description No such translation review */
+ 404: {
+ headers: {
+ [name: string]: unknown;
};
+ content?: never;
+ };
};
- translation_review_strings: {
- parameters: {
- query?: {
- limit?: number;
- offset?: number;
- };
- header?: never;
- path: {
- translation_reviewid: number;
- };
- cookie?: never;
- };
- requestBody?: never;
- responses: {
- /** @description Get all strings for a translation review */
- 200: {
- headers: {
- [name: string]: unknown;
- };
- content: {
- "application/json": components["schemas"]["LocalizationStrings"];
- };
- };
- /** @description No such translation review */
- 404: {
- headers: {
- [name: string]: unknown;
- };
- content?: never;
- };
- };
+ };
+ user_keys: {
+ parameters: {
+ query?: {
+ limit?: number;
+ offset?: number;
+ };
+ header?: never;
+ path?: never;
+ cookie?: never;
};
- user_keys: {
- parameters: {
- query?: {
- limit?: number;
- offset?: number;
- };
- header?: never;
- path?: never;
- cookie?: never;
+ requestBody?: never;
+ responses: {
+ /** @description Get all keys for user */
+ 200: {
+ headers: {
+ [name: string]: unknown;
};
- requestBody?: never;
- responses: {
- /** @description Get all keys for user */
- 200: {
- headers: {
- [name: string]: unknown;
- };
- content: {
- "application/json": components["schemas"]["UserKeys"];
- };
- };
+ content: {
+ 'application/json': components['schemas']['UserKeys'];
};
+ };
};
- user_key_add: {
- parameters: {
- query?: never;
- header?: never;
- path?: never;
- cookie?: never;
+ };
+ user_key_add: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody: {
+ content: {
+ 'application/json': components['schemas']['UserKeyData'];
+ };
+ };
+ responses: {
+ /** @description Key added to current user */
+ 200: {
+ headers: {
+ [name: string]: unknown;
};
- requestBody: {
- content: {
- "application/json": components["schemas"]["UserKeyData"];
- };
+ content: {
+ 'application/json': components['schemas']['UserKey'];
};
- responses: {
- /** @description Key added to current user */
- 200: {
- headers: {
- [name: string]: unknown;
- };
- content: {
- "application/json": components["schemas"]["UserKey"];
- };
- };
- /** @description Key too large or invalid */
- 400: {
- headers: {
- [name: string]: unknown;
- };
- content?: never;
- };
+ };
+ /** @description Key too large or invalid */
+ 400: {
+ headers: {
+ [name: string]: unknown;
};
+ content?: never;
+ };
+ };
+ };
+ user_key_get: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path: {
+ id: number;
+ };
+ cookie?: never;
};
- user_key_get: {
- parameters: {
- query?: never;
- header?: never;
- path: {
- id: number;
- };
- cookie?: never;
+ requestBody?: never;
+ responses: {
+ /** @description User key */
+ 200: {
+ headers: {
+ [name: string]: unknown;
};
- requestBody?: never;
- responses: {
- /** @description User key */
- 200: {
- headers: {
- [name: string]: unknown;
- };
- content: {
- "application/json": components["schemas"]["UserKey"];
- };
- };
- /** @description No such key */
- 404: {
- headers: {
- [name: string]: unknown;
- };
- content?: never;
- };
+ content: {
+ 'application/json': components['schemas']['UserKey'];
};
+ };
+ /** @description No such key */
+ 404: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content?: never;
+ };
+ };
+ };
+ user_key_del: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path: {
+ id: number;
+ };
+ cookie?: never;
};
- user_key_del: {
- parameters: {
- query?: never;
- header?: never;
- path: {
- id: number;
- };
- cookie?: never;
+ requestBody?: never;
+ responses: {
+ /** @description Key removed from current user */
+ 200: {
+ headers: {
+ [name: string]: unknown;
};
- requestBody?: never;
- responses: {
- /** @description Key removed from current user */
- 200: {
- headers: {
- [name: string]: unknown;
- };
- content?: never;
- };
- /** @description No such key for current user */
- 404: {
- headers: {
- [name: string]: unknown;
- };
- content?: never;
- };
+ content?: never;
+ };
+ /** @description No such key for current user */
+ 404: {
+ headers: {
+ [name: string]: unknown;
};
+ content?: never;
+ };
+ };
+ };
+ users: {
+ parameters: {
+ query?: {
+ limit?: number;
+ offset?: number;
+ };
+ header?: never;
+ path?: never;
+ cookie?: never;
};
- users: {
- parameters: {
- query?: {
- limit?: number;
- offset?: number;
- };
- header?: never;
- path?: never;
- cookie?: never;
+ requestBody?: never;
+ responses: {
+ /** @description Get all users */
+ 200: {
+ headers: {
+ [name: string]: unknown;
};
- requestBody?: never;
- responses: {
- /** @description Get all users */
- 200: {
- headers: {
- [name: string]: unknown;
- };
- content: {
- "application/json": components["schemas"]["Users"];
- };
- };
+ content: {
+ 'application/json': components['schemas']['Users'];
};
+ };
};
+ };
}
diff --git a/client/src/lib/config.ts b/client/src/lib/config.ts
new file mode 100644
index 0000000..fccb09d
--- /dev/null
+++ b/client/src/lib/config.ts
@@ -0,0 +1,54 @@
+import { browser } from '$app/environment';
+import type { Infer } from 'superstruct';
+import { assert, object, optional, number, string } from 'superstruct';
+
+const Config = object({
+ active_project: optional(string())
+});
+
+type Config = Infer<typeof Config>;
+
+const CachedConfig = object({
+ config: Config,
+ expires: number()
+});
+
+type CachedConfig = Infer<typeof CachedConfig>;
+
+const CACHE_TTL = 10 * 60 * 60 * 1000;
+
+async function get_config(): Promise<Config> {
+ // Cache, might be outdated but saves on call to server
+ if (browser) {
+ try {
+ // TODO: Use async localStorage
+ const stored = localStorage.getItem('config');
+ if (stored !== null) {
+ const cached: CachedConfig = JSON.parse(stored);
+ assert(cached, CachedConfig);
+ if (cached.expires < Date.now()) {
+ // TODO: Should we update expires here?
+ // If page is in use we probably don't need to sync for a while.
+ return cached.config;
+ }
+ }
+ } catch {
+ // ignore errors
+ }
+ }
+
+ // Default config
+ // eslint-disable-next-line prefer-const
+ let config: Config = { active_project: undefined };
+
+ // TODO: Fetch config
+
+ if (browser) {
+ const cached: CachedConfig = { config: config, expires: Date.now() + CACHE_TTL };
+ // TODO: Use async localStorage
+ localStorage.setItem('config', JSON.stringify(cached));
+ }
+ return config;
+}
+
+export { type Config, get_config };
diff --git a/client/src/lib/fetch-client.ts b/client/src/lib/fetch-client.ts
new file mode 100644
index 0000000..b0b60bd
--- /dev/null
+++ b/client/src/lib/fetch-client.ts
@@ -0,0 +1,10 @@
+import { PUBLIC_BASE_URL } from '$env/static/public';
+import createClient from 'openapi-fetch';
+import type { paths } from './api/schema.d.ts';
+
+const client = createClient<paths>({
+ // TODO: Can we make this relative?
+ baseUrl: PUBLIC_BASE_URL + '/api/v1'
+});
+
+export { client };
diff --git a/client/src/routes/(app)/+error.svelte b/client/src/routes/(app)/+error.svelte
new file mode 100644
index 0000000..63f3d66
--- /dev/null
+++ b/client/src/routes/(app)/+error.svelte
@@ -0,0 +1,5 @@
+<script lang="ts">
+ import { page } from '$app/state';
+</script>
+
+<h1>{page.status} {page.error?.message}</h1>
diff --git a/client/src/routes/(app)/+layout.svelte b/client/src/routes/(app)/+layout.svelte
new file mode 100644
index 0000000..47dc736
--- /dev/null
+++ b/client/src/routes/(app)/+layout.svelte
@@ -0,0 +1,12 @@
+<script lang="ts">
+ let { children } = $props();
+</script>
+
+<h1>eyeballs</h1>
+
+<nav>
+ <a href="/">Dashboard</a>
+ <a href="/settings">Settings</a>
+</nav>
+
+{@render children()}
diff --git a/client/src/routes/(app)/+layout.ts b/client/src/routes/(app)/+layout.ts
new file mode 100644
index 0000000..08366b0
--- /dev/null
+++ b/client/src/routes/(app)/+layout.ts
@@ -0,0 +1,6 @@
+import type { LayoutLoad } from './$types';
+
+export const load: LayoutLoad = () => {
+ // TODO: Decrypt sessioncookie if set, if not set, redirect to /login
+ return {};
+};
diff --git a/client/src/routes/(app)/+page.svelte b/client/src/routes/(app)/+page.svelte
new file mode 100644
index 0000000..945945e
--- /dev/null
+++ b/client/src/routes/(app)/+page.svelte
@@ -0,0 +1,25 @@
+<script lang="ts">
+ import type { PageProps } from './$types';
+ import ListPrevNext from '$lib/ListPrevNext.svelte';
+ let { data }: PageProps = $props();
+</script>
+
+<h1>Reviews</h1>
+
+{#if data.reviews === undefined}
+ Select active project
+
+ <ul>
+ {#each data.projects!!.projects as { id, title } (id)}
+ <li>{title}</li>
+ {/each}
+ </ul>
+ <ListPrevNext list={data.projects} query_offset="project_offset" />
+{:else}
+ <ul>
+ {#each data.reviews.reviews as { id, title } (id)}
+ <li>{title}</li>
+ {/each}
+ </ul>
+ <ListPrevNext list={data.reviews} />
+{/if}
diff --git a/client/src/routes/(app)/+page.ts b/client/src/routes/(app)/+page.ts
new file mode 100644
index 0000000..b6b923e
--- /dev/null
+++ b/client/src/routes/(app)/+page.ts
@@ -0,0 +1,38 @@
+import { client } from '$lib/fetch-client';
+import { get_config } from '$lib/config';
+import type { PageLoad } from './$types';
+
+function maybeInt(input: string | null, fallback: number): number {
+ if (input === null) return fallback;
+ try {
+ return parseInt(input);
+ } catch {
+ return fallback;
+ }
+}
+
+export const load: PageLoad = async ({ fetch, url }) => {
+ const config = await get_config();
+ if (config.active_project === undefined) {
+ const projects = await client.GET('/projects', {
+ params: { query: { offset: maybeInt(url.searchParams.get('projects_offset'), 0) } },
+ fetch
+ });
+ return {
+ projects: projects.data!!,
+ reviews: undefined
+ };
+ } else {
+ const reviews = await client.GET('/project/{projectid}/reviews', {
+ params: {
+ path: { projectid: config.active_project },
+ query: { offset: maybeInt(url.searchParams.get('reviews_offset'), 0) }
+ },
+ fetch
+ });
+ return {
+ projects: undefined,
+ reviews: reviews.data
+ };
+ }
+};
diff --git a/client/src/routes/+page.svelte b/client/src/routes/+page.svelte
deleted file mode 100644
index cc88df0..0000000
--- a/client/src/routes/+page.svelte
+++ /dev/null
@@ -1,2 +0,0 @@
-<h1>Welcome to SvelteKit</h1>
-<p>Visit <a href="https://svelte.dev/docs/kit">svelte.dev/docs/kit</a> to read the documentation</p>
diff --git a/client/src/routes/login/+page.server.ts b/client/src/routes/login/+page.server.ts
new file mode 100644
index 0000000..738b8ad
--- /dev/null
+++ b/client/src/routes/login/+page.server.ts
@@ -0,0 +1,36 @@
+import { redirect } from '@sveltejs/kit';
+import { base } from '$app/paths';
+import type { Actions } from './$types';
+import { client } from '$lib/fetch-client';
+
+export const actions = {
+ default: async ({ request, fetch }) => {
+ const data = await request.formData();
+ const username = data.get('username');
+ const password = data.get('password');
+ const ret = data.get('return');
+
+ const login = await client.POST('/login', {
+ body: {
+ username: username?.toString() || '',
+ password: password?.toString() || ''
+ },
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded'
+ },
+ fetch
+ });
+ if (login.data?.ok === true) {
+ if (ret) {
+ redirect(303, ret.toString());
+ } else {
+ redirect(303, base);
+ }
+ } else {
+ return {
+ error: true,
+ username: username
+ };
+ }
+ }
+} satisfies Actions;
diff --git a/client/src/routes/login/+page.svelte b/client/src/routes/login/+page.svelte
new file mode 100644
index 0000000..8a91125
--- /dev/null
+++ b/client/src/routes/login/+page.svelte
@@ -0,0 +1,22 @@
+<script lang="ts">
+ import type { PageProps } from './$types';
+
+ let { data, form }: PageProps = $props();
+</script>
+
+{#if form?.error}
+ <p>Unknown username or password</p>
+{/if}
+
+<form method="POST">
+ <label>
+ Username
+ <input name="username" type="text" value={form?.username} />
+ </label>
+ <label>
+ Password
+ <input name="password" type="password" />
+ </label>
+ <button>Log in</button>
+ <input type="hidden" name="return" value={data.return} />
+</form>
diff --git a/client/src/routes/login/+page.ts b/client/src/routes/login/+page.ts
new file mode 100644
index 0000000..70d306a
--- /dev/null
+++ b/client/src/routes/login/+page.ts
@@ -0,0 +1,7 @@
+import type { PageLoad } from './$types';
+
+export const load: PageLoad = async ({ url }) => {
+ return {
+ return: url.searchParams.get('return') || ''
+ };
+};