feat: mvp
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/state';
|
||||
</script>
|
||||
|
||||
<div class="flex items-center justify-center grow">
|
||||
<h3>{page.status}: {page.error?.message ?? 'Upsi, da isch öppis kaputt gange...'}</h3>
|
||||
</div>
|
||||
@@ -0,0 +1,10 @@
|
||||
import { getSession as getSession } from '$lib/auth';
|
||||
import type { LayoutServerLoad } from './$types';
|
||||
|
||||
export const load: LayoutServerLoad = async (event) => {
|
||||
const session = await getSession(event);
|
||||
|
||||
return {
|
||||
session
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,47 @@
|
||||
<script lang="ts">
|
||||
import '../app.css';
|
||||
import favicon from '$lib/assets/favicon.svg';
|
||||
import {
|
||||
Navbar,
|
||||
NavBrand,
|
||||
NavLi,
|
||||
NavUl,
|
||||
NavHamburger,
|
||||
DropdownItem,
|
||||
Button
|
||||
} from 'flowbite-svelte';
|
||||
import type { LayoutProps } from './$types';
|
||||
import { signIn, signOut } from '@auth/sveltekit/client';
|
||||
import UserDisplay from '$lib/components/UserDisplay.svelte';
|
||||
|
||||
let { data, children }: LayoutProps = $props();
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<link rel="icon" href={favicon} />
|
||||
</svelte:head>
|
||||
<Navbar class="border-b border-gray-200 dark:border-gray-700">
|
||||
<NavBrand href="/">
|
||||
<span class="self-center text-xl font-semibold whitespace-nowrap dark:text-white">aktiteil</span
|
||||
>
|
||||
</NavBrand>
|
||||
|
||||
<NavHamburger />
|
||||
<NavUl>
|
||||
<NavLi href="/">Dehei</NavLi>
|
||||
<NavLi href="/akti">Neui Akti</NavLi>
|
||||
</NavUl>
|
||||
|
||||
{#if data.session?.user}
|
||||
<UserDisplay user={data.session.user}>
|
||||
<DropdownItem>
|
||||
<Button onclick={() => signOut()}>Sign out</Button>
|
||||
</DropdownItem>
|
||||
</UserDisplay>
|
||||
{:else}
|
||||
<Button onclick={() => signIn('nextcloud')}>Signin</Button>
|
||||
{/if}
|
||||
</Navbar>
|
||||
<main class="p-5 sm:px-20 2xl:px-60 flex flex-col h-full grow">
|
||||
{@render children()}
|
||||
</main>
|
||||
@@ -0,0 +1,21 @@
|
||||
import { db } from '$lib/server/db';
|
||||
import { aktis, ratings } from '$lib/server/db/schema';
|
||||
import { avg, eq } from 'drizzle-orm';
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
export const load: PageServerLoad = async () => {
|
||||
const a = await db
|
||||
.select({
|
||||
id: aktis.id,
|
||||
title: aktis.title,
|
||||
summary: aktis.summary,
|
||||
rating: avg(ratings.rating)
|
||||
})
|
||||
.from(aktis)
|
||||
.leftJoin(ratings, eq(aktis.id, ratings.aktiId))
|
||||
.groupBy(aktis.id, aktis.title, aktis.summary);
|
||||
|
||||
return {
|
||||
aktis: a.map((a) => ({ ...a, rating: a.rating ? parseFloat(a.rating) : undefined }))
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
<script lang="ts">
|
||||
import AktiCard from '$lib/components/akti/AktiCard.svelte';
|
||||
import type { PageProps } from './$types';
|
||||
|
||||
let { data }: PageProps = $props();
|
||||
</script>
|
||||
|
||||
<div class="grid gap-5 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 3xl:grid-cols-5">
|
||||
{#each data.aktis as akti (akti.id)}
|
||||
<AktiCard {akti}></AktiCard>
|
||||
{/each}
|
||||
</div>
|
||||
@@ -0,0 +1,38 @@
|
||||
import { redirect, type Actions } from '@sveltejs/kit';
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { extractFormData } from '$lib/extractFormData';
|
||||
import { resolve } from '$app/paths';
|
||||
|
||||
import * as v from 'valibot';
|
||||
import { ensureAuth } from '$lib/auth';
|
||||
import { db } from '$lib/server/db';
|
||||
import { aktis } from '$lib/server/db/schema';
|
||||
export const load: PageServerLoad = async (event) => {
|
||||
await ensureAuth(event);
|
||||
return {};
|
||||
};
|
||||
export const actions = {
|
||||
default: async (event) => {
|
||||
const user = await ensureAuth(event);
|
||||
|
||||
const akti = (
|
||||
await extractFormData(
|
||||
event.request,
|
||||
v.object({
|
||||
title: v.pipe(v.string(), v.minLength(5)),
|
||||
summary: v.pipe(v.string(), v.minLength(5)),
|
||||
body: v.pipe(v.string(), v.minLength(5))
|
||||
})
|
||||
)
|
||||
).data;
|
||||
|
||||
if (!akti) return {};
|
||||
|
||||
const res = await db
|
||||
.insert(aktis)
|
||||
.values({ ...akti, author: user.id! })
|
||||
.returning({ id: aktis.id });
|
||||
|
||||
return redirect(303, resolve(`/akti/[aktiId]`, { aktiId: res[0].id }));
|
||||
}
|
||||
} satisfies Actions;
|
||||
@@ -0,0 +1,5 @@
|
||||
<script lang="ts">
|
||||
import AktiEditor from '$lib/components/akti/AktiEditor.svelte';
|
||||
</script>
|
||||
|
||||
<AktiEditor akti={{ title: '', summary: '', body: '' }} />
|
||||
@@ -0,0 +1,66 @@
|
||||
import { db } from '$lib/server/db';
|
||||
import { aktis, ratings } from '$lib/server/db/schema';
|
||||
import { error, redirect, type Actions } from '@sveltejs/kit';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { ensureAuth } from '$lib/auth';
|
||||
import { extractFormData } from '$lib/extractFormData';
|
||||
import * as v from 'valibot';
|
||||
import { resolve } from '$app/paths';
|
||||
|
||||
export const load: PageServerLoad = async (event) => {
|
||||
const akti = await db.query.aktis.findFirst({
|
||||
where: eq(aktis.id, event.params.aktiId),
|
||||
with: { author: true }
|
||||
});
|
||||
|
||||
const r = await db.query.ratings.findMany({
|
||||
with: { user: true },
|
||||
where: eq(ratings.aktiId, event.params.aktiId)
|
||||
});
|
||||
|
||||
if (!akti) {
|
||||
error(404, { message: 'Die Akti gits garnid, sorry...' });
|
||||
}
|
||||
|
||||
return {
|
||||
akti,
|
||||
ratings: r
|
||||
};
|
||||
};
|
||||
export const actions = {
|
||||
default: async (event) => {
|
||||
const user = await ensureAuth(event);
|
||||
|
||||
if (!event.params.aktiId) return error(404);
|
||||
|
||||
const akti = await db
|
||||
.select({ id: aktis.id, version: aktis.version, author: aktis.author })
|
||||
.from(aktis)
|
||||
.limit(1)
|
||||
.where(eq(aktis.id, event.params.aktiId));
|
||||
|
||||
if (!akti || akti.length == 0) return error(404);
|
||||
if (akti[0].author != user.id) return error(403);
|
||||
|
||||
const changeRequest = (
|
||||
await extractFormData(
|
||||
event.request,
|
||||
v.object({
|
||||
title: v.pipe(v.string(), v.minLength(5)),
|
||||
summary: v.pipe(v.string(), v.minLength(5)),
|
||||
body: v.pipe(v.string(), v.minLength(5))
|
||||
})
|
||||
)
|
||||
).data;
|
||||
|
||||
if (!changeRequest) return error(400);
|
||||
|
||||
const res = await db
|
||||
.insert(aktis)
|
||||
.values({ ...changeRequest, author: user.id, version: akti[0].version + 1 })
|
||||
.returning({ id: aktis.id });
|
||||
|
||||
return redirect(303, resolve(`/akti/[aktiId]`, { aktiId: res[0].id }));
|
||||
}
|
||||
} satisfies Actions;
|
||||
@@ -0,0 +1,40 @@
|
||||
<script lang="ts">
|
||||
import { Button } from 'flowbite-svelte';
|
||||
import type { PageProps } from './$types';
|
||||
import { EditOutline, CloseOutline } from 'flowbite-svelte-icons';
|
||||
import AktiEditor from '$lib/components/akti/AktiEditor.svelte';
|
||||
import UserDisplay from '$lib/components/UserDisplay.svelte';
|
||||
|
||||
let { data }: PageProps = $props();
|
||||
|
||||
let edit = $state(false);
|
||||
</script>
|
||||
|
||||
<div class="flex justify-between">
|
||||
<h2>{data.akti?.title} <span class="text-xs text-gray-400">v{data.akti.version}</span></h2>
|
||||
{#if data.session?.user?.id === data.akti.author?.id && data.akti.author?.id}
|
||||
<Button onclick={() => (edit = !edit)} color={edit ? 'gray' : 'primary'}>
|
||||
{#if edit}
|
||||
<CloseOutline class="shrink-0 h-6 w-6" />
|
||||
{:else}
|
||||
<EditOutline class="shrink-0 h-6 w-6 -mr-0.5 ml-0.5" />
|
||||
{/if}
|
||||
</Button>
|
||||
{:else}
|
||||
<div class="flex gap-5 items-center">
|
||||
<p>gschribe vo:</p>
|
||||
<UserDisplay user={data.akti.author} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if edit}
|
||||
<AktiEditor akti={data.akti} />
|
||||
{:else}
|
||||
<div class="p-5 my-5 bg-gray-200 rounded-md">
|
||||
<h3 class="mb-2">Zämefassig</h3>
|
||||
<p>{data.akti.summary}</p>
|
||||
</div>
|
||||
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||
{@html data.akti.body}
|
||||
{/if}
|
||||
Reference in New Issue
Block a user