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:
parent
64105808b5
commit
6a00a2ed55
8 changed files with 172 additions and 62 deletions
|
@ -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:
|
||||||
|
|
|
@ -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: '/'
|
|
||||||
// });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
94
frontend/src/routes/_allauth/[...path]/+server.ts
Normal file
94
frontend/src/routes/_allauth/[...path]/+server.ts
Normal 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 });
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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 = {
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -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">
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue