diff --git a/apps/manacore/apps/web/src/lib/api/services/my-data.ts b/apps/manacore/apps/web/src/lib/api/services/my-data.ts new file mode 100644 index 000000000..de63aa141 --- /dev/null +++ b/apps/manacore/apps/web/src/lib/api/services/my-data.ts @@ -0,0 +1,113 @@ +/** + * My Data API Service + * + * Self-service GDPR endpoints for users to view, export, and delete their own data. + */ + +import { browser } from '$app/environment'; +import { createApiClient, type ApiResult } from '../base-client'; +import { authStore } from '$lib/stores/auth.svelte'; + +// Re-export types from admin (same structure for user data) +export type { + UserDataSummary, + DeleteUserDataResponse, + ProjectDataSummary, + EntityCount, + UserInfo, + AuthDataSummary, + CreditsDataSummary, +} from './admin'; + +/** + * User data export with metadata + */ +export interface UserDataExport { + exportedAt: string; + exportVersion: string; + data: import('./admin').UserDataSummary; +} + +// Get Auth API URL dynamically at runtime +function getAuthApiUrl(): string { + if (browser && typeof window !== 'undefined') { + const injectedUrl = (window as unknown as { __PUBLIC_MANA_CORE_AUTH_URL__?: string }) + .__PUBLIC_MANA_CORE_AUTH_URL__; + if (injectedUrl) { + return `${injectedUrl}/api/v1`; + } + } + return 'http://localhost:3001/api/v1'; +} + +// Lazy-initialized client +let _client: ReturnType | null = null; + +function getClient() { + if (!_client) { + _client = createApiClient(getAuthApiUrl()); + } + return _client; +} + +/** + * My Data service for self-service data management + */ +export const myDataService = { + /** + * Get the authenticated user's data summary + */ + async getMyData(): Promise> { + return getClient().get('/me/data'); + }, + + /** + * Export user data as JSON file download + * Returns the full export object with metadata + */ + async exportMyData(): Promise> { + return getClient().get('/me/data/export'); + }, + + /** + * Trigger browser download of user data + */ + async downloadMyData(): Promise { + const baseUrl = getAuthApiUrl(); + const token = await authStore.getAccessToken(); + + // Use fetch with blob response for file download + const response = await fetch(`${baseUrl}/me/data/export`, { + method: 'GET', + headers: { + Authorization: `Bearer ${token}`, + }, + }); + + if (!response.ok) { + throw new Error(`Export failed: ${response.status}`); + } + + const blob = await response.blob(); + const filename = + response.headers.get('Content-Disposition')?.match(/filename="(.+)"/)?.[1] || + `meine-daten-${new Date().toISOString().split('T')[0]}.json`; + + // Create download link + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = filename; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + }, + + /** + * Delete all user data (GDPR right to be forgotten) + */ + async deleteMyData(): Promise> { + return getClient().delete('/me/data'); + }, +}; diff --git a/apps/manacore/apps/web/src/lib/components/my-data/DeleteConfirmationModal.svelte b/apps/manacore/apps/web/src/lib/components/my-data/DeleteConfirmationModal.svelte new file mode 100644 index 000000000..32bc20187 --- /dev/null +++ b/apps/manacore/apps/web/src/lib/components/my-data/DeleteConfirmationModal.svelte @@ -0,0 +1,254 @@ + + +{#if show} + +
+ +
+{/if} diff --git a/apps/manacore/apps/web/src/routes/(app)/settings/+page.svelte b/apps/manacore/apps/web/src/routes/(app)/settings/+page.svelte index 9f58fa940..dbc68e2c1 100644 --- a/apps/manacore/apps/web/src/routes/(app)/settings/+page.svelte +++ b/apps/manacore/apps/web/src/routes/(app)/settings/+page.svelte @@ -265,39 +265,69 @@ - + -
+
-

Gefahrenzone

-

Irreversible Aktionen

+

Meine Daten (DSGVO)

+

Datenschutz und Datenexport

-
-
+
+
-

Konto löschen

+

Daten ansehen & exportieren

- Das Löschen deines Kontos kann nicht rückgängig gemacht werden. + Sieh alle deine gespeicherten Daten ein und exportiere sie als JSON

- + + Meine Daten + + + + +
+ +
+
+
+

Konto loschen

+

+ Das Loschen deines Kontos kann nicht ruckgangig gemacht werden. +

+
+ + Verwalten + +
diff --git a/apps/manacore/apps/web/src/routes/(app)/settings/my-data/+page.svelte b/apps/manacore/apps/web/src/routes/(app)/settings/my-data/+page.svelte new file mode 100644 index 000000000..a5459cbc1 --- /dev/null +++ b/apps/manacore/apps/web/src/routes/(app)/settings/my-data/+page.svelte @@ -0,0 +1,391 @@ + + + + Meine Daten - Einstellungen + + +
+ +
+
+ + + + + +
+

Meine Daten

+

+ Ubersicht uber alle deine gespeicherten Daten (GDPR/DSGVO) +

+
+
+ {#if userData} + + {/if} +
+ + {#if loading} +
+ {#each Array(4) as _} +
+
+
+
+ {/each} +
+ {:else if error} +
+

{error}

+ +
+ {:else if userData} + + +
+
+
+ + {(userData.user.name || userData.user.email)[0].toUpperCase()} + +
+
+

{userData.user.name || 'Kein Name'}

+

{userData.user.email}

+
+ + {userData.user.role} + + {#if userData.user.emailVerified} + + + + + Email verifiziert + + {:else} + + + + + Email nicht verifiziert + + {/if} +
+

+ Registriert am {formatDate(userData.user.createdAt)} +

+
+
+
+
+ + +
+ + + + +
+ + +
+ + +
+

+ + + + Authentifizierung +

+
+
+ Aktive Sessions + {userData.auth.sessionsCount} +
+
+ Verknupfte Accounts + {userData.auth.accountsCount} +
+
+ 2FA aktiviert + + {userData.auth.has2FA ? 'Ja' : 'Nein'} + +
+
+ Letzter Login + + {userData.auth.lastLoginAt ? formatDate(userData.auth.lastLoginAt) : '-'} + +
+
+
+
+ + + +
+

+ + + + Credits +

+
+
+ Aktueller Stand + {userData.credits.balance} +
+
+ Gesamt verdient + +{userData.credits.totalEarned} +
+
+ Gesamt ausgegeben + -{userData.credits.totalSpent} +
+
+ Transaktionen + {userData.credits.transactionsCount} +
+
+
+
+
+ + +
+

Projektdaten

+
+ {#each userData.projects as project} + + {/each} +
+
+ + + +
+
+
+ + + +
+
+

Gefahrenzone

+

Diese Aktionen sind unwiderruflich

+
+
+ +
+
+
+

Alle meine Daten loschen

+

+ Loscht dein Konto und alle damit verbundenen Daten dauerhaft aus allen Projekten. + Diese Aktion kann nicht ruckgangig gemacht werden. +

+
+ +
+
+
+
+ {/if} +
+ + + diff --git a/services/mana-core-auth/src/admin/admin.module.ts b/services/mana-core-auth/src/admin/admin.module.ts index e726f6eb1..c3b78b6ba 100644 --- a/services/mana-core-auth/src/admin/admin.module.ts +++ b/services/mana-core-auth/src/admin/admin.module.ts @@ -17,5 +17,6 @@ import { AuthModule } from '../auth/auth.module'; ], controllers: [UserDataController], providers: [UserDataService, AdminGuard], + exports: [UserDataService], }) export class AdminModule {} diff --git a/services/mana-core-auth/src/app.module.ts b/services/mana-core-auth/src/app.module.ts index 3245b5baa..ff80934c1 100644 --- a/services/mana-core-auth/src/app.module.ts +++ b/services/mana-core-auth/src/app.module.ts @@ -13,6 +13,7 @@ import { HealthModule } from './health/health.module'; import { ReferralsModule } from './referrals/referrals.module'; import { SettingsModule } from './settings/settings.module'; import { TagsModule } from './tags/tags.module'; +import { MeModule } from './me/me.module'; import { AnalyticsModule } from './analytics'; import { MetricsModule } from './metrics'; import { HttpExceptionFilter } from './common/filters/http-exception.filter'; @@ -43,6 +44,7 @@ import { LoggerModule } from './common/logger'; ReferralsModule, SettingsModule, TagsModule, + MeModule, ], providers: [ { diff --git a/services/mana-core-auth/src/me/me.controller.ts b/services/mana-core-auth/src/me/me.controller.ts new file mode 100644 index 000000000..e4a2eae89 --- /dev/null +++ b/services/mana-core-auth/src/me/me.controller.ts @@ -0,0 +1,54 @@ +import { Controller, Get, Delete, UseGuards, Res, HttpStatus } from '@nestjs/common'; +import type { Response } from 'express'; +import { MeService } from './me.service'; +import { JwtAuthGuard } from '../common/guards/jwt-auth.guard'; +import { CurrentUser, type CurrentUserData } from '../common/decorators/current-user.decorator'; + +/** + * Self-service endpoints for users to manage their own data. + * GDPR compliance: view, export, and delete personal data. + * + * All endpoints require authentication via JwtAuthGuard. + * User ID is extracted from the JWT token - no userId parameter needed. + */ +@Controller('api/v1/me') +@UseGuards(JwtAuthGuard) +export class MeController { + constructor(private readonly meService: MeService) {} + + /** + * Get the authenticated user's data summary. + * Returns aggregated data from auth, credits, and all connected project backends. + */ + @Get('data') + async getMyData(@CurrentUser() user: CurrentUserData) { + return this.meService.getMyData(user.userId); + } + + /** + * Export the authenticated user's data as a JSON file download. + * GDPR Article 20: Right to data portability. + */ + @Get('data/export') + async exportMyData(@CurrentUser() user: CurrentUserData, @Res() res: Response) { + const exportData = await this.meService.exportMyData(user.userId); + + const filename = `meine-daten-${new Date().toISOString().split('T')[0]}.json`; + + res.setHeader('Content-Type', 'application/json'); + res.setHeader('Content-Disposition', `attachment; filename="${filename}"`); + res.status(HttpStatus.OK).send(JSON.stringify(exportData, null, 2)); + } + + /** + * Delete all data for the authenticated user. + * GDPR Article 17: Right to erasure ("right to be forgotten"). + * + * This performs a soft-delete of the user account and hard-deletes all associated data. + * The operation is irreversible. + */ + @Delete('data') + async deleteMyData(@CurrentUser() user: CurrentUserData) { + return this.meService.deleteMyData(user.userId); + } +} diff --git a/services/mana-core-auth/src/me/me.module.ts b/services/mana-core-auth/src/me/me.module.ts new file mode 100644 index 000000000..7a9ff4bf5 --- /dev/null +++ b/services/mana-core-auth/src/me/me.module.ts @@ -0,0 +1,20 @@ +import { Module } from '@nestjs/common'; +import { HttpModule } from '@nestjs/axios'; +import { ConfigModule } from '@nestjs/config'; +import { MeController } from './me.controller'; +import { MeService } from './me.service'; +import { AdminModule } from '../admin/admin.module'; + +@Module({ + imports: [ + ConfigModule, + HttpModule.register({ + timeout: 5000, + maxRedirects: 3, + }), + AdminModule, + ], + controllers: [MeController], + providers: [MeService], +}) +export class MeModule {} diff --git a/services/mana-core-auth/src/me/me.service.ts b/services/mana-core-auth/src/me/me.service.ts new file mode 100644 index 000000000..49da2699e --- /dev/null +++ b/services/mana-core-auth/src/me/me.service.ts @@ -0,0 +1,50 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { UserDataService } from '../admin/user-data.service'; +import type { UserDataSummary, DeleteUserDataResponse } from '../admin/dto/user-data.dto'; + +/** + * Self-service data management for authenticated users. + * Wraps UserDataService to allow users to access their own data without admin privileges. + */ +@Injectable() +export class MeService { + private readonly logger = new Logger(MeService.name); + + constructor(private readonly userDataService: UserDataService) {} + + /** + * Get the authenticated user's data summary + */ + async getMyData(userId: string): Promise { + this.logger.log(`User ${userId} requesting own data`); + return this.userDataService.getUserDataSummary(userId); + } + + /** + * Export the authenticated user's data as a complete JSON object + */ + async exportMyData(userId: string): Promise { + this.logger.log(`User ${userId} exporting own data`); + const summary = await this.userDataService.getUserDataSummary(userId); + + return { + exportedAt: new Date().toISOString(), + exportVersion: '1.0', + data: summary, + }; + } + + /** + * Delete all data for the authenticated user (GDPR right to be forgotten) + */ + async deleteMyData(userId: string): Promise { + this.logger.log(`User ${userId} requesting deletion of own data`); + return this.userDataService.deleteUserData(userId); + } +} + +export interface UserDataExport { + exportedAt: string; + exportVersion: string; + data: UserDataSummary; +}