1
0
Fork 0
mirror of https://github.com/seanmorley15/AdventureLog.git synced 2025-07-19 21:09:37 +02:00

Refactor Docker Compose configuration and enhance email management in settings

This commit is contained in:
Sean Morley 2024-12-07 16:15:41 -05:00
parent 64105808b5
commit 6a00a2ed55
8 changed files with 172 additions and 62 deletions

View file

@ -1,7 +1,7 @@
services: services:
web: web:
build: ./frontend/ #build: ./frontend/
#image: ghcr.io/seanmorley15/adventurelog-frontend:latest image: ghcr.io/seanmorley15/adventurelog-frontend:latest
container_name: adventurelog-frontend container_name: adventurelog-frontend
restart: unless-stopped restart: unless-stopped
environment: environment:
@ -25,8 +25,8 @@ services:
- postgres_data:/var/lib/postgresql/data/ - postgres_data:/var/lib/postgresql/data/
server: server:
build: ./backend/ #build: ./backend/
#image: ghcr.io/seanmorley15/adventurelog-backend:latest image: ghcr.io/seanmorley15/adventurelog-backend:latest
container_name: adventurelog-backend container_name: adventurelog-backend
restart: unless-stopped restart: unless-stopped
environment: environment:

View file

@ -10,27 +10,3 @@ export const fetchCSRFToken = async () => {
return null; return null;
} }
}; };
export const tryRefreshToken = async (refreshToken: string) => {
const csrfToken = await fetchCSRFToken();
const refreshFetch = await fetch(`${serverEndpoint}/auth/token/refresh/`, {
method: 'POST',
headers: {
'X-CSRFToken': csrfToken,
'Content-Type': 'application/json' // Corrected header name
},
body: JSON.stringify({ refresh: refreshToken })
});
if (refreshFetch.ok) {
const refresh = await refreshFetch.json();
const token = `auth=${refresh.access}`;
return token;
// event.cookies.set('auth', `auth=${refresh.access}`, {
// httpOnly: true,
// sameSite: 'lax',
// expires: new Date(Date.now() + 60 * 60 * 1000), // 60 minutes
// path: '/'
// });
}
};

View file

@ -0,0 +1,94 @@
const PUBLIC_SERVER_URL = process.env['PUBLIC_SERVER_URL'];
const endpoint = PUBLIC_SERVER_URL || 'http://localhost:8000';
import { fetchCSRFToken } from '$lib/index.server';
import { json } from '@sveltejs/kit';
/** @type {import('./$types').RequestHandler} */
export async function GET(event) {
const { url, params, request, fetch, cookies } = event;
const searchParam = url.search ? `${url.search}&format=json` : '?format=json';
return handleRequest(url, params, request, fetch, cookies, searchParam);
}
/** @type {import('./$types').RequestHandler} */
export async function POST({ url, params, request, fetch, cookies }) {
const searchParam = url.search ? `${url.search}&format=json` : '?format=json';
return handleRequest(url, params, request, fetch, cookies, searchParam, true);
}
export async function PATCH({ url, params, request, fetch, cookies }) {
const searchParam = url.search ? `${url.search}&format=json` : '?format=json';
return handleRequest(url, params, request, fetch, cookies, searchParam, true);
}
export async function PUT({ url, params, request, fetch, cookies }) {
const searchParam = url.search ? `${url.search}&format=json` : '?format=json';
return handleRequest(url, params, request, fetch, cookies, searchParam, true);
}
export async function DELETE({ url, params, request, fetch, cookies }) {
const searchParam = url.search ? `${url.search}` : '';
return handleRequest(url, params, request, fetch, cookies, searchParam, false);
}
async function handleRequest(
url: any,
params: any,
request: any,
fetch: any,
cookies: any,
searchParam: string,
requreTrailingSlash: boolean | undefined = false
) {
const path = params.path;
let targetUrl = `${endpoint}/_allauth/${path}`;
// Ensure the path ends with a trailing slash
if (requreTrailingSlash && !targetUrl.endsWith('/')) {
targetUrl += '/';
}
// Append query parameters to the path correctly
targetUrl += searchParam; // This will add ?format=json or &format=json to the URL
const headers = new Headers(request.headers);
const csrfToken = await fetchCSRFToken();
if (!csrfToken) {
return json({ error: 'CSRF token is missing or invalid' }, { status: 400 });
}
try {
const response = await fetch(targetUrl, {
method: request.method,
headers: {
...Object.fromEntries(headers),
'X-CSRFToken': csrfToken,
Cookie: `csrftoken=${csrfToken}`
},
body:
request.method !== 'GET' && request.method !== 'HEAD' ? await request.text() : undefined,
credentials: 'include' // This line ensures cookies are sent with the request
});
if (response.status === 204) {
return new Response(null, {
status: 204,
headers: response.headers
});
}
const responseData = await response.text();
// Create a new Headers object without the 'set-cookie' header
const cleanHeaders = new Headers(response.headers);
cleanHeaders.delete('set-cookie');
return new Response(responseData, {
status: response.status,
headers: cleanHeaders
});
} catch (error) {
console.error('Error forwarding request:', error);
return json({ error: 'Internal Server Error' }, { status: 500 });
}
}

View file

@ -79,10 +79,13 @@ async function handleRequest(
} }
const responseData = await response.text(); const responseData = await response.text();
// Create a new Headers object without the 'set-cookie' header
const cleanHeaders = new Headers(response.headers);
cleanHeaders.delete('set-cookie');
return new Response(responseData, { return new Response(responseData, {
status: response.status, status: response.status,
headers: response.headers headers: cleanHeaders
}); });
} catch (error) { } catch (error) {
console.error('Error forwarding request:', error); console.error('Error forwarding request:', error);

View file

@ -12,9 +12,28 @@ export const load = (async (event) => {
}); });
let adventures = (await visitedFetch.json()) as Adventure[]; let adventures = (await visitedFetch.json()) as Adventure[];
let dates: Array<{
id: string;
start: string;
end: string;
title: string;
backgroundColor?: string;
}> = [];
adventures.forEach((adventure) => {
adventure.visits.forEach((visit) => {
dates.push({
id: adventure.id,
start: visit.start_date,
end: visit.end_date || visit.start_date,
title: adventure.name + (adventure.category?.icon ? ' ' + adventure.category.icon : '')
});
});
});
return { return {
props: { props: {
adventures adventures,
dates
} }
}; };
}) satisfies PageServerLoad; }) satisfies PageServerLoad;

View file

@ -11,24 +11,7 @@
export let data: PageData; export let data: PageData;
let adventures = data.props.adventures; let adventures = data.props.adventures;
let dates = data.props.dates;
let dates: Array<{
id: string;
start: string;
end: string;
title: string;
backgroundColor?: string;
}> = [];
adventures.forEach((adventure) => {
adventure.visits.forEach((visit) => {
dates.push({
id: adventure.id,
start: visit.start_date,
end: visit.end_date,
title: adventure.name + ' ' + adventure.category?.icon
});
});
});
let plugins = [TimeGrid, DayGrid]; let plugins = [TimeGrid, DayGrid];
let options = { let options = {

View file

@ -20,13 +20,24 @@ export const load: PageServerLoad = async (event) => {
}); });
let user = (await res.json()) as User; let user = (await res.json()) as User;
if (!res.ok) { let emailFetch = await fetch(`${endpoint}/_allauth/browser/v1/account/email`, {
headers: {
Cookie: `sessionid=${sessionId}`
}
});
let emailResponse = (await emailFetch.json()) as {
status: number;
data: { email: string; verified: boolean; primary: boolean }[];
};
let emails = emailResponse.data;
if (!res.ok || !emailFetch.ok) {
return redirect(302, '/'); return redirect(302, '/');
} }
return { return {
props: { props: {
user user,
emails
} }
}; };
}; };

View file

@ -9,8 +9,10 @@
export let data; export let data;
let user: User; let user: User;
let emails: typeof data.props.emails;
if (data.user) { if (data.user) {
user = data.user; user = data.user;
emails = data.props.emails;
} }
onMount(async () => { onMount(async () => {
@ -48,6 +50,22 @@
addToast('error', $t('adventures.error_updating_regions')); addToast('error', $t('adventures.error_updating_regions'));
} }
} }
async function removeEmail(email: { email: any; verified?: boolean; primary?: boolean }) {
let res = await fetch('/_allauth/browser/v1/account/email/', {
method: 'DELETE',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ email: email.email })
});
if (res.ok) {
addToast('success', 'Email removed');
emails = emails.filter((e) => e.email !== email.email);
} else {
addToast('error', 'Error removing email');
}
}
</script> </script>
<h1 class="text-center font-extrabold text-4xl mb-6">{$t('settings.settings_page')}</h1> <h1 class="text-center font-extrabold text-4xl mb-6">{$t('settings.settings_page')}</h1>
@ -160,17 +178,21 @@
<h1 class="text-center font-extrabold text-xl mt-4 mb-2">{$t('settings.email_change')}</h1> <h1 class="text-center font-extrabold text-xl mt-4 mb-2">{$t('settings.email_change')}</h1>
<div class="flex justify-center"> <div class="flex justify-center">
<form action="?/changeEmail" method="post" class="w-full max-w-xs"> <div>
<label for="current_email">{$t('settings.current_email')}</label> {#each emails as email}
<input <p>
type="email" {email.email}
name="current_email" {email.verified ? '✅' : '❌'}
placeholder={user.email || $t('settings.no_email_set')} {email.primary ? '🔑' : ''}
id="current_email" <button class="btn btn-sm btn-warning" on:click={() => removeEmail(email)}>Remove</button>
readonly </p>
class="block mb-2 input input-bordered w-full max-w-xs" {/each}
/> {#if emails.length === 0}
<br /> <p>No emails</p>
{/if}
</div>
<div>
<input <input
type="email" type="email"
name="new_email" name="new_email"
@ -178,8 +200,10 @@
id="new_email" id="new_email"
class="block mb-2 input input-bordered w-full max-w-xs" class="block mb-2 input input-bordered w-full max-w-xs"
/> />
</div>
<div>
<button class="py-2 px-4 btn btn-primary mt-2">{$t('settings.email_change')}</button> <button class="py-2 px-4 btn btn-primary mt-2">{$t('settings.email_change')}</button>
</form> </div>
</div> </div>
<div class="flex flex-col items-center mt-4"> <div class="flex flex-col items-center mt-4">