Configure app for HTTPS reverse proxy on custom domain

This commit is contained in:
mARTin 2026-02-18 01:48:33 +01:00
parent 64955ad07b
commit 46dbf2ac02
16 changed files with 121 additions and 22 deletions

View File

@ -8,14 +8,14 @@ SMTP_USERNAME=mail@carrylight.de
SMTP_PASSWORD=QijU8e2A8p3FE8WS8esR SMTP_PASSWORD=QijU8e2A8p3FE8WS8esR
SMTP_SECURE=true SMTP_SECURE=true
# NextAuth Configuration NEXTAUTH_URL=https://todo.martin-bierschenk.de
NEXTAUTH_SECRET=Ca7EoJzZSMJO2CkqwdWd # NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_URL=http://localhost:3000
# Google OAuth (Replace with your credentials) # Google OAuth (Replace with your credentials)
GOOGLE_CLIENT_ID=196368743757-1fn17q2ecg5n8rej4tu96khltno173he.apps.googleusercontent.com GOOGLE_CLIENT_ID=196368743757-1fn17q2ecg5n8rej4tu96khltno173he.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=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 GOOGLE_CLIENT_SECRET=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
GOOGLE_REDIRECT_URI=http://localhost:3000/api/calendar/google/oauth GOOGLE_REDIRECT_URI=https://todo.martin-bierschenk.de/api/calendar/google/oauth
# GOOGLE_REDIRECT_URI=http://localhost:3000/api/calendar/google/oauth
# Apple Sign In (Replace with your credentials when ready) # Apple Sign In (Replace with your credentials when ready)
@ -23,4 +23,4 @@ GOOGLE_REDIRECT_URI=http://localhost:3000/api/calendar/google/oauth
# APPLE_SECRET=your-apple-secret # APPLE_SECRET=your-apple-secret
# Base URL for the application # Base URL for the application
NEXT_PUBLIC_BASE_URL=http://localhost:3001 NEXT_PUBLIC_BASE_URL=https://todo.martin-bierschenk.de

2
.gitignore vendored
View File

@ -25,3 +25,5 @@ node_modules/
# Next.js # Next.js
.next/ .next/
certificates

View File

@ -83,7 +83,8 @@ To enable "Sign in with Google":
5. Choose **Web application** 5. Choose **Web application**
6. Add these redirect URIs: 6. Add these redirect URIs:
- `http://localhost:3000/api/auth/callback/google` - `http://localhost:3000/api/auth/callback/google`
- `http://your-domain.com/api/auth/callback/google` (for production) - `https://todo.martin-bierschenk.de/api/auth/callback/google` (via Reverse Proxy)
- `https://todo.martin-bierschenk.de/api/calendar/google/oauth` (via Reverse Proxy)
7. Copy your **Client ID** and **Client Secret** 7. Copy your **Client ID** and **Client Secret**
### 2. Update Environment Variables ### 2. Update Environment Variables

View File

@ -8,7 +8,7 @@ services:
environment: environment:
- DATABASE_URL=postgresql://root:WKE7xeZohxdZit7eObjG@db:5432/My-Weekly-ToDo-List?schema=public - DATABASE_URL=postgresql://root:WKE7xeZohxdZit7eObjG@db:5432/My-Weekly-ToDo-List?schema=public
- NEXTAUTH_SECRET=Ca7EoJzZSMJO2CkqwdWd - NEXTAUTH_SECRET=Ca7EoJzZSMJO2CkqwdWd
- NEXT_PUBLIC_BASE_URL=http://localhost:3000 - NEXT_PUBLIC_BASE_URL=https://todo.martin-bierschenk.de
depends_on: depends_on:
- db - db
volumes: volumes:

View File

@ -8,7 +8,7 @@ services:
environment: environment:
- DATABASE_URL=postgresql://root:WKE7xeZohxdZit7eObjG@db:5432/My-Weekly-ToDo-List?schema=public - DATABASE_URL=postgresql://root:WKE7xeZohxdZit7eObjG@db:5432/My-Weekly-ToDo-List?schema=public
- NEXTAUTH_SECRET=Ca7EoJzZSMJO2CkqwdWd - NEXTAUTH_SECRET=Ca7EoJzZSMJO2CkqwdWd
- NEXT_PUBLIC_BASE_URL=http://localhost:13000 - NEXT_PUBLIC_BASE_URL=https://todo.martin-bierschenk.de
depends_on: depends_on:
- db - db
volumes: volumes:

View File

@ -1,6 +1,8 @@
/** @type {import('next').NextConfig} */ /** @type {import('next').NextConfig} */
const nextConfig = { const nextConfig = {
serverExternalPackages: ['ical.js', 'tsdav'], experimental: {
serverComponentsExternalPackages: ['ical.js', 'tsdav'],
},
}; };
module.exports = nextConfig; module.exports = nextConfig;

View File

@ -4,7 +4,7 @@
"description": "A web-based weekly task management application that organizes to-dos and calendar events in a single, intuitive weekly view", "description": "A web-based weekly task management application that organizes to-dos and calendar events in a single, intuitive weekly view",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"dev": "next dev", "dev": "next dev -H 0.0.0.0",
"build": "next build", "build": "next build",
"start": "next start", "start": "next start",
"lint": "next lint", "lint": "next lint",

View File

@ -25,6 +25,11 @@ export async function GET(request: NextRequest) {
const oauth2Client = new google.auth.OAuth2(clientId, clientSecret, redirectUri); const oauth2Client = new google.auth.OAuth2(clientId, clientSecret, redirectUri);
console.log('--- Google OAuth Debug ---');
console.log('Redirect URI:', redirectUri);
console.log('Client ID starts with:', clientId?.substring(0, 10));
console.log('--------------------------');
// Generate the authorization URL // Generate the authorization URL
const authUrl = oauth2Client.generateAuthUrl({ const authUrl = oauth2Client.generateAuthUrl({
access_type: 'offline', access_type: 'offline',

32
src/app/error.tsx Normal file
View File

@ -0,0 +1,32 @@
'use client';
import { useEffect } from 'react';
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
useEffect(() => {
// Log the error to an error reporting service
console.error(error);
}, [error]);
return (
<div className="flex min-h-screen flex-col items-center justify-center gap-4 p-4 text-center">
<h2 className="text-xl font-semibold">Something went wrong!</h2>
<p className="text-gray-500">{error.message || 'An unexpected error occurred'}</p>
<button
className="rounded bg-blue-500 px-4 py-2 text-white transition-colors hover:bg-blue-600"
onClick={
// Attempt to recover by trying to re-render the segment
() => reset()
}
>
Try again
</button>
</div>
);
}

28
src/app/global-error.tsx Normal file
View File

@ -0,0 +1,28 @@
'use client';
export default function GlobalError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
return (
<html>
<body>
<div className="flex h-screen w-full flex-col items-center justify-center bg-white text-black">
<h2 className="mb-4 text-2xl font-bold">Something went wrong!</h2>
<p className="mb-4 text-gray-600">
{error.message || 'A critical error occurred'}
</p>
<button
className="rounded bg-blue-500 px-6 py-3 text-white transition-colors hover:bg-blue-600"
onClick={() => reset()}
>
Try again
</button>
</div>
</body>
</html>
);
}

View File

@ -1,12 +1,15 @@
'use client';
import { SessionProvider } from 'next-auth/react';
import './globals.css';
import type { Metadata } from 'next'; import type { Metadata } from 'next';
import { Inter } from 'next/font/google'; import { Inter } from 'next/font/google';
import './globals.css';
import { Providers } from './providers';
const inter = Inter({ subsets: ['latin'] }); const inter = Inter({ subsets: ['latin'] });
export const metadata: Metadata = {
title: 'My Weekly To Do List',
description: 'A simple, designy to-do app.',
};
export default function RootLayout({ export default function RootLayout({
children, children,
}: { }: {
@ -15,9 +18,7 @@ export default function RootLayout({
return ( return (
<html lang="en"> <html lang="en">
<body className={inter.className}> <body className={inter.className}>
<SessionProvider> <Providers>{children}</Providers>
{children}
</SessionProvider>
</body> </body>
</html> </html>
); );

16
src/app/not-found.tsx Normal file
View File

@ -0,0 +1,16 @@
import Link from 'next/link';
export default function NotFound() {
return (
<div className="flex min-h-screen flex-col items-center justify-center gap-4 bg-white p-4 text-black">
<h2 className="text-2xl font-bold">Not Found</h2>
<p className="text-gray-600">Could not find requested resource</p>
<Link
href="/"
className="rounded bg-blue-500 px-4 py-2 text-white transition-colors hover:bg-blue-600"
>
Return Home
</Link>
</div>
);
}

7
src/app/providers.tsx Normal file
View File

@ -0,0 +1,7 @@
'use client';
import { SessionProvider } from 'next-auth/react';
export function Providers({ children }: { children: React.ReactNode }) {
return <SessionProvider>{children}</SessionProvider>;
}

View File

@ -16,6 +16,7 @@ export interface AppleCalendarEvent {
export interface AppleCalendar { export interface AppleCalendar {
id: string; id: string;
title: string; title: string;
color?: string;
isPrimary?: boolean; isPrimary?: boolean;
} }
@ -47,11 +48,15 @@ export const validateCredentials = async (email: string, appSpecificPassword: st
// Since tsdav creates a complex object graph, we'll wrap this in a try/catch // Since tsdav creates a complex object graph, we'll wrap this in a try/catch
const calendars = await client.fetchCalendars(); const calendars = await client.fetchCalendars();
return calendars.map(cal => ({ const mappedCalendars = calendars.map(cal => ({
id: cal.url, // Using URL as ID for CalDAV id: cal.url, // Using URL as ID for CalDAV
title: (cal.displayName as string) || 'Untitled Calendar', title: (cal.displayName as string) || 'Untitled Calendar',
color: cal.calendarColor,
isPrimary: false, // Hard to determine primary in generic CalDAV isPrimary: false, // Hard to determine primary in generic CalDAV
})); }));
console.log('[APPLE CALENDAR] Found calendars:', mappedCalendars.map(c => ({ title: c.title, color: c.color })));
return mappedCalendars;
} catch (error) { } catch (error) {
console.error('Apple Calendar validation failed:', error); console.error('Apple Calendar validation failed:', error);
throw new Error('Invalid credentials or unable to connect to iCloud.'); throw new Error('Invalid credentials or unable to connect to iCloud.');

View File

@ -319,7 +319,7 @@ export const getCalendarEvents = async (
source: 'apple' as const, source: 'apple' as const,
calendarId, calendarId,
calendarTitle: calendars.find(c => c.id === calendarId)?.title || 'Apple Calendar', calendarTitle: calendars.find(c => c.id === calendarId)?.title || 'Apple Calendar',
backgroundColor: '#FF3B30' // Value for Apple Color (Red) or derive from calendar? backgroundColor: calendars.find(c => c.id === calendarId)?.color || '#FF3B30'
}))); })));
} }
} else if (connection.provider === 'outlook') { } else if (connection.provider === 'outlook') {

File diff suppressed because one or more lines are too long