mirror of
https://github.com/zoriya/vex.git
synced 2026-06-03 02:41:31 +00:00
Start to read the api
This commit is contained in:
@@ -31,5 +31,17 @@ services:
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
web:
|
||||
build:
|
||||
context: ./web
|
||||
dockerfile: Dockerfile.dev
|
||||
restart: on-failure
|
||||
volumes:
|
||||
- ./web:/app
|
||||
ports:
|
||||
- 5173:5173
|
||||
environment:
|
||||
- API_URL=http://api:1597
|
||||
|
||||
volumes:
|
||||
db:
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
FROM node:22-alpine
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json .
|
||||
COPY package-lock.json .
|
||||
|
||||
RUN npm install
|
||||
|
||||
COPY . .
|
||||
|
||||
CMD ["npm", "run", "dev", "--", "--host"]
|
||||
@@ -0,0 +1,37 @@
|
||||
import { env } from '$env/dynamic/private';
|
||||
import type { Post } from '$lib/types';
|
||||
|
||||
export async function login(email: string, password: string) {
|
||||
const r = await fetch(env.API_URL + '/login', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ email, password })
|
||||
});
|
||||
const j = await r.json();
|
||||
return j.token as string;
|
||||
}
|
||||
|
||||
export async function register(email: string, username: string, password: string) {
|
||||
const r = await fetch(env.API_URL + '/register', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ email, password, name: username })
|
||||
});
|
||||
const j = await r.json();
|
||||
return j.token as string;
|
||||
}
|
||||
|
||||
export async function getPosts(token?: string) {
|
||||
const opts = {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`
|
||||
}
|
||||
}
|
||||
const r = await fetch(env.API_URL + '/entries', token ? opts : undefined);
|
||||
const j = await r.json();
|
||||
return j.posts as Post[];
|
||||
}
|
||||
@@ -7,7 +7,7 @@ export type Post = {
|
||||
link: string;
|
||||
date: Date;
|
||||
|
||||
author?: string;
|
||||
authors?: string[];
|
||||
isRead: boolean;
|
||||
isBookmarked: boolean;
|
||||
isIgnored: boolean;
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import type { Post, Feed } from "$lib/types";
|
||||
import { getPosts } from "$lib/server/api";
|
||||
import { get } from "svelte/store";
|
||||
|
||||
const feeds: Feed[] = [
|
||||
{
|
||||
@@ -41,7 +43,7 @@ const posts: Post[] = [
|
||||
isBookmarked: false,
|
||||
isIgnored: false,
|
||||
isReadLater: false,
|
||||
author: "John Doe",
|
||||
authors: ["John Doe"],
|
||||
feed: feeds[0],
|
||||
},
|
||||
{
|
||||
@@ -60,7 +62,7 @@ const posts: Post[] = [
|
||||
|
||||
export function load() {
|
||||
return {
|
||||
posts,
|
||||
posts: getPosts(),
|
||||
feeds
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import { fail } from '@sveltejs/kit';
|
||||
import { login, register } from '$lib/server/api.js';
|
||||
|
||||
|
||||
export const actions = {
|
||||
connect: async ({ cookies, request }) => {
|
||||
const data = await request.formData();
|
||||
const email = data.get('email');
|
||||
const password = data.get('password');
|
||||
const token = await login(email as any, password as any);
|
||||
return {
|
||||
token
|
||||
};
|
||||
},
|
||||
register: async ({ cookies, request }) => {
|
||||
const data = await request.formData();
|
||||
const email = data.get('email');
|
||||
const username = data.get('username');
|
||||
const password = data.get('password');
|
||||
const passwordRepeat = data.get('password-repeat');
|
||||
console.log("register", email, username, password, passwordRepeat);
|
||||
if (password !== passwordRepeat) {
|
||||
return fail(400, {
|
||||
data: {
|
||||
email
|
||||
},
|
||||
errors: {
|
||||
password: 'Passwords do not match'
|
||||
}
|
||||
})
|
||||
}
|
||||
const token = await register(email as any, username as any, password as any);
|
||||
return {
|
||||
token
|
||||
};
|
||||
},
|
||||
}
|
||||
@@ -8,9 +8,19 @@
|
||||
Heading,
|
||||
Secondary,
|
||||
P,
|
||||
Helper,
|
||||
Span,
|
||||
Spinner,
|
||||
} from "flowbite-svelte";
|
||||
import { EnvelopeOutline, LockOutline } from "flowbite-svelte-icons";
|
||||
import {
|
||||
EnvelopeOutline,
|
||||
LockOutline,
|
||||
UserOutline,
|
||||
} from "flowbite-svelte-icons";
|
||||
import { enhance } from "$app/forms";
|
||||
export let form;
|
||||
let registering = false;
|
||||
let connecting = false;
|
||||
</script>
|
||||
|
||||
<div class="text-center mb-10">
|
||||
@@ -21,10 +31,20 @@
|
||||
>
|
||||
Welcome to <Span gradient>Vex</Span>
|
||||
</Heading>
|
||||
<P class="mb-4 w-full text-center">The best place to share your thoughts with the world</P>
|
||||
<P class="mb-4 w-full text-center">The best place to gather your feeds</P>
|
||||
</div>
|
||||
<div class="main">
|
||||
<form>
|
||||
<form
|
||||
method="POST"
|
||||
action="?/connect"
|
||||
use:enhance={() => {
|
||||
connecting = true;
|
||||
return async ({ update }) => {
|
||||
await update();
|
||||
connecting = false;
|
||||
};
|
||||
}}
|
||||
>
|
||||
<Heading tag="h3" class="mb-4">Login</Heading>
|
||||
<div>
|
||||
<Label for="email" class="block mb-2">Email</Label>
|
||||
@@ -32,7 +52,13 @@
|
||||
<InputAddon>
|
||||
<EnvelopeOutline class="w-4 h-4 text-gray-500 dark:text-gray-400" />
|
||||
</InputAddon>
|
||||
<Input id="email" type="email" placeholder="name@flowbite.com"></Input>
|
||||
<Input
|
||||
id="email"
|
||||
type="email"
|
||||
name="email"
|
||||
placeholder="name@flowbite.com"
|
||||
required
|
||||
></Input>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
<div>
|
||||
@@ -41,44 +67,121 @@
|
||||
<InputAddon>
|
||||
<LockOutline class="w-4 h-4 text-gray-500 dark:text-gray-400" />
|
||||
</InputAddon>
|
||||
<Input id="password" type="password"></Input>
|
||||
<Input id="password" type="password" name="password" required></Input>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
<div>
|
||||
<Button type="submit" color="primary" class="w-full">Connect</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
color="primary"
|
||||
disabled={connecting}
|
||||
class="w-full"
|
||||
>
|
||||
{#if connecting}
|
||||
<Spinner class="me-3" size="4" color="white" />
|
||||
{/if}
|
||||
Connect</Button
|
||||
>
|
||||
</div>
|
||||
</form>
|
||||
<form>
|
||||
<form
|
||||
method="POST"
|
||||
action="?/register"
|
||||
use:enhance={() => {
|
||||
registering = true;
|
||||
return async ({ update }) => {
|
||||
await update();
|
||||
registering = false;
|
||||
};
|
||||
}}
|
||||
>
|
||||
<Heading tag="h3" class="mb-4">Welcome</Heading>
|
||||
<div>
|
||||
<Label for="username" class="block mb-2">Your Username</Label>
|
||||
<ButtonGroup class="w-full">
|
||||
<InputAddon>
|
||||
<UserOutline class="w-4 h-4 text-gray-500 dark:text-gray-400" />
|
||||
</InputAddon>
|
||||
<Input
|
||||
id="username"
|
||||
type="text"
|
||||
name="username"
|
||||
placeholder="Jean luc"
|
||||
required
|
||||
></Input>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
<div>
|
||||
<Label for="email" class="block mb-2">Your Email</Label>
|
||||
<ButtonGroup class="w-full">
|
||||
<InputAddon>
|
||||
<EnvelopeOutline class="w-4 h-4 text-gray-500 dark:text-gray-400" />
|
||||
</InputAddon>
|
||||
<Input id="email" type="email" placeholder="name@flowbite.com"></Input>
|
||||
<Input
|
||||
id="email"
|
||||
type="email"
|
||||
name="email"
|
||||
placeholder="name@flowbite.com"
|
||||
value={form?.data?.email}
|
||||
required
|
||||
></Input>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
<div>
|
||||
<Label for="password" class="block mb-2">Your Password</Label>
|
||||
<Label
|
||||
for="password"
|
||||
class="block mb-2"
|
||||
color={form?.errors?.password ? "red" : undefined}>Your Password</Label
|
||||
>
|
||||
<ButtonGroup class="w-full">
|
||||
<InputAddon>
|
||||
<LockOutline class="w-4 h-4 text-gray-500 dark:text-gray-400" />
|
||||
</InputAddon>
|
||||
<Input id="password" type="password"></Input>
|
||||
<Input
|
||||
color={form?.errors?.password ? "red" : undefined}
|
||||
id="password"
|
||||
name="password"
|
||||
type="password"
|
||||
required
|
||||
></Input>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
<div>
|
||||
<Label for="password-repeat" class="block mb-2">Repeat Password</Label>
|
||||
<Label
|
||||
for="password-repeat"
|
||||
color={form?.errors?.password ? "red" : undefined}
|
||||
class="block mb-2">Repeat Password</Label
|
||||
>
|
||||
<ButtonGroup class="w-full">
|
||||
<InputAddon>
|
||||
<LockOutline class="w-4 h-4 text-gray-500 dark:text-gray-400" />
|
||||
</InputAddon>
|
||||
<Input id="password-repeat" type="password"></Input>
|
||||
<Input
|
||||
id="password-repeat"
|
||||
name="password-repeat"
|
||||
type="password"
|
||||
color={form?.errors?.password ? "red" : undefined}
|
||||
required
|
||||
></Input>
|
||||
</ButtonGroup>
|
||||
{#if form?.errors?.password}
|
||||
<Helper class="mt-2" color="red"
|
||||
><span class="font-medium">Invalid!:</span> Passwords do not match</Helper
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
<div>
|
||||
<Button type="submit" color="primary" class="w-full">Register</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
color="primary"
|
||||
disabled={registering}
|
||||
class="w-full"
|
||||
>
|
||||
{#if registering}
|
||||
<Spinner class="me-3" size="4" color="white" />
|
||||
{/if}
|
||||
Register</Button
|
||||
>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user