1
0
Fork 0
mirror of https://github.com/seanmorley15/AdventureLog.git synced 2025-07-19 04:49: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:
web:
build: ./frontend/
#image: ghcr.io/seanmorley15/adventurelog-frontend:latest
#build: ./frontend/
image: ghcr.io/seanmorley15/adventurelog-frontend:latest
container_name: adventurelog-frontend
restart: unless-stopped
environment:
@ -25,8 +25,8 @@ services:
- postgres_data:/var/lib/postgresql/data/
server:
build: ./backend/
#image: ghcr.io/seanmorley15/adventurelog-backend:latest
#build: ./backend/
image: ghcr.io/seanmorley15/adventurelog-backend:latest
container_name: adventurelog-backend
restart: unless-stopped
environment:

View file

@ -10,27 +10,3 @@ export const fetchCSRFToken = async () => {
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();
// 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: response.headers
headers: cleanHeaders
});
} catch (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 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 {
props: {
adventures
adventures,
dates
}
};
}) satisfies PageServerLoad;

View file

@ -11,24 +11,7 @@
export let data: PageData;
let adventures = data.props.adventures;
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 dates = data.props.dates;
let plugins = [TimeGrid, DayGrid];
let options = {

View file

@ -20,13 +20,24 @@ export const load: PageServerLoad = async (event) => {
});
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 {
props: {
user
user,
emails
}
};
};

View file

@ -9,8 +9,10 @@
export let data;
let user: User;
let emails: typeof data.props.emails;
if (data.user) {
user = data.user;
emails = data.props.emails;
}
onMount(async () => {
@ -48,6 +50,22 @@
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>
<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>
<div class="flex justify-center">
<form action="?/changeEmail" method="post" class="w-full max-w-xs">
<label for="current_email">{$t('settings.current_email')}</label>
<input
type="email"
name="current_email"
placeholder={user.email || $t('settings.no_email_set')}
id="current_email"
readonly
class="block mb-2 input input-bordered w-full max-w-xs"
/>
<br />
<div>
{#each emails as email}
<p>
{email.email}
{email.verified ? '✅' : '❌'}
{email.primary ? '🔑' : ''}
<button class="btn btn-sm btn-warning" on:click={() => removeEmail(email)}>Remove</button>
</p>
{/each}
{#if emails.length === 0}
<p>No emails</p>
{/if}
</div>
<div>
<input
type="email"
name="new_email"
@ -178,8 +200,10 @@
id="new_email"
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>
</form>
</div>
</div>
<div class="flex flex-col items-center mt-4">