initial commit
This commit is contained in:
commit
155aa524f3
48 changed files with 3943 additions and 0 deletions
23
src/routes/+error.svelte
Normal file
23
src/routes/+error.svelte
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
<script lang="ts">
|
||||
import { page } from "$app/state";
|
||||
import Centered from "$lib/Centered.svelte";
|
||||
|
||||
let errorMessage = page.error?.message || null;
|
||||
if (errorMessage?.startsWith('Error: ')) { errorMessage = errorMessage.slice(7); }
|
||||
if (errorMessage && /^[0-9]+$/.test(errorMessage) && +errorMessage === page.status) {
|
||||
errorMessage = null;
|
||||
}
|
||||
</script>
|
||||
|
||||
<Centered>
|
||||
<h1>{page.status}</h1>
|
||||
|
||||
{#if errorMessage}
|
||||
<p>{errorMessage}</p>
|
||||
{/if}
|
||||
{#if page.status >= 500}
|
||||
<p><button onclick={() => {history.go(0);}} class="inline">Refresh</button></p>
|
||||
{:else}
|
||||
<p><a href="/">Back to homepage</a></p>
|
||||
{/if}
|
||||
</Centered>
|
||||
86
src/routes/+layout.svelte
Normal file
86
src/routes/+layout.svelte
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
<script lang="ts">
|
||||
import { version } from '$app/environment';
|
||||
import type { Snippet } from 'svelte';
|
||||
import type { ServerHealth, UserEntry } from '$lib/backend';
|
||||
import { appName, version as bkVersion } from '$lib/globals.svelte';
|
||||
import MobileFooter from '$lib/MobileFooter.svelte';
|
||||
import MetaNav from '$lib/MetaNav.svelte';
|
||||
import { getFlash } from 'sveltekit-flash-message';
|
||||
import { page } from "$app/state";
|
||||
import FlashMessage from '$lib/FlashMessage.svelte';
|
||||
|
||||
let { data, children } : {
|
||||
data: {me: UserEntry},
|
||||
children: Snippet
|
||||
} = $props();
|
||||
|
||||
let {me} = $derived(data);
|
||||
|
||||
const flash = getFlash(page);
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<meta name="og:site_name" content="app_name" />
|
||||
{#if data}
|
||||
<meta name="generator" content={`app_name (frontend ${version} + Svelte 5, backend ${bkVersion()})`} />
|
||||
{/if}
|
||||
<!-- TODO csrf token? -->
|
||||
<!-- icon styles? -->
|
||||
<!-- SEO tags start -->
|
||||
<!-- end SEO tags -->
|
||||
</svelte:head>
|
||||
|
||||
<header>
|
||||
<h1>
|
||||
<a href="/">{appName()}</a>
|
||||
</h1>
|
||||
<!-- .metanav -->
|
||||
<MetaNav user={me} />
|
||||
</header>
|
||||
|
||||
<main>
|
||||
{#if $flash}
|
||||
<FlashMessage message={$flash?.message}/>
|
||||
{/if}
|
||||
|
||||
{@render children()}
|
||||
</main>
|
||||
|
||||
<MobileFooter />
|
||||
|
||||
<style>
|
||||
header {
|
||||
background-color: var(--background);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
overflow: hidden;
|
||||
height: 3em;
|
||||
padding: .75em 1.5em;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
header h1 {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-size: 1.5em;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
header a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
main {
|
||||
min-height: 70vh;
|
||||
margin: 12px auto;
|
||||
max-width: 1600px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 799px) {
|
||||
main {
|
||||
height: 100vh;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
45
src/routes/+layout.ts
Normal file
45
src/routes/+layout.ts
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
import { backend, type ServerHealth, type UserEntry } from '$lib/backend.js';
|
||||
import { setHealth, setMe } from '$lib/globals.svelte.js';
|
||||
import type { LoadEvent } from '@sveltejs/kit';
|
||||
|
||||
|
||||
async function loadSite (event: LoadEvent): Promise<ServerHealth | null> {
|
||||
const resp = await backend.withEvent(event).fetch('health');
|
||||
|
||||
if ([200].indexOf(resp.status) < 0) return null;
|
||||
try {
|
||||
|
||||
const respJ = await resp.json();
|
||||
|
||||
setHealth(respJ);
|
||||
|
||||
return respJ;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadMe(event: LoadEvent, me?: string): Promise<UserEntry | null> {
|
||||
if (!me) return null;
|
||||
|
||||
const resp = await backend.withEvent(event).fetch('user/@me');
|
||||
|
||||
if ([200].indexOf(resp.status) < 0) return null;
|
||||
try {
|
||||
const respJ = await resp.json();
|
||||
const { users } = respJ;
|
||||
const meJ = users[me];
|
||||
setMe(meJ);
|
||||
return meJ;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export async function load(event): Promise<{site: ServerHealth | null, me: UserEntry | null} > {
|
||||
let site = await loadSite(event);
|
||||
let me = await loadMe (event, site?.me || void 0);
|
||||
return { site, me };
|
||||
}
|
||||
|
||||
40
src/routes/+page.svelte
Normal file
40
src/routes/+page.svelte
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
<script lang="ts">
|
||||
import type { PostEntry } from "$lib/backend";
|
||||
import Centered from "$lib/Centered.svelte";
|
||||
import Feed from "$lib/Feed.svelte";
|
||||
import { activePostCount, activeUserCount, appName, getMe } from "$lib/globals.svelte";
|
||||
import SLayout from "$lib/SLayout.svelte";
|
||||
|
||||
let me = getMe();
|
||||
let { data } : { data: { feed: PostEntry[] } } = $props();
|
||||
let feed: PostEntry[] = $state([]);
|
||||
let feedIndex = $state(0);
|
||||
|
||||
$effect(() => {
|
||||
if (me && feed) {
|
||||
feed.push(...data.feed.slice(feedIndex));
|
||||
feedIndex += feed.length;
|
||||
} else if (me) {
|
||||
console.log('feed is', feed)
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if me}
|
||||
<SLayout title={appName()}>
|
||||
<Feed posts={feed} />
|
||||
{#snippet left()}
|
||||
...
|
||||
{/snippet}
|
||||
|
||||
{#snippet right()}
|
||||
...
|
||||
{/snippet}
|
||||
</SLayout>
|
||||
{:else}
|
||||
<Centered>
|
||||
<p>{appName()} is a social media platform made by people like you.<br />
|
||||
<a href="/login">Log in</a> or (sign up) to see {activePostCount()} posts and talk with {activeUserCount()} users right now!</p>
|
||||
</Centered>
|
||||
{/if}
|
||||
|
||||
32
src/routes/+page.ts
Normal file
32
src/routes/+page.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import { backend, type PostEntry } from '$lib/backend.js';
|
||||
import { getMe } from '$lib/globals.svelte.js';
|
||||
import type { LoadEvent } from '@sveltejs/kit';
|
||||
|
||||
|
||||
async function loadFeed (event: LoadEvent): Promise<PostEntry[] | null> {
|
||||
const resp = await backend.withEvent(event).fetch('home/feed');
|
||||
|
||||
if ([200].indexOf(resp.status) < 0) return null;
|
||||
|
||||
try {
|
||||
const respJ = await resp.json();
|
||||
|
||||
const { feed } : { feed: PostEntry[] } = respJ;
|
||||
|
||||
return feed;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export async function load(event): Promise<{feed: PostEntry[] | null}> {
|
||||
let feed = null;
|
||||
let me = getMe();
|
||||
|
||||
if (me) {
|
||||
feed = await loadFeed(event);
|
||||
}
|
||||
|
||||
return { feed };
|
||||
}
|
||||
23
src/routes/[x+2b][name]/+page.svelte
Normal file
23
src/routes/[x+2b][name]/+page.svelte
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
<script lang="ts">
|
||||
import type { GuildEntry, PostEntry } from "$lib/backend";
|
||||
import Feed from "$lib/Feed.svelte";
|
||||
import GuildAbout from "$lib/GuildAbout.svelte";
|
||||
import GuildMenu from "$lib/GuildMenu.svelte";
|
||||
import SLayout from "$lib/SLayout.svelte";
|
||||
|
||||
let { data } : { data: { guild: GuildEntry , feed: PostEntry[] } } = $props();
|
||||
let { guild, feed = [] } = $derived(data);
|
||||
|
||||
</script>
|
||||
|
||||
<SLayout title={guild.display_name? `${guild.display_name} (+${guild.name})` :`+${guild.name}`}>
|
||||
<Feed posts={feed} />
|
||||
|
||||
{#snippet left()}
|
||||
<GuildMenu {guild} />
|
||||
{/snippet}
|
||||
|
||||
{#snippet right()}
|
||||
<GuildAbout {guild} />
|
||||
{/snippet}
|
||||
</SLayout>
|
||||
36
src/routes/[x+2b][name]/+page.ts
Normal file
36
src/routes/[x+2b][name]/+page.ts
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
import { backend } from '$lib/backend.js';
|
||||
import { error } from '@sveltejs/kit';
|
||||
|
||||
export async function load(event) {
|
||||
const { params } = event;
|
||||
const { name } = params;
|
||||
|
||||
|
||||
const resp = await backend.withEvent(event).fetch('guild/@' + encodeURIComponent(name) + "/feed");
|
||||
|
||||
if(resp.status === 404) {
|
||||
error(404);
|
||||
}
|
||||
|
||||
try{
|
||||
const respJ = await resp.json();
|
||||
|
||||
let { guilds, feed } = respJ;
|
||||
let guild = null;
|
||||
for (let g in guilds) {
|
||||
if (guilds[g].name === name) {
|
||||
guild = guilds[g];
|
||||
}
|
||||
}
|
||||
|
||||
if (!guild) error(404);
|
||||
|
||||
return { guild, feed };
|
||||
}
|
||||
catch (e) {
|
||||
console.error(e);
|
||||
error(502);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
11
src/routes/[x+3d][id]/+page.svelte
Normal file
11
src/routes/[x+3d][id]/+page.svelte
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<script lang="ts">
|
||||
import type { PostEntry } from "$lib/backend.ts";
|
||||
import FullPost from "$lib/FullPost.svelte";
|
||||
|
||||
|
||||
let { data } : { data: PostEntry } = $props();
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<FullPost post={data} />
|
||||
31
src/routes/[x+3d][id]/+page.ts
Normal file
31
src/routes/[x+3d][id]/+page.ts
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
|
||||
|
||||
import { backend } from '$lib/backend.js';
|
||||
import { error } from '@sveltejs/kit';
|
||||
|
||||
export async function load(event) {
|
||||
const { params } = event;
|
||||
const { id } = params;
|
||||
|
||||
|
||||
const resp = await backend.withEvent(event).fetch('post/' + encodeURIComponent(id));
|
||||
|
||||
if(resp.status === 404) {
|
||||
error(404);
|
||||
}
|
||||
|
||||
try{
|
||||
const respJ = await resp.json();
|
||||
|
||||
let { posts } = respJ;
|
||||
return posts[id];
|
||||
|
||||
//return {};
|
||||
}
|
||||
catch (e) {
|
||||
console.error(e);
|
||||
error(502);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
11
src/routes/[x+3d][id]/[slug]/+page.svelte
Normal file
11
src/routes/[x+3d][id]/[slug]/+page.svelte
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<script lang="ts">
|
||||
import type { PostEntry } from "$lib/backend.ts";
|
||||
import FullPost from "$lib/FullPost.svelte";
|
||||
|
||||
|
||||
let { data } : { data: PostEntry } = $props();
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<FullPost post={data} />
|
||||
28
src/routes/[x+40][name]/+page.svelte
Normal file
28
src/routes/[x+40][name]/+page.svelte
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
<script lang="ts">
|
||||
import type { PostEntry, UserEntry } from "$lib/backend.js";
|
||||
import Feed from "$lib/Feed.svelte";
|
||||
import SLayout from "$lib/SLayout.svelte";
|
||||
import UserAbout from "$lib/UserAbout.svelte";
|
||||
import UserMenu from "$lib/UserMenu.svelte";
|
||||
import { RiUserLine } from "svelte-remixicon";
|
||||
|
||||
let { data } : { data: { user: UserEntry, feed: PostEntry[] } } = $props();
|
||||
|
||||
// TEMP make it work!
|
||||
let { user, feed } = $derived(data);
|
||||
let username = $derived(user.username);
|
||||
</script>
|
||||
|
||||
{#if username}
|
||||
<SLayout title={'@' + username}>
|
||||
<Feed posts={feed} />
|
||||
|
||||
{#snippet left()}
|
||||
<UserMenu {user} />
|
||||
{/snippet}
|
||||
|
||||
{#snippet right()}
|
||||
<UserAbout {user} />
|
||||
{/snippet}
|
||||
</SLayout>
|
||||
{/if}
|
||||
34
src/routes/[x+40][name]/+page.ts
Normal file
34
src/routes/[x+40][name]/+page.ts
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import { backend } from '$lib/backend.js';
|
||||
import { error } from '@sveltejs/kit';
|
||||
|
||||
export async function load(event) {
|
||||
const { params } = event;
|
||||
const { name } = params;
|
||||
|
||||
|
||||
const resp = await backend.withEvent(event).fetch('user/@' + encodeURIComponent(name) + '/feed');
|
||||
|
||||
if(resp.status === 404) {
|
||||
error(404);
|
||||
}
|
||||
|
||||
try{
|
||||
const respJ = await resp.json();
|
||||
|
||||
let { users, feed } = respJ;
|
||||
let user;
|
||||
for (let u in users) {
|
||||
if (users[u].username === name) {
|
||||
user = users[u];
|
||||
}
|
||||
}
|
||||
|
||||
return { user, feed };
|
||||
}
|
||||
catch (e) {
|
||||
console.error(e);
|
||||
error(502);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
53
src/routes/login/+page.server.ts
Normal file
53
src/routes/login/+page.server.ts
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
import { backend } from "$lib/backend";
|
||||
import { error, type Actions } from "@sveltejs/kit";
|
||||
import { redirect } from 'sveltekit-flash-message/server';
|
||||
|
||||
|
||||
export const actions = {
|
||||
default: async (event) => {
|
||||
// TODO login
|
||||
|
||||
const { request } = event;
|
||||
|
||||
const data = await request.formData();
|
||||
const username = data.get('username');
|
||||
const password = data.get('password');
|
||||
const remember = !!data.get('remember');
|
||||
|
||||
const backend2 = await backend.withEvent(event).oath();
|
||||
const resp = await backend2.submitJson('login', {
|
||||
username,
|
||||
password,
|
||||
remember
|
||||
});
|
||||
|
||||
const { status } = resp;
|
||||
const respData = await resp.json();
|
||||
|
||||
if ([200, 204].indexOf(status) < 0) {
|
||||
// login error
|
||||
console.log(`/login: status ${status}, data below`);
|
||||
console.debug(respData);
|
||||
switch(status) {
|
||||
case 400:
|
||||
redirect({message: 'Invalid login'}, event);
|
||||
break;
|
||||
case 404:
|
||||
redirect({message: 'Invalid username or password'}, event);
|
||||
break;
|
||||
case 403:
|
||||
redirect({message: 'Login not allowed'}, event);
|
||||
break;
|
||||
default:
|
||||
redirect({message: `Unknown error (HTTP ${status})`}, event);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// login success
|
||||
const { id: myId } = respData;
|
||||
|
||||
redirect(303, "/user/" + myId);
|
||||
}
|
||||
}
|
||||
} satisfies Actions;
|
||||
|
||||
48
src/routes/login/+page.svelte
Normal file
48
src/routes/login/+page.svelte
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
|
||||
<script lang="ts">
|
||||
import { enhance } from "$app/forms";
|
||||
import Centered from "$lib/Centered.svelte";
|
||||
import CheckboxLabel from "$lib/CheckboxLabel.svelte";
|
||||
import PasswordInput from "$lib/PasswordInput.svelte";
|
||||
import { RiEyeLine, RiEyeOffLine, RiKeyLine, RiMailLine, RiUserLine } from "svelte-remixicon";
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<Centered narrow>
|
||||
<card>
|
||||
<form method="POST" use:enhance>
|
||||
<label>
|
||||
<span><RiUserLine /> Username / <RiMailLine /> E-mail:</span>
|
||||
<input type="text" name="username" />
|
||||
</label>
|
||||
<label>
|
||||
<span><RiKeyLine /> Password</span>
|
||||
<PasswordInput name="password" />
|
||||
</label>
|
||||
<CheckboxLabel name="remember">
|
||||
Remember me
|
||||
</CheckboxLabel>
|
||||
|
||||
<button class="primary">Log in</button>
|
||||
</form>
|
||||
|
||||
</card>
|
||||
</Centered>
|
||||
|
||||
<style>
|
||||
label {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 1em auto;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
button.primary {
|
||||
display: block;
|
||||
width: 100%;
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
</style>
|
||||
26
src/routes/logout/+page.server.ts
Normal file
26
src/routes/logout/+page.server.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import { backend } from '$lib/backend.js';
|
||||
import { getMe, setMe } from '$lib/globals.svelte.js';
|
||||
import { error, redirect } from '@sveltejs/kit';
|
||||
|
||||
|
||||
|
||||
export async function load (event) {
|
||||
const me = getMe();
|
||||
|
||||
const next = "/";
|
||||
|
||||
// already logged out
|
||||
if (me == null) {
|
||||
redirect(303, next);
|
||||
}
|
||||
|
||||
const resp = await backend.withEvent(event).fetch("logout", {method: 'POST'});
|
||||
|
||||
if ([200, 204].indexOf(resp.status) >= 0) {
|
||||
setMe(null);
|
||||
redirect(303, next);
|
||||
}
|
||||
|
||||
console.error(`status ${resp.status} received, not logging out`)
|
||||
error(500);
|
||||
}
|
||||
0
src/routes/search/+page.server.ts
Normal file
0
src/routes/search/+page.server.ts
Normal file
28
src/routes/user/[id]/+page.ts
Normal file
28
src/routes/user/[id]/+page.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import { backend } from '$lib/backend';
|
||||
import { error, redirect } from '@sveltejs/kit';
|
||||
|
||||
export async function load(event) {
|
||||
const { params } = event;
|
||||
const { id } = params;
|
||||
|
||||
const resp = await backend.withEvent(event).fetch('user/' + encodeURIComponent(id));
|
||||
|
||||
if(resp.status === 404) {
|
||||
error(404);
|
||||
}
|
||||
|
||||
|
||||
|
||||
const respJ = await resp.json();
|
||||
|
||||
let { users } = respJ;
|
||||
console.log(users);
|
||||
if (users[id]) {
|
||||
if (users[id].username) {
|
||||
redirect(302, "/@" +users[id].username );
|
||||
}
|
||||
}
|
||||
|
||||
error(404);
|
||||
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue