From 4a0228771eb6566d3c788bbdfab93a12b41288c5 Mon Sep 17 00:00:00 2001 From: Dejan Vasic Date: Sun, 19 Jan 2025 16:02:28 +1100 Subject: [PATCH] Implementing the server side API endpoints for managing friends --- src/lib/browserFetch.ts | 2 +- src/lib/server/mapApi.ts | 1 + src/routes/api/board/[id]/+server.ts | 4 ++-- src/routes/api/connections/+server.ts | 19 +++++++++++++++++ src/routes/api/friends/+server.ts | 4 +--- src/routes/api/friends/[id]/+server.ts | 22 ++++++++++++++++++++ src/routes/api/invites/[id]/+server.ts | 21 +++++++++++++++++++ src/routes/api/notes/+server.ts | 4 ++-- src/routes/api/notes/[id]/+server.ts | 8 +++---- src/routes/api/notes/[id]/editors/+server.ts | 4 ++-- src/routes/my/friends/v2/+page.svelte | 11 ++++++++-- 11 files changed, 84 insertions(+), 16 deletions(-) create mode 100644 src/routes/api/connections/+server.ts create mode 100644 src/routes/api/friends/[id]/+server.ts create mode 100644 src/routes/api/invites/[id]/+server.ts diff --git a/src/lib/browserFetch.ts b/src/lib/browserFetch.ts index a395773..f8ea974 100644 --- a/src/lib/browserFetch.ts +++ b/src/lib/browserFetch.ts @@ -43,7 +43,7 @@ export async function tryFetch( }); if (response.ok) { - const shouldParse = options?.shouldParse ?? true; + const shouldParse = options?.shouldParse ?? false; if (shouldParse) { const data = await response.json(); return some(data) as Maybe; diff --git a/src/lib/server/mapApi.ts b/src/lib/server/mapApi.ts index 5bee331..030f915 100644 --- a/src/lib/server/mapApi.ts +++ b/src/lib/server/mapApi.ts @@ -1,6 +1,7 @@ import type { ApiError, ServerError } from '$lib/types'; export const mapToApiError = (err: T): ApiError => { + console.error(err); switch (err._tag) { case 'DatabaseError': case 'SendEmailError': diff --git a/src/routes/api/board/[id]/+server.ts b/src/routes/api/board/[id]/+server.ts index d549123..81226f0 100644 --- a/src/routes/api/board/[id]/+server.ts +++ b/src/routes/api/board/[id]/+server.ts @@ -1,5 +1,5 @@ import type { RequestHandler } from '@sveltejs/kit'; -import { json } from '@sveltejs/kit'; +import { json, error } from '@sveltejs/kit'; import { taskEither as TE } from 'fp-ts'; import { pipe } from 'fp-ts/lib/function'; @@ -25,7 +25,7 @@ export const PATCH: RequestHandler = async ({ locals, params, request }) => { TE.flatMap(({ changes, board }) => updateBoard({ ...board, ...changes })), TE.mapLeft(mapToApiError), TE.match( - (err) => json({ message: err.message }, { status: err.status }), + (err) => error(err.status, { message: err.message }), (board) => json(board) ) )(); diff --git a/src/routes/api/connections/+server.ts b/src/routes/api/connections/+server.ts new file mode 100644 index 0000000..7994c6b --- /dev/null +++ b/src/routes/api/connections/+server.ts @@ -0,0 +1,19 @@ +import { type RequestHandler, json, error } from '@sveltejs/kit'; +import { pipe } from 'fp-ts/lib/function'; +import { taskEither as TE } from 'fp-ts'; +import { mapToApiError } from '$lib/server/mapApi'; +import { acceptInvite } from '$lib/server/services/friendService'; + +export const POST: RequestHandler = async ({ locals, request }) => { + const user = locals.user!; + const body = await request.json(); + + return pipe( + acceptInvite(body.inviteId, { id: user.id, email: user.email! }), + TE.mapLeft(mapToApiError), + TE.match( + (err) => error(err.status, { message: err.message }), + (connection) => json(connection) + ) + )(); +}; diff --git a/src/routes/api/friends/+server.ts b/src/routes/api/friends/+server.ts index 0003fd9..2a3bf63 100644 --- a/src/routes/api/friends/+server.ts +++ b/src/routes/api/friends/+server.ts @@ -18,9 +18,7 @@ export const GET: RequestHandler = ({ locals }) => { TE.bind('friends', () => getFriends(user.id)), TE.mapLeft(mapToApiError), TE.match( - (err) => { - throw error(err.status, err.message); - }, + (err) => error(err.status, { message: err.message }), ({ friends, pendingReceivedInvites, pendingSentInvites }) => json({ friends, diff --git a/src/routes/api/friends/[id]/+server.ts b/src/routes/api/friends/[id]/+server.ts new file mode 100644 index 0000000..f758db2 --- /dev/null +++ b/src/routes/api/friends/[id]/+server.ts @@ -0,0 +1,22 @@ +import { error, type RequestHandler } from '@sveltejs/kit'; +import { pipe } from 'fp-ts/lib/function'; +import { taskEither as TE } from 'fp-ts'; + +import { mapToApiError } from '$lib/server/mapApi'; +import { removeConnection } from '$lib/server/services/friendService'; + +export const DELETE: RequestHandler = ({ locals, params }) => { + if (!locals.user) { + return error(401, { message: 'Unauthorized' }); + } + + const friendId = params.id!; + return pipe( + removeConnection(locals.user!.id, friendId), + TE.mapLeft(mapToApiError), + TE.match( + (err) => error(err.status, { message: err.message }), + () => new Response(null, { status: 204 }) + ) + )(); +}; diff --git a/src/routes/api/invites/[id]/+server.ts b/src/routes/api/invites/[id]/+server.ts new file mode 100644 index 0000000..ca50da8 --- /dev/null +++ b/src/routes/api/invites/[id]/+server.ts @@ -0,0 +1,21 @@ +import { error, type RequestHandler } from '@sveltejs/kit'; +import { pipe } from 'fp-ts/lib/function'; +import { taskEither as TE } from 'fp-ts'; +import { mapToApiError } from '$lib/server/mapApi'; +import { cancelInvite } from '$lib/server/services/friendService'; + +export const DELETE: RequestHandler = ({ locals, params }) => { + if (!locals.user) { + return error(401, { message: 'Unauthorized' }); + } + + const inviteId = params.id!; + return pipe( + cancelInvite(inviteId), + TE.mapLeft(mapToApiError), + TE.match( + (err) => error(err.status, { message: err.message }), + () => new Response(null, { status: 204 }) + ) + )(); +}; diff --git a/src/routes/api/notes/+server.ts b/src/routes/api/notes/+server.ts index a02bdb5..6860554 100644 --- a/src/routes/api/notes/+server.ts +++ b/src/routes/api/notes/+server.ts @@ -1,4 +1,4 @@ -import { json, type RequestHandler } from '@sveltejs/kit'; +import { json, error, type RequestHandler } from '@sveltejs/kit'; import { pipe } from 'fp-ts/lib/function'; import { taskEither as TE } from 'fp-ts/lib'; @@ -27,7 +27,7 @@ export const POST: RequestHandler = async ({ locals, request }) => { TE.flatMap(({ note }) => createNote({ ...note, boardId: note.boardId! })), TE.mapLeft(mapToApiError), TE.match( - (error) => json(error, { status: error.status }), + (err) => error(err.status, { message: err.message }), (note) => json(note, { status: 201 }) ) )(); diff --git a/src/routes/api/notes/[id]/+server.ts b/src/routes/api/notes/[id]/+server.ts index 34ffe1d..f0164a8 100644 --- a/src/routes/api/notes/[id]/+server.ts +++ b/src/routes/api/notes/[id]/+server.ts @@ -1,4 +1,4 @@ -import { json, type RequestHandler } from '@sveltejs/kit'; +import { json, error, type RequestHandler } from '@sveltejs/kit'; import { taskEither as TE } from 'fp-ts'; import { pipe } from 'fp-ts/lib/function'; @@ -19,7 +19,7 @@ export const GET: RequestHandler = ({ locals, params }) => { TE.flatMap(({ user, note }) => isNoteOwner({ user, note })), TE.mapLeft(mapToApiError), TE.match( - (err) => json({ message: err.message }, { status: err.status }), + (err) => error(err.status, { message: err.message }), (note) => json(note) ) )(); @@ -37,7 +37,7 @@ export const PATCH: RequestHandler = async ({ locals, params, request }) => { TE.flatMap(({ noteInput, note }) => updateNote({ ...note, ...noteInput })), TE.mapLeft(mapToApiError), TE.match( - (err) => json({ message: err.message }, { status: err.status }), + (err) => error(err.status, { message: err.message }), (note) => json(note) ) )(); @@ -64,7 +64,7 @@ export const DELETE: RequestHandler = async ({ locals, params }) => { ), TE.mapLeft(mapToApiError), TE.match( - (err) => json({ message: err.message }, { status: err.status }), + (err) => error(err.status, { message: err.message }), () => new Response(null, { status: 204 }) ) )(); diff --git a/src/routes/api/notes/[id]/editors/+server.ts b/src/routes/api/notes/[id]/editors/+server.ts index 8f84814..9e672de 100644 --- a/src/routes/api/notes/[id]/editors/+server.ts +++ b/src/routes/api/notes/[id]/editors/+server.ts @@ -1,4 +1,4 @@ -import { type RequestHandler, json } from '@sveltejs/kit'; +import { type RequestHandler, json, error } from '@sveltejs/kit'; import { pipe } from 'fp-ts/lib/function.js'; import { taskEither as TE } from 'fp-ts'; @@ -18,7 +18,7 @@ export const POST: RequestHandler = async ({ request, params }) => { TE.flatMap((data) => updateNoteEditor(data)), TE.mapLeft(mapToApiError), TE.match( - (err) => json({ message: err.message }, { status: err.status }), + (err) => error(err.status, { message: err.message }), (data) => json(data) ) )(); diff --git a/src/routes/my/friends/v2/+page.svelte b/src/routes/my/friends/v2/+page.svelte index fd07273..a099ecf 100644 --- a/src/routes/my/friends/v2/+page.svelte +++ b/src/routes/my/friends/v2/+page.svelte @@ -50,7 +50,7 @@ async function handleCancelInvite(id: string) { const [index, invite] = friendsState.cancelInvite(id); - const result = await tryFetch(`/api/friends/invite/${id}`, { method: 'DELETE' }); + const result = await tryFetch(`/api/invites/${id}`, { method: 'DELETE' }); if (result.type === 'error') { toastMessages.addMessage({ type: 'error', @@ -74,7 +74,12 @@ async function handleAcceptInvite(id: string) { const [index, invite] = friendsState.acceptInvite(id); - const result = await tryFetch(`/api/friends/accept/${id}`, { method: 'POST' }); + const result = await tryFetch(`/api/connections`, { + method: 'POST', + body: JSON.stringify({ + inviteId: id + }) + }); if (result.type === 'error') { toastMessages.addMessage({ type: 'error', @@ -82,6 +87,8 @@ }); friendsState.addReceivedInviteAtIndex(index, invite); friendsState.removeFriend(invite.userId); + } else { + console.log('accepted invite', result.value); } }