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

Open Locations in Maps and new API Routes

This commit is contained in:
Sean Morley 2025-02-28 15:50:49 -05:00 committed by GitHub
commit ed76c1426e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
31 changed files with 105 additions and 222 deletions

View file

@ -1,31 +1,6 @@
class AppVersionMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# Process request (if needed)
response = self.get_response(request)
# Add custom header to response
# Replace with your app version
response['X-AdventureLog-Version'] = '1.0.0'
return response
# make a middlewra that prints all of the request cookies
class PrintCookiesMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
print(request.COOKIES)
response = self.get_response(request)
return response
# middlewares.py
from django.conf import settings
from django.utils.deprecation import MiddlewareMixin
import os
from django.http import HttpRequest
class OverrideHostMiddleware:
def __init__(self, get_response):
@ -44,3 +19,14 @@ class OverrideHostMiddleware:
response = self.get_response(request)
return response
class XSessionTokenMiddleware(MiddlewareMixin):
def process_request(self, request):
session_token = request.headers.get('X-Session-Token')
if session_token:
request.COOKIES[settings.SESSION_COOKIE_NAME] = session_token
class DisableCSRFForSessionTokenMiddleware(MiddlewareMixin):
def process_request(self, request):
if 'X-Session-Token' in request.headers:
setattr(request, '_dont_enforce_csrf_checks', True)

View file

@ -133,35 +133,6 @@ class AdventureViewSet(viewsets.ModelViewSet):
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
@action(detail=False, methods=['get'])
def search(self, request):
query = request.query_params.get('query', '')
property = request.query_params.get('property', 'all')
if len(query) < 2:
return Response({"error": "Query must be at least 2 characters long"}, status=400)
valid_properties = ['name', 'location', 'description', 'activity_types']
if property not in valid_properties:
property = 'all'
filters = {
'name': Q(name__icontains=query),
'location': Q(location__icontains=query),
'description': Q(description__icontains=query),
'activity_types': Q(activity_types__icontains=query),
'all': Q(name__icontains=query) | Q(description__icontains=query) |
Q(location__icontains=query) | Q(activity_types__icontains=query)
}
queryset = Adventure.objects.filter(
filters[property] & (Q(user_id=request.user.id) | Q(is_public=True))
)
queryset = self.apply_sorting(queryset)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
def update(self, request, *args, **kwargs):
instance = self.get_object()
serializer = self.get_serializer(instance, data=request.data, partial=True)

View file

@ -14,13 +14,13 @@ class StatsViewSet(viewsets.ViewSet):
"""
A simple ViewSet for listing the stats of a user.
"""
@action(detail=False, methods=['get'], url_path='counts/(?P<username>[^/]+)')
@action(detail=False, methods=['get'], url_path='counts/(?P<username>[\w.@+-]+)')
def counts(self, request, username):
if request.user.username == username:
user = get_object_or_404(User, username=username)
else:
user = get_object_or_404(User, username=username, public_profile=True)
serializer = PublicUserSerializer(user)
# serializer = PublicUserSerializer(user)
# remove the email address from the response
user.email = None

View file

@ -69,6 +69,8 @@ INSTALLED_APPS = (
MIDDLEWARE = (
'whitenoise.middleware.WhiteNoiseMiddleware',
'adventures.middleware.XSessionTokenMiddleware',
'adventures.middleware.DisableCSRFForSessionTokenMiddleware',
'corsheaders.middleware.CorsMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
@ -133,6 +135,8 @@ FRONTEND_URL = unParsedFrontenedUrl.translate(str.maketrans('', '', '\'"'))
SESSION_COOKIE_SAMESITE = 'Lax'
SESSION_COOKIE_NAME = 'sessionid'
SESSION_COOKIE_SECURE = FRONTEND_URL.startswith('https')
hostname = urlparse(FRONTEND_URL).hostname

View file

@ -15,7 +15,7 @@ schema_view = get_schema_view(
urlpatterns = [
path('api/', include('adventures.urls')),
path('api/', include('worldtravel.urls')),
path("_allauth/", include("allauth.headless.urls")),
path("auth/", include("allauth.headless.urls")),
# Serve protected media files
re_path(r'^media/(?P<path>.*)$', serve_protected_media, name='serve-protected-media'),

View file

@ -8,7 +8,7 @@ class UserAPITestCase(APITestCase):
def setUp(self):
# Signup a new user
response = self.client.post('/_allauth/browser/v1/auth/signup', {
response = self.client.post('/auth/browser/v1/auth/signup', {
'username': 'testuser',
'email': 'testuser@example.com',
'password': 'testpassword',
@ -63,7 +63,7 @@ class UserAPITestCase(APITestCase):
def test_003_user_add_email(self):
# Update user email
response = self.client.post('/_allauth/browser/v1/account/email', {
response = self.client.post('/auth/browser/v1/account/email', {
'email': 'testuser2@example.com',
}, format='json')
self.assertEqual(response.status_code, 200)

View file

@ -49,9 +49,9 @@
reverseGeocode();
}
if (!item.name) {
item.name = markers[0].name;
}
// if (!item.name) {
// item.name = markers[0].name;
// }
}
$: if (triggerMarkVisted && willBeMarkedVisited) {
@ -193,7 +193,7 @@
) {
old_display_name = reverseGeocodePlace.display_name;
item.location = reverseGeocodePlace.display_name;
if (reverseGeocodePlace.location_name) {
if (reverseGeocodePlace.location_name && !item.name) {
item.name = reverseGeocodePlace.location_name;
}
}
@ -270,6 +270,8 @@
activity_type: place.type
}
];
item.name = place.name;
}}
>
{place.display_name}

View file

@ -37,7 +37,7 @@
}
async function fetchSetupInfo() {
const res = await fetch('/_allauth/browser/v1/account/authenticators/totp', {
const res = await fetch('/auth/browser/v1/account/authenticators/totp', {
method: 'GET'
});
const data = await res.json();
@ -53,7 +53,7 @@
}
async function sendTotp() {
const res = await fetch('/_allauth/browser/v1/account/authenticators/totp', {
const res = await fetch('/auth/browser/v1/account/authenticators/totp', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
@ -78,7 +78,7 @@
async function getRecoveryCodes() {
console.log('getting recovery codes');
const res = await fetch('/_allauth/browser/v1/account/authenticators/recovery-codes', {
const res = await fetch('/auth/browser/v1/account/authenticators/recovery-codes', {
method: 'GET'
});
if (res.ok) {

View file

@ -246,7 +246,8 @@
"lodging_information": "Unterkunftsinformationen",
"price": "Preis",
"reservation_number": "Reservierungsnummer",
"welcome_map_info": "Öffentliche Abenteuer auf diesem Server"
"welcome_map_info": "Öffentliche Abenteuer auf diesem Server",
"open_in_maps": "In Karten geöffnet"
},
"home": {
"desc_1": "Entdecken, planen und erkunden Sie mit Leichtigkeit",

View file

@ -113,6 +113,7 @@
"copy_link": "Copy Link",
"image": "Image",
"upload_image": "Upload Image",
"open_in_maps": "Open in Maps",
"url": "URL",
"fetch_image": "Fetch Image",
"wikipedia": "Wikipedia",

View file

@ -294,7 +294,8 @@
"price": "Precio",
"region": "Región",
"reservation_number": "Número de reserva",
"welcome_map_info": "Aventuras públicas en este servidor"
"welcome_map_info": "Aventuras públicas en este servidor",
"open_in_maps": "Abrir en mapas"
},
"worldtravel": {
"all": "Todo",

View file

@ -246,7 +246,8 @@
"price": "Prix",
"region": "Région",
"reservation_number": "Numéro de réservation",
"welcome_map_info": "Aventures publiques sur ce serveur"
"welcome_map_info": "Aventures publiques sur ce serveur",
"open_in_maps": "Ouvert dans les cartes"
},
"home": {
"desc_1": "Découvrez, planifiez et explorez en toute simplicité",

View file

@ -246,7 +246,8 @@
"price": "Prezzo",
"region": "Regione",
"welcome_map_info": "Avventure pubbliche su questo server",
"reservation_number": "Numero di prenotazione"
"reservation_number": "Numero di prenotazione",
"open_in_maps": "Aperto in mappe"
},
"home": {
"desc_1": "Scopri, pianifica ed esplora con facilità",

View file

@ -246,7 +246,8 @@
"price": "가격",
"region": "지역",
"reservation_number": "예약 번호",
"welcome_map_info": "이 서버의 공개 모험"
"welcome_map_info": "이 서버의 공개 모험",
"open_in_maps": "지도에서 열립니다"
},
"auth": {
"both_passwords_required": "두 암호 모두 필요합니다",

View file

@ -246,7 +246,8 @@
"lodging_delete_confirm": "Weet u zeker dat u deze accommodatielocatie wilt verwijderen? \nDeze actie kan niet ongedaan worden gemaakt.",
"lodging_information": "Informatie overliggen",
"price": "Prijs",
"region": "Regio"
"region": "Regio",
"open_in_maps": "Open in kaarten"
},
"home": {
"desc_1": "Ontdek, plan en verken met gemak",

View file

@ -294,7 +294,8 @@
"price": "Cena",
"region": "Region",
"reservation_number": "Numer rezerwacji",
"welcome_map_info": "Publiczne przygody na tym serwerze"
"welcome_map_info": "Publiczne przygody na tym serwerze",
"open_in_maps": "Otwarte w mapach"
},
"worldtravel": {
"country_list": "Lista krajów",

View file

@ -246,7 +246,8 @@
"lodging_information": "Logi information",
"price": "Pris",
"region": "Område",
"reservation_number": "Bokningsnummer"
"reservation_number": "Bokningsnummer",
"open_in_maps": "Kappas in"
},
"home": {
"desc_1": "Upptäck, planera och utforska med lätthet",

View file

@ -246,7 +246,8 @@
"price": "价格",
"region": "地区",
"reservation_number": "预订号",
"welcome_map_info": "该服务器上的公共冒险"
"welcome_map_info": "该服务器上的公共冒险",
"open_in_maps": "在地图上打开"
},
"home": {
"desc_1": "轻松发现、规划和探索",

View file

@ -41,7 +41,7 @@ export const actions: Actions = {
return;
}
const res = await fetch(`${serverEndpoint}/_allauth/browser/v1/auth/session`, {
const res = await fetch(`${serverEndpoint}/auth/browser/v1/auth/session`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',

View file

@ -1,101 +0,0 @@
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}` : '';
return handleRequest(url, params, request, fetch, cookies, searchParam, false);
}
export async function PATCH({ url, params, request, fetch, cookies }) {
const searchParam = url.search ? `${url.search}` : '';
return handleRequest(url, params, request, fetch, cookies, searchParam, false);
}
export async function PUT({ url, params, request, fetch, cookies }) {
const searchParam = url.search ? `${url.search}` : '';
return handleRequest(url, params, request, fetch, cookies, searchParam, false);
}
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);
// Delete existing csrf cookie by setting an expired date
cookies.delete('csrftoken', { path: '/' });
// Generate a new csrf token (using your existing fetchCSRFToken function)
const csrfToken = await fetchCSRFToken();
if (!csrfToken) {
return json({ error: 'CSRF token is missing or invalid' }, { status: 400 });
}
// Set the new csrf token in both headers and cookies
const cookieHeader = `csrftoken=${csrfToken}; Path=/; HttpOnly; SameSite=Lax`;
try {
const response = await fetch(targetUrl, {
method: request.method,
headers: {
...Object.fromEntries(headers),
'X-CSRFToken': csrfToken,
Cookie: cookieHeader
},
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

@ -436,6 +436,12 @@
</div>
</div>
{/if}
<a
class="btn btn-neutral btn-sm max-w-32"
href={`https://maps.apple.com/?q=${adventure.latitude},${adventure.longitude}`}
target="_blank"
rel="noopener noreferrer">{$t('adventures.open_in_maps')}</a
>
<MapLibre
style="https://basemaps.cartocdn.com/gl/voyager-gl-style/style.json"
class="flex items-center self-center justify-center aspect-[9/16] max-h-[70vh] sm:aspect-video sm:max-h-full w-10/12 rounded-lg"

View file

@ -12,8 +12,8 @@ export async function GET(event) {
/** @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);
const searchParam = url.search ? `${url.search}` : '';
return handleRequest(url, params, request, fetch, cookies, searchParam, false);
}
export async function PATCH({ url, params, request, fetch, cookies }) {
@ -27,8 +27,8 @@ export async function PUT({ url, params, request, fetch, cookies }) {
}
export async function DELETE({ url, params, request, fetch, cookies }) {
const searchParam = url.search ? `${url.search}&format=json` : '?format=json';
return handleRequest(url, params, request, fetch, cookies, searchParam, true);
const searchParam = url.search ? `${url.search}` : '';
return handleRequest(url, params, request, fetch, cookies, searchParam, false);
}
async function handleRequest(

View file

@ -21,7 +21,7 @@ export const load = (async (event) => {
let stats = null;
let res = await event.fetch(
`${serverEndpoint}/api/stats/counts/${event.locals.user.username}`,
`${serverEndpoint}/api/stats/counts/${event.locals.user.username}/`,
{
headers: {
Cookie: `sessionid=${event.cookies.get('sessionid')}`

View file

@ -42,7 +42,7 @@ export const actions: Actions = {
const csrfToken = await fetchCSRFToken();
// Initial login attempt
const loginFetch = await event.fetch(`${serverEndpoint}/_allauth/browser/v1/auth/login`, {
const loginFetch = await event.fetch(`${serverEndpoint}/auth/browser/v1/auth/login`, {
method: 'POST',
headers: {
'X-CSRFToken': csrfToken,
@ -69,7 +69,7 @@ export const actions: Actions = {
// Attempt MFA authentication
const sessionId = extractSessionId(loginFetch.headers.get('Set-Cookie'));
const mfaLoginFetch = await event.fetch(
`${serverEndpoint}/_allauth/browser/v1/auth/2fa/authenticate`,
`${serverEndpoint}/auth/browser/v1/auth/2fa/authenticate`,
{
method: 'POST',
headers: {

View file

@ -163,10 +163,21 @@
{/each}
</p>
{/if}
<button
class="btn btn-neutral btn-wide btn-sm mt-4"
on:click={() => goto(`/adventures/${adventure.id}`)}>{$t('map.view_details')}</button
>
<div class="flex flex-col">
{#if adventure.longitude && adventure.latitude}
<a
class="btn btn-neutral btn-wide btn-sm mt-4"
href={`https://maps.apple.com/?q=${adventure.latitude},${adventure.longitude}`}
target="_blank"
rel="noopener noreferrer">{$t('adventures.open_in_maps')}</a
>
{/if}
<button
class="btn btn-neutral btn-wide btn-sm mt-2"
on:click={() => goto(`/adventures/${adventure.id}`)}
>{$t('map.view_details')}</button
>
</div>
</Popup>
{/if}
</Marker>

View file

@ -31,7 +31,7 @@ export const load: PageServerLoad = async (event) => {
});
let user = (await res.json()) as User;
let emailFetch = await fetch(`${endpoint}/_allauth/browser/v1/account/email`, {
let emailFetch = await fetch(`${endpoint}/auth/browser/v1/account/email`, {
headers: {
Cookie: `sessionid=${sessionId}`
}
@ -45,14 +45,11 @@ export const load: PageServerLoad = async (event) => {
return redirect(302, '/');
}
let mfaAuthenticatorFetch = await fetch(
`${endpoint}/_allauth/browser/v1/account/authenticators`,
{
headers: {
Cookie: `sessionid=${sessionId}`
}
let mfaAuthenticatorFetch = await fetch(`${endpoint}/auth/browser/v1/account/authenticators`, {
headers: {
Cookie: `sessionid=${sessionId}`
}
);
});
let mfaAuthenticatorResponse = (await mfaAuthenticatorFetch.json()) as MFAAuthenticatorResponse;
let authenticators = (mfaAuthenticatorResponse.data.length > 0) as boolean;
@ -208,7 +205,7 @@ export const actions: Actions = {
let csrfToken = await fetchCSRFToken();
if (current_password) {
let res = await fetch(`${endpoint}/_allauth/browser/v1/account/password/change`, {
let res = await fetch(`${endpoint}/auth/browser/v1/account/password/change`, {
method: 'POST',
headers: {
Referer: event.url.origin, // Include Referer header
@ -226,7 +223,7 @@ export const actions: Actions = {
}
return { success: true };
} else {
let res = await fetch(`${endpoint}/_allauth/browser/v1/account/password/change`, {
let res = await fetch(`${endpoint}/auth/browser/v1/account/password/change`, {
method: 'POST',
headers: {
Referer: event.url.origin, // Include Referer header

View file

@ -72,7 +72,7 @@
}
async function removeEmail(email: { email: any; verified?: boolean; primary?: boolean }) {
let res = await fetch('/_allauth/browser/v1/account/email/', {
let res = await fetch('/auth/browser/v1/account/email', {
method: 'DELETE',
headers: {
'Content-Type': 'application/json'
@ -88,7 +88,7 @@
}
async function verifyEmail(email: { email: any; verified?: boolean; primary?: boolean }) {
let res = await fetch('/_allauth/browser/v1/account/email/', {
let res = await fetch('/auth/browser/v1/account/email/', {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
@ -103,7 +103,7 @@
}
async function addEmail() {
let res = await fetch('/_allauth/browser/v1/account/email/', {
let res = await fetch('/auth/browser/v1/account/email/', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
@ -122,7 +122,7 @@
}
async function primaryEmail(email: { email: any; verified?: boolean; primary?: boolean }) {
let res = await fetch('/_allauth/browser/v1/account/email/', {
let res = await fetch('/auth/browser/v1/account/email/', {
method: 'PATCH',
headers: {
'Content-Type': 'application/json'
@ -194,7 +194,7 @@
}
async function disableMfa() {
const res = await fetch('/_allauth/browser/v1/account/authenticators/totp', {
const res = await fetch('/auth/browser/v1/account/authenticators/totp', {
method: 'DELETE'
});
if (res.ok) {

View file

@ -51,7 +51,7 @@ export const actions: Actions = {
const tokenPromise = await csrfTokenFetch.json();
const csrfToken = tokenPromise.csrfToken;
const loginFetch = await event.fetch(`${serverEndpoint}/_allauth/browser/v1/auth/signup`, {
const loginFetch = await event.fetch(`${serverEndpoint}/auth/browser/v1/auth/signup`, {
method: 'POST',
headers: {
'X-CSRFToken': csrfToken,

View file

@ -16,7 +16,7 @@ export const actions: Actions = {
let csrfToken = await fetchCSRFToken();
let res = await fetch(`${endpoint}/_allauth/browser/v1/auth/password/request`, {
let res = await fetch(`${endpoint}/auth/browser/v1/auth/password/request`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',

View file

@ -29,20 +29,17 @@ export const actions: Actions = {
const serverEndpoint = PUBLIC_SERVER_URL || 'http://localhost:8000';
const csrfToken = await fetchCSRFToken();
const response = await event.fetch(
`${serverEndpoint}/_allauth/browser/v1/auth/password/reset`,
{
headers: {
'Content-Type': 'application/json',
Cookie: `csrftoken=${csrfToken}`,
'X-CSRFToken': csrfToken,
Referer: event.url.origin // Include Referer header
},
method: 'POST',
credentials: 'include',
body: JSON.stringify({ key: key, password: password })
}
);
const response = await event.fetch(`${serverEndpoint}/auth/browser/v1/auth/password/reset`, {
headers: {
'Content-Type': 'application/json',
Cookie: `csrftoken=${csrfToken}`,
'X-CSRFToken': csrfToken,
Referer: event.url.origin // Include Referer header
},
method: 'POST',
credentials: 'include',
body: JSON.stringify({ key: key, password: password })
});
if (response.status !== 401) {
const error_message = await response.json();

View file

@ -11,7 +11,7 @@ export const load = (async (event) => {
const serverEndpoint = PUBLIC_SERVER_URL || 'http://localhost:8000';
const csrfToken = await fetchCSRFToken();
let verifyFetch = await event.fetch(`${serverEndpoint}/_allauth/browser/v1/auth/email/verify`, {
let verifyFetch = await event.fetch(`${serverEndpoint}/auth/browser/v1/auth/email/verify`, {
headers: {
Cookie: `csrftoken=${csrfToken}`,
'X-CSRFToken': csrfToken