feat: docs
This commit is contained in:
parent
6ab00206df
commit
d382485a7c
@ -1,6 +1,7 @@
|
|||||||
.direnv
|
.direnv
|
||||||
.env
|
.env
|
||||||
build
|
build
|
||||||
|
result
|
||||||
|
|
||||||
# Client
|
# Client
|
||||||
/client/node_modules
|
/client/node_modules
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,6 +1,7 @@
|
|||||||
.direnv
|
.direnv
|
||||||
.env
|
.env
|
||||||
build
|
build
|
||||||
|
result
|
||||||
|
|
||||||
# Client
|
# Client
|
||||||
/client/node_modules
|
/client/node_modules
|
||||||
|
3947
client/package-lock.json
generated
3947
client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -18,11 +18,14 @@
|
|||||||
"@connectrpc/connect-web": "^2.0.2",
|
"@connectrpc/connect-web": "^2.0.2",
|
||||||
"@eslint/compat": "^1.2.5",
|
"@eslint/compat": "^1.2.5",
|
||||||
"@eslint/js": "^9.18.0",
|
"@eslint/js": "^9.18.0",
|
||||||
|
"@lucide/svelte": "^0.479.0",
|
||||||
|
"@scalar/api-reference": "^1.28.1",
|
||||||
"@sveltejs/adapter-static": "^3.0.8",
|
"@sveltejs/adapter-static": "^3.0.8",
|
||||||
"@sveltejs/kit": "^2.16.0",
|
"@sveltejs/kit": "^2.16.0",
|
||||||
"@sveltejs/vite-plugin-svelte": "^5.0.0",
|
"@sveltejs/vite-plugin-svelte": "^5.0.0",
|
||||||
"@tailwindcss/vite": "^4.0.13",
|
"@tailwindcss/vite": "^4.0.13",
|
||||||
"bits-ui": "^1.3.11",
|
"bits-ui": "^1.3.11",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
"eslint": "^9.18.0",
|
"eslint": "^9.18.0",
|
||||||
"eslint-config-prettier": "^10.0.1",
|
"eslint-config-prettier": "^10.0.1",
|
||||||
"eslint-plugin-svelte": "^3.0.0",
|
"eslint-plugin-svelte": "^3.0.0",
|
||||||
@ -32,7 +35,10 @@
|
|||||||
"prettier-plugin-tailwindcss": "^0.6.11",
|
"prettier-plugin-tailwindcss": "^0.6.11",
|
||||||
"svelte": "^5.0.0",
|
"svelte": "^5.0.0",
|
||||||
"svelte-check": "^4.0.0",
|
"svelte-check": "^4.0.0",
|
||||||
|
"svelte-sonner": "^0.3.28",
|
||||||
|
"tailwind-merge": "^3.0.2",
|
||||||
"tailwindcss": "^4.0.13",
|
"tailwindcss": "^4.0.13",
|
||||||
|
"tw-animate-css": "^1.2.0",
|
||||||
"typescript": "^5.0.0",
|
"typescript": "^5.0.0",
|
||||||
"typescript-eslint": "^8.20.0",
|
"typescript-eslint": "^8.20.0",
|
||||||
"vite": "^6.0.0"
|
"vite": "^6.0.0"
|
||||||
|
@ -1 +1,22 @@
|
|||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
|
@import "tw-animate-css";
|
||||||
|
|
||||||
|
@theme {
|
||||||
|
--color-crust: #11111b;
|
||||||
|
--color-mantle: #181825;
|
||||||
|
--color-base: #1e1e2e;
|
||||||
|
|
||||||
|
--color-surface-0: #313244;
|
||||||
|
--color-surface-1: #45475a;
|
||||||
|
--color-surface-2: #585b70;
|
||||||
|
|
||||||
|
--color-overlay-0: #6c7086;
|
||||||
|
--color-overlay-1: #7f849c;
|
||||||
|
--color-overlay-2: #9399b2;
|
||||||
|
|
||||||
|
--color-subtext-0: #a6adc8;
|
||||||
|
--color-subtext-1: #bac2de;
|
||||||
|
|
||||||
|
--color-text: #cdd6f4;
|
||||||
|
--color-sky: #89dceb;
|
||||||
|
}
|
@ -6,7 +6,7 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
%sveltekit.head%
|
%sveltekit.head%
|
||||||
</head>
|
</head>
|
||||||
<body data-sveltekit-preload-data="hover">
|
<body data-sveltekit-preload-data="tap" class="min-h-screen bg-base text-text">
|
||||||
<div style="display: contents">%sveltekit.body%</div>
|
<div style="display: contents">%sveltekit.body%</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -10,7 +10,7 @@ import type { Message } from "@bufbuild/protobuf";
|
|||||||
* Describes the file user/v1/auth.proto.
|
* Describes the file user/v1/auth.proto.
|
||||||
*/
|
*/
|
||||||
export const file_user_v1_auth: GenFile = /*@__PURE__*/
|
export const file_user_v1_auth: GenFile = /*@__PURE__*/
|
||||||
fileDesc("ChJ1c2VyL3YxL2F1dGgucHJvdG8SB3VzZXIudjEiMgoMTG9naW5SZXF1ZXN0EhAKCHVzZXJuYW1lGAEgASgJEhAKCHBhc3N3b3JkGAIgASgJIh4KDUxvZ2luUmVzcG9uc2USDQoFdG9rZW4YASABKAkiMwoNU2lnblVwUmVxdWVzdBIQCgh1c2VybmFtZRgBIAEoCRIQCghwYXNzd29yZBgCIAEoCSIQCg5TaWduVXBSZXNwb25zZSIPCg1Mb2dvdXRSZXF1ZXN0IhAKDkxvZ291dFJlc3BvbnNlMsEBCgtBdXRoU2VydmljZRI4CgVMb2dpbhIVLnVzZXIudjEuTG9naW5SZXF1ZXN0GhYudXNlci52MS5Mb2dpblJlc3BvbnNlIgASOwoGU2lnblVwEhYudXNlci52MS5TaWduVXBSZXF1ZXN0GhcudXNlci52MS5TaWduVXBSZXNwb25zZSIAEjsKBkxvZ291dBIWLnVzZXIudjEuTG9nb3V0UmVxdWVzdBoXLnVzZXIudjEuTG9nb3V0UmVzcG9uc2UiAEKdAQoLY29tLnVzZXIudjFCCUF1dGhQcm90b1ABWkZnaXRodWIuY29tL3Nwb3RkZW1vNC90cmV2c3RhY2svc2VydmVyL2ludGVybmFsL3NlcnZpY2VzL3VzZXIvdjE7dXNlcnYxogIDVVhYqgIHVXNlci5WMcoCB1VzZXJcVjHiAhNVc2VyXFYxXEdQQk1ldGFkYXRh6gIIVXNlcjo6VjFiBnByb3RvMw");
|
fileDesc("ChJ1c2VyL3YxL2F1dGgucHJvdG8SB3VzZXIudjEiMgoMTG9naW5SZXF1ZXN0EhAKCHVzZXJuYW1lGAEgASgJEhAKCHBhc3N3b3JkGAIgASgJIh4KDUxvZ2luUmVzcG9uc2USDQoFdG9rZW4YASABKAkiTQoNU2lnblVwUmVxdWVzdBIQCgh1c2VybmFtZRgBIAEoCRIQCghwYXNzd29yZBgCIAEoCRIYChBjb25maXJtX3Bhc3N3b3JkGAMgASgJIhAKDlNpZ25VcFJlc3BvbnNlIg8KDUxvZ291dFJlcXVlc3QiEAoOTG9nb3V0UmVzcG9uc2UywQEKC0F1dGhTZXJ2aWNlEjgKBUxvZ2luEhUudXNlci52MS5Mb2dpblJlcXVlc3QaFi51c2VyLnYxLkxvZ2luUmVzcG9uc2UiABI7CgZTaWduVXASFi51c2VyLnYxLlNpZ25VcFJlcXVlc3QaFy51c2VyLnYxLlNpZ25VcFJlc3BvbnNlIgASOwoGTG9nb3V0EhYudXNlci52MS5Mb2dvdXRSZXF1ZXN0GhcudXNlci52MS5Mb2dvdXRSZXNwb25zZSIAQp0BCgtjb20udXNlci52MUIJQXV0aFByb3RvUAFaRmdpdGh1Yi5jb20vc3BvdGRlbW80L3RyZXZzdGFjay9zZXJ2ZXIvaW50ZXJuYWwvc2VydmljZXMvdXNlci92MTt1c2VydjGiAgNVWFiqAgdVc2VyLlYxygIHVXNlclxWMeICE1VzZXJcVjFcR1BCTWV0YWRhdGHqAghVc2VyOjpWMWIGcHJvdG8z");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @generated from message user.v1.LoginRequest
|
* @generated from message user.v1.LoginRequest
|
||||||
@ -64,6 +64,11 @@ export type SignUpRequest = Message<"user.v1.SignUpRequest"> & {
|
|||||||
* @generated from field: string password = 2;
|
* @generated from field: string password = 2;
|
||||||
*/
|
*/
|
||||||
password: string;
|
password: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: string confirm_password = 3;
|
||||||
|
*/
|
||||||
|
confirmPassword: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
6
client/src/lib/utils.ts
Normal file
6
client/src/lib/utils.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { type ClassValue, clsx } from "clsx";
|
||||||
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
|
export function cn(...inputs: ClassValue[]) {
|
||||||
|
return twMerge(clsx(inputs));
|
||||||
|
}
|
189
client/src/routes/(app)/+layout.svelte
Normal file
189
client/src/routes/(app)/+layout.svelte
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import {
|
||||||
|
LayoutGrid,
|
||||||
|
Settings,
|
||||||
|
LogOut,
|
||||||
|
Menu,
|
||||||
|
LayoutList,
|
||||||
|
Book,
|
||||||
|
House,
|
||||||
|
type Icon as IconType
|
||||||
|
} from '@lucide/svelte';
|
||||||
|
import { NavigationMenu, Popover, Separator, Dialog } from 'bits-ui';
|
||||||
|
import { fly, slide } from 'svelte/transition';
|
||||||
|
import { toast } from 'svelte-sonner';
|
||||||
|
import { goto } from '$app/navigation';
|
||||||
|
import { AuthClient } from '$lib/transport';
|
||||||
|
import { page } from '$app/state';
|
||||||
|
import { cn } from '$lib/utils';
|
||||||
|
let { children } = $props();
|
||||||
|
|
||||||
|
const username = localStorage.getItem('username');
|
||||||
|
let sidebarOpen = $state(false);
|
||||||
|
|
||||||
|
type MenuItem = {
|
||||||
|
name: string;
|
||||||
|
href: string;
|
||||||
|
icon: typeof IconType;
|
||||||
|
};
|
||||||
|
const menuItems: MenuItem[] = [
|
||||||
|
{
|
||||||
|
name: 'Home',
|
||||||
|
href: '/',
|
||||||
|
icon: House
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Items',
|
||||||
|
href: '/items',
|
||||||
|
icon: LayoutList
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Docs',
|
||||||
|
href: '/docs/',
|
||||||
|
icon: Book
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
async function logout() {
|
||||||
|
await AuthClient.logout({});
|
||||||
|
localStorage.removeItem('username');
|
||||||
|
await goto('/auth');
|
||||||
|
toast.success('logged out successfully');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<header
|
||||||
|
class="border-surface-0 bg-mantle fixed flex h-[50px] w-full items-center justify-between border-b p-2 px-6 drop-shadow-md"
|
||||||
|
>
|
||||||
|
<div class="flex items-center gap-4">
|
||||||
|
<Dialog.Root bind:open={sidebarOpen}>
|
||||||
|
<Dialog.Trigger class="hover:bg-surface-0 cursor-pointer rounded p-1 px-3 transition-all">
|
||||||
|
<Menu />
|
||||||
|
</Dialog.Trigger>
|
||||||
|
<Dialog.Portal>
|
||||||
|
<Dialog.Overlay class="fixed inset-0 z-50 mt-[50px] bg-black/50" />
|
||||||
|
<Dialog.Content forceMount>
|
||||||
|
{#snippet child({ props, open })}
|
||||||
|
{#if open}
|
||||||
|
<div
|
||||||
|
class="bg-mantle border-surface-0 fixed inset-0 z-50 mt-[50px] flex w-60 flex-col justify-between border-r drop-shadow-md"
|
||||||
|
{...props}
|
||||||
|
transition:slide={{
|
||||||
|
axis: 'x'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<NavigationMenu.Root orientation="vertical">
|
||||||
|
<NavigationMenu.List class="flex w-full flex-col gap-2 overflow-y-scroll p-2">
|
||||||
|
{#each menuItems as item}
|
||||||
|
{@const Icon = item.icon}
|
||||||
|
<NavigationMenu.Item>
|
||||||
|
<NavigationMenu.Link
|
||||||
|
class={cn(
|
||||||
|
'hover:bg-surface-0 flex select-none gap-2 whitespace-nowrap rounded-lg p-2 transition-all',
|
||||||
|
page.url.pathname === item.href && 'bg-surface-0'
|
||||||
|
)}
|
||||||
|
href={item.href}
|
||||||
|
onclick={() => {
|
||||||
|
if (sidebarOpen) {
|
||||||
|
sidebarOpen = false;
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Icon />
|
||||||
|
<span>{item.name}</span>
|
||||||
|
</NavigationMenu.Link>
|
||||||
|
</NavigationMenu.Item>
|
||||||
|
{/each}
|
||||||
|
</NavigationMenu.List>
|
||||||
|
</NavigationMenu.Root>
|
||||||
|
|
||||||
|
<div class="border-surface-0 flex flex-col gap-2 border-t p-2">
|
||||||
|
<a
|
||||||
|
href="/settings"
|
||||||
|
class="hover:bg-surface-0 flex select-none items-center gap-2 rounded-lg p-2 transition-all"
|
||||||
|
>
|
||||||
|
<Settings />
|
||||||
|
<span>Settings</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="hover:bg-surface-0 flex w-full cursor-pointer items-center gap-2 whitespace-nowrap rounded-lg p-2 transition-all"
|
||||||
|
onclick={logout}
|
||||||
|
>
|
||||||
|
<LogOut size="20" />
|
||||||
|
Log out
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{/snippet}
|
||||||
|
</Dialog.Content>
|
||||||
|
</Dialog.Portal>
|
||||||
|
</Dialog.Root>
|
||||||
|
|
||||||
|
<a href="/" class="flex select-none items-center gap-2 text-2xl font-bold tracking-wider">
|
||||||
|
TrevStack
|
||||||
|
<LayoutGrid />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<NavigationMenu.Root class="hidden md:block">
|
||||||
|
<NavigationMenu.List class="flex gap-2">
|
||||||
|
{#each menuItems as item}
|
||||||
|
<NavigationMenu.Item>
|
||||||
|
<NavigationMenu.Link
|
||||||
|
class={cn(
|
||||||
|
'hover:bg-surface-0 flex select-none gap-2 rounded-lg p-1 px-2 transition-all',
|
||||||
|
page.url.pathname === item.href && 'bg-surface-0'
|
||||||
|
)}
|
||||||
|
href={item.href}
|
||||||
|
>
|
||||||
|
<span>{item.name}</span>
|
||||||
|
</NavigationMenu.Link>
|
||||||
|
</NavigationMenu.Item>
|
||||||
|
{/each}
|
||||||
|
</NavigationMenu.List>
|
||||||
|
<NavigationMenu.Viewport class="absolute" />
|
||||||
|
</NavigationMenu.Root>
|
||||||
|
|
||||||
|
<Popover.Root>
|
||||||
|
<Popover.Trigger
|
||||||
|
class="border-surface-2 hover:bg-surface-0 cursor-pointer rounded border p-1 px-4 text-sm transition-all"
|
||||||
|
>
|
||||||
|
{username}
|
||||||
|
</Popover.Trigger>
|
||||||
|
<Popover.Content forceMount>
|
||||||
|
{#snippet child({ wrapperProps, props, open })}
|
||||||
|
{#if open}
|
||||||
|
<div {...wrapperProps}>
|
||||||
|
<div
|
||||||
|
class="bg-mantle border-surface-0 z-50 mt-1 rounded border drop-shadow-md transition-all"
|
||||||
|
{...props}
|
||||||
|
transition:fly
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
class="hover:bg-surface-0 flex items-center gap-1 p-3 px-4 text-sm"
|
||||||
|
href="/settings"
|
||||||
|
>
|
||||||
|
<Settings size="20" />
|
||||||
|
Settings
|
||||||
|
</a>
|
||||||
|
<Separator.Root class="bg-surface-0 h-px" />
|
||||||
|
<button
|
||||||
|
class="hover:bg-surface-0 flex w-full cursor-pointer items-center gap-1 p-3 px-4 text-sm transition-all"
|
||||||
|
onclick={logout}
|
||||||
|
>
|
||||||
|
<LogOut size="20" />
|
||||||
|
Log out
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{/snippet}
|
||||||
|
</Popover.Content>
|
||||||
|
</Popover.Root>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="pt-[50px]">
|
||||||
|
{@render children()}
|
||||||
|
</div>
|
26
client/src/routes/(app)/docs/+page.svelte
Normal file
26
client/src/routes/(app)/docs/+page.svelte
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { createApiReference } from '@scalar/api-reference';
|
||||||
|
import { onDestroy, onMount } from 'svelte';
|
||||||
|
|
||||||
|
let ref: ReturnType<typeof createApiReference>;
|
||||||
|
let htmlref: HTMLDivElement;
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
ref = createApiReference(htmlref, {
|
||||||
|
url: '/openapi/openapi.yaml',
|
||||||
|
hideClientButton: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
onDestroy(() => {
|
||||||
|
ref.destroy();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
<style>
|
||||||
|
@import '@scalar/api-reference/style.css';
|
||||||
|
</style>
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
|
<div bind:this={htmlref}></div>
|
@ -1,6 +1,14 @@
|
|||||||
<script>
|
<script>
|
||||||
let { children } = $props();
|
|
||||||
import '../app.css';
|
import '../app.css';
|
||||||
|
import { Toaster } from 'svelte-sonner';
|
||||||
|
|
||||||
|
let { children } = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<Toaster toastOptions={{
|
||||||
|
classes: {
|
||||||
|
toast: '!bg-mantle !text-text !border-surface-0',
|
||||||
|
}
|
||||||
|
}} />
|
||||||
|
|
||||||
{@render children()}
|
{@render children()}
|
||||||
|
@ -1,2 +1,3 @@
|
|||||||
export const prerender = true;
|
export const prerender = true;
|
||||||
export const ssr = false;
|
export const ssr = false;
|
||||||
|
export const trailingSlash = 'always';
|
@ -1,29 +1,151 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Tabs } from 'bits-ui';
|
import { Tabs, Button } from 'bits-ui';
|
||||||
|
import { cn } from '$lib/utils';
|
||||||
|
import { AuthClient } from '$lib/transport';
|
||||||
|
import { goto } from '$app/navigation';
|
||||||
|
import { ConnectError } from '@connectrpc/connect';
|
||||||
|
import { toast } from 'svelte-sonner';
|
||||||
|
|
||||||
|
let tab = $state('login');
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="pt-6">
|
<div class="flex h-screen flex-col items-center justify-center">
|
||||||
<Tabs.Root
|
<Tabs.Root bind:value={tab} class="w-[390px] p-3">
|
||||||
value="outbound"
|
|
||||||
class="rounded-card border-muted bg-background-alt shadow-card w-[390px] border p-3"
|
|
||||||
>
|
|
||||||
<Tabs.List
|
<Tabs.List
|
||||||
class="rounded-9px bg-dark-10 shadow-mini-inset dark:bg-background grid w-full grid-cols-2 gap-1 p-1 text-sm font-semibold leading-[0.01em] dark:border dark:border-neutral-600/30"
|
class="bg-mantle border-surface-0 flex w-full justify-around gap-1 rounded-lg border p-1 drop-shadow-md"
|
||||||
>
|
>
|
||||||
<Tabs.Trigger
|
<Tabs.Trigger
|
||||||
value="outbound"
|
value="login"
|
||||||
class="data-[state=active]:shadow-mini dark:data-[state=active]:bg-muted h-8 rounded-[7px] bg-transparent py-2 data-[state=active]:bg-white"
|
class={cn(
|
||||||
|
'hover:bg-surface-0 grow cursor-pointer rounded p-2 transition-all',
|
||||||
|
tab == 'login' && 'bg-surface-0'
|
||||||
|
)}>Log In</Tabs.Trigger
|
||||||
>
|
>
|
||||||
Outbound
|
|
||||||
</Tabs.Trigger>
|
|
||||||
<Tabs.Trigger
|
<Tabs.Trigger
|
||||||
value="inbound"
|
value="signup"
|
||||||
class="data-[state=active]:shadow-mini dark:data-[state=active]:bg-muted h-8 rounded-[7px] bg-transparent py-2 data-[state=active]:bg-white"
|
class={cn(
|
||||||
|
'hover:bg-surface-0 grow cursor-pointer rounded p-2 transition-all',
|
||||||
|
tab == 'signup' && 'bg-surface-0'
|
||||||
|
)}>Sign Up</Tabs.Trigger
|
||||||
>
|
>
|
||||||
Inbound
|
|
||||||
</Tabs.Trigger>
|
|
||||||
</Tabs.List>
|
</Tabs.List>
|
||||||
<Tabs.Content value="outbound" class="select-none pt-3">Test1</Tabs.Content>
|
<Tabs.Content
|
||||||
<Tabs.Content value="inbound" class="select-none pt-3">Test2</Tabs.Content>
|
value="login"
|
||||||
|
class="bg-mantle border-surface-0 mt-2 rounded-lg border p-6 drop-shadow-md"
|
||||||
|
>
|
||||||
|
<form
|
||||||
|
onsubmit={async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const formData = new FormData(e.target as HTMLFormElement);
|
||||||
|
const username = formData.get('login-username')?.toString();
|
||||||
|
const password = formData.get('login-password')?.toString();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await AuthClient.login({
|
||||||
|
username: username,
|
||||||
|
password: password
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.token && username) {
|
||||||
|
localStorage.setItem('username', username);
|
||||||
|
goto('/');
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
const error = ConnectError.from(err);
|
||||||
|
toast.error(error.rawMessage);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div class="flex flex-col gap-4">
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
|
<label for="login-username" class="text-sm">Username</label>
|
||||||
|
<input
|
||||||
|
id="login-username"
|
||||||
|
name="login-username"
|
||||||
|
type="text"
|
||||||
|
class="border-surface-0 rounded border p-2 text-sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
|
<label for="login-password" class="text-sm">Password</label>
|
||||||
|
<input
|
||||||
|
id="login-password"
|
||||||
|
name="login-password"
|
||||||
|
type="password"
|
||||||
|
class="border-surface-0 rounded border p-2 text-sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Button.Root
|
||||||
|
type="submit"
|
||||||
|
class="bg-sky text-crust hover:brightness-120 w-20 cursor-pointer rounded p-2 px-4 text-sm transition-all"
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</Button.Root>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Tabs.Content>
|
||||||
|
<Tabs.Content
|
||||||
|
value="signup"
|
||||||
|
class="bg-mantle border-surface-0 mt-2 rounded-lg border p-6 drop-shadow-md"
|
||||||
|
>
|
||||||
|
<form
|
||||||
|
onsubmit={async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const form = e.target as HTMLFormElement;
|
||||||
|
const formData = new FormData(form);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await AuthClient.signUp({
|
||||||
|
username: formData.get('signup-username')?.toString(),
|
||||||
|
password: formData.get('signup-password')?.toString(),
|
||||||
|
confirmPassword: formData.get('signup-confirm-password')?.toString()
|
||||||
|
});
|
||||||
|
|
||||||
|
toast.success('account created successfully, please log in');
|
||||||
|
form.reset();
|
||||||
|
tab = 'login';
|
||||||
|
} catch (err) {
|
||||||
|
const error = ConnectError.from(err);
|
||||||
|
toast.error(error.rawMessage);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div class="flex flex-col gap-4">
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
|
<label for="signup-username" class="text-sm">Username</label>
|
||||||
|
<input
|
||||||
|
id="signup-username"
|
||||||
|
name="signup-username"
|
||||||
|
type="text"
|
||||||
|
class="border-surface-0 rounded border p-2 text-sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
|
<label for="signup-password" class="text-sm">Password</label>
|
||||||
|
<input
|
||||||
|
id="signup-password"
|
||||||
|
name="signup-password"
|
||||||
|
type="password"
|
||||||
|
class="border-surface-0 rounded border p-2 text-sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
|
<label for="signup-confirm-password" class="text-sm">Confirm Password</label>
|
||||||
|
<input
|
||||||
|
id="signup-confirm-password"
|
||||||
|
name="signup-confirm-password"
|
||||||
|
type="password"
|
||||||
|
class="border-surface-0 rounded border p-2 text-sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Button.Root
|
||||||
|
type="submit"
|
||||||
|
class="bg-sky text-crust hover:brightness-120 w-20 cursor-pointer rounded p-2 px-4 text-sm transition-all"
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</Button.Root>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Tabs.Content>
|
||||||
</Tabs.Root>
|
</Tabs.Root>
|
||||||
</div>
|
</div>
|
||||||
|
@ -51,6 +51,9 @@ components:
|
|||||||
password:
|
password:
|
||||||
type: string
|
type: string
|
||||||
title: password
|
title: password
|
||||||
|
confirmPassword:
|
||||||
|
type: string
|
||||||
|
title: confirm_password
|
||||||
title: SignUpRequest
|
title: SignUpRequest
|
||||||
additionalProperties: false
|
additionalProperties: false
|
||||||
user.v1.SignUpResponse:
|
user.v1.SignUpResponse:
|
||||||
|
24
flake.nix
24
flake.nix
@ -38,7 +38,7 @@
|
|||||||
pname = "${pname}-client";
|
pname = "${pname}-client";
|
||||||
inherit version;
|
inherit version;
|
||||||
src = gitignore.lib.gitignoreSource ./client;
|
src = gitignore.lib.gitignoreSource ./client;
|
||||||
npmDepsHash = "sha256-hOmZZrCSuHyRQhG6M7Yu5uRLTdCYOL/giT4zUm9iTRE=";
|
npmDepsHash = "sha256-YCmx+XNir+6iI0zqIBIjlNxyQ5iz4j9Is+unfSMEgQE=";
|
||||||
nodejs = pkgs.nodejs_22;
|
nodejs = pkgs.nodejs_22;
|
||||||
|
|
||||||
installPhase = ''
|
installPhase = ''
|
||||||
@ -68,6 +68,9 @@
|
|||||||
# Svelte frontend
|
# Svelte frontend
|
||||||
nodejs_22
|
nodejs_22
|
||||||
|
|
||||||
|
# Openapi gen
|
||||||
|
openapi-generator-cli
|
||||||
|
|
||||||
# Helper scripts
|
# Helper scripts
|
||||||
(writeShellApplication {
|
(writeShellApplication {
|
||||||
name = "run";
|
name = "run";
|
||||||
@ -91,25 +94,6 @@
|
|||||||
'';
|
'';
|
||||||
})
|
})
|
||||||
|
|
||||||
(writeShellApplication {
|
|
||||||
name = "build";
|
|
||||||
|
|
||||||
text = ''
|
|
||||||
gitroot=$(git rev-parse --show-toplevel)
|
|
||||||
|
|
||||||
cd "''${gitroot}"
|
|
||||||
buf lint
|
|
||||||
buf generate
|
|
||||||
|
|
||||||
cd "''${gitroot}/client"
|
|
||||||
npm run build
|
|
||||||
cp -r build ../server/client
|
|
||||||
|
|
||||||
cd "''${gitroot}/server"
|
|
||||||
go build -o ../build/trevstack .
|
|
||||||
'';
|
|
||||||
})
|
|
||||||
|
|
||||||
(writeShellApplication {
|
(writeShellApplication {
|
||||||
name = "protobufwatch";
|
name = "protobufwatch";
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@ message LoginResponse {
|
|||||||
message SignUpRequest {
|
message SignUpRequest {
|
||||||
string username = 1;
|
string username = 1;
|
||||||
string password = 2;
|
string password = 2;
|
||||||
|
string confirm_password = 3;
|
||||||
}
|
}
|
||||||
message SignUpResponse {}
|
message SignUpResponse {}
|
||||||
|
|
||||||
|
@ -77,6 +77,9 @@ func (s *AuthHandler) SignUp(ctx context.Context, req *connect.Request[userv1.Si
|
|||||||
} else {
|
} else {
|
||||||
return nil, connect.NewError(connect.CodeAlreadyExists, errors.New("username already exists"))
|
return nil, connect.NewError(connect.CodeAlreadyExists, errors.New("username already exists"))
|
||||||
}
|
}
|
||||||
|
if req.Msg.Password != req.Msg.ConfirmPassword {
|
||||||
|
return nil, connect.NewError(connect.CodeInvalidArgument, errors.New("passwords do not match"))
|
||||||
|
}
|
||||||
|
|
||||||
// Hash password
|
// Hash password
|
||||||
hash, err := bcrypt.GenerateFromPassword([]byte(req.Msg.Password), bcrypt.DefaultCost)
|
hash, err := bcrypt.GenerateFromPassword([]byte(req.Msg.Password), bcrypt.DefaultCost)
|
||||||
|
@ -7,11 +7,51 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"connectrpc.com/connect"
|
"connectrpc.com/connect"
|
||||||
"github.com/golang-jwt/jwt/v5"
|
"github.com/golang-jwt/jwt/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func WithAuthRedirect(next http.Handler, key string) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
log.Println("start", "method", r.Method, "path", r.URL.Path)
|
||||||
|
pathItems := strings.Split(r.URL.Path, "/")
|
||||||
|
|
||||||
|
if len(pathItems) < 2 {
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch pathItems[1] {
|
||||||
|
case "auth":
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
|
||||||
|
case "_app":
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Check if the request contains a valid cookie token
|
||||||
|
cookies := getCookies(r.Header.Get("Cookie"))
|
||||||
|
for _, cookie := range cookies {
|
||||||
|
if cookie.Name == "token" {
|
||||||
|
_, err := validateToken(cookie.Value, key)
|
||||||
|
if err == nil {
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise redirect
|
||||||
|
http.Redirect(w, r, "/auth", http.StatusFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
type authInterceptor struct {
|
type authInterceptor struct {
|
||||||
key string
|
key string
|
||||||
}
|
}
|
||||||
|
@ -118,11 +118,12 @@ func (x *LoginResponse) GetToken() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type SignUpRequest struct {
|
type SignUpRequest struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
Username string `protobuf:"bytes,1,opt,name=username,proto3" json:"username,omitempty"`
|
Username string `protobuf:"bytes,1,opt,name=username,proto3" json:"username,omitempty"`
|
||||||
Password string `protobuf:"bytes,2,opt,name=password,proto3" json:"password,omitempty"`
|
Password string `protobuf:"bytes,2,opt,name=password,proto3" json:"password,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
ConfirmPassword string `protobuf:"bytes,3,opt,name=confirm_password,json=confirmPassword,proto3" json:"confirm_password,omitempty"`
|
||||||
sizeCache protoimpl.SizeCache
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *SignUpRequest) Reset() {
|
func (x *SignUpRequest) Reset() {
|
||||||
@ -169,6 +170,13 @@ func (x *SignUpRequest) GetPassword() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *SignUpRequest) GetConfirmPassword() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.ConfirmPassword
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
type SignUpResponse struct {
|
type SignUpResponse struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
@ -288,38 +296,41 @@ var file_user_v1_auth_proto_rawDesc = string([]byte{
|
|||||||
0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73,
|
0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73,
|
||||||
0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0x25, 0x0a, 0x0d, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65,
|
0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0x25, 0x0a, 0x0d, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65,
|
||||||
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18,
|
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18,
|
||||||
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x47, 0x0a, 0x0d,
|
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x72, 0x0a, 0x0d,
|
||||||
0x53, 0x69, 0x67, 0x6e, 0x55, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a,
|
0x53, 0x69, 0x67, 0x6e, 0x55, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a,
|
||||||
0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
|
0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||||
0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73,
|
0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73,
|
||||||
0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73,
|
0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73,
|
||||||
0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0x10, 0x0a, 0x0e, 0x53, 0x69, 0x67, 0x6e, 0x55, 0x70, 0x52,
|
0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x29, 0x0a, 0x10, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d,
|
||||||
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x0f, 0x0a, 0x0d, 0x4c, 0x6f, 0x67, 0x6f, 0x75,
|
0x5f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||||
0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x10, 0x0a, 0x0e, 0x4c, 0x6f, 0x67, 0x6f,
|
0x0f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64,
|
||||||
0x75, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xc1, 0x01, 0x0a, 0x0b, 0x41,
|
0x22, 0x10, 0x0a, 0x0e, 0x53, 0x69, 0x67, 0x6e, 0x55, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
||||||
0x75, 0x74, 0x68, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x38, 0x0a, 0x05, 0x4c, 0x6f,
|
0x73, 0x65, 0x22, 0x0f, 0x0a, 0x0d, 0x4c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75,
|
||||||
0x67, 0x69, 0x6e, 0x12, 0x15, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x6f,
|
0x65, 0x73, 0x74, 0x22, 0x10, 0x0a, 0x0e, 0x4c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x52, 0x65, 0x73,
|
||||||
0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x75, 0x73, 0x65,
|
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xc1, 0x01, 0x0a, 0x0b, 0x41, 0x75, 0x74, 0x68, 0x53, 0x65,
|
||||||
0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x38, 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x15,
|
||||||
0x73, 0x65, 0x22, 0x00, 0x12, 0x3b, 0x0a, 0x06, 0x53, 0x69, 0x67, 0x6e, 0x55, 0x70, 0x12, 0x16,
|
0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65,
|
||||||
0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x55, 0x70, 0x52,
|
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e,
|
||||||
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31,
|
0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12,
|
||||||
0x2e, 0x53, 0x69, 0x67, 0x6e, 0x55, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
|
0x3b, 0x0a, 0x06, 0x53, 0x69, 0x67, 0x6e, 0x55, 0x70, 0x12, 0x16, 0x2e, 0x75, 0x73, 0x65, 0x72,
|
||||||
0x00, 0x12, 0x3b, 0x0a, 0x06, 0x4c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x12, 0x16, 0x2e, 0x75, 0x73,
|
0x2e, 0x76, 0x31, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x55, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||||
0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75,
|
0x74, 0x1a, 0x17, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x69, 0x67, 0x6e,
|
||||||
0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x6f,
|
0x55, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x3b, 0x0a, 0x06,
|
||||||
0x67, 0x6f, 0x75, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x9d,
|
0x4c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x12, 0x16, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31,
|
||||||
0x01, 0x0a, 0x0b, 0x63, 0x6f, 0x6d, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x42, 0x09,
|
0x2e, 0x4c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17,
|
||||||
0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x46, 0x67, 0x69, 0x74,
|
0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x52,
|
||||||
0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x70, 0x6f, 0x74, 0x64, 0x65, 0x6d, 0x6f,
|
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x9d, 0x01, 0x0a, 0x0b, 0x63, 0x6f,
|
||||||
0x34, 0x2f, 0x74, 0x72, 0x65, 0x76, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2f, 0x73, 0x65, 0x72, 0x76,
|
0x6d, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x42, 0x09, 0x41, 0x75, 0x74, 0x68, 0x50,
|
||||||
0x65, 0x72, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x73, 0x65, 0x72, 0x76,
|
0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x46, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63,
|
||||||
0x69, 0x63, 0x65, 0x73, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x2f, 0x76, 0x31, 0x3b, 0x75, 0x73, 0x65,
|
0x6f, 0x6d, 0x2f, 0x73, 0x70, 0x6f, 0x74, 0x64, 0x65, 0x6d, 0x6f, 0x34, 0x2f, 0x74, 0x72, 0x65,
|
||||||
0x72, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x55, 0x58, 0x58, 0xaa, 0x02, 0x07, 0x55, 0x73, 0x65, 0x72,
|
0x76, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x69, 0x6e,
|
||||||
0x2e, 0x56, 0x31, 0xca, 0x02, 0x07, 0x55, 0x73, 0x65, 0x72, 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x13,
|
0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f,
|
||||||
0x55, 0x73, 0x65, 0x72, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64,
|
0x75, 0x73, 0x65, 0x72, 0x2f, 0x76, 0x31, 0x3b, 0x75, 0x73, 0x65, 0x72, 0x76, 0x31, 0xa2, 0x02,
|
||||||
0x61, 0x74, 0x61, 0xea, 0x02, 0x08, 0x55, 0x73, 0x65, 0x72, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06,
|
0x03, 0x55, 0x58, 0x58, 0xaa, 0x02, 0x07, 0x55, 0x73, 0x65, 0x72, 0x2e, 0x56, 0x31, 0xca, 0x02,
|
||||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
0x07, 0x55, 0x73, 0x65, 0x72, 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x13, 0x55, 0x73, 0x65, 0x72, 0x5c,
|
||||||
|
0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02,
|
||||||
|
0x08, 0x55, 0x73, 0x65, 0x72, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||||
|
0x33,
|
||||||
})
|
})
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -21,6 +21,7 @@ import (
|
|||||||
|
|
||||||
"github.com/spotdemo4/trevstack/server/internal/database"
|
"github.com/spotdemo4/trevstack/server/internal/database"
|
||||||
"github.com/spotdemo4/trevstack/server/internal/handlers"
|
"github.com/spotdemo4/trevstack/server/internal/handlers"
|
||||||
|
"github.com/spotdemo4/trevstack/server/internal/interceptors"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed all:client
|
//go:embed all:client
|
||||||
@ -120,7 +121,7 @@ func main() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed to get sub filesystem: %v", err)
|
log.Fatalf("failed to get sub filesystem: %v", err)
|
||||||
}
|
}
|
||||||
mux.Handle("/", http.FileServer(http.FS(clientFs)))
|
mux.Handle("/", interceptors.WithAuthRedirect(http.FileServer(http.FS(clientFs)), env.Key))
|
||||||
mux.Handle("/grpc/", http.StripPrefix("/grpc", api))
|
mux.Handle("/grpc/", http.StripPrefix("/grpc", api))
|
||||||
|
|
||||||
// Start server
|
// Start server
|
||||||
|
Loading…
x
Reference in New Issue
Block a user