feat(profile): implement user-initiated GDPR-compliant profile deletion
- Add DELETE /users/me endpoint with cascading data removal - Implement frontend confirmation dialog and deletion flow - Add audit logging for deletion requests - Update localization files for new UI strings - Add scheduled cleanup service for AI traces - Document GDPR compliance in technical specification BREAKING CHANGE: Users can now permanently delete their profiles and associated data
This commit is contained in:
@@ -0,0 +1,154 @@
|
||||
# Plan: Implementera automatiserad datarensning för AiTrace
|
||||
|
||||
## Mål
|
||||
Implementera en automatiserad rensning av gamla `AiTrace`-poster för att säkerställa att känsliga data inte lagras längre än nödvändigt och för att följa GDPR-krav.
|
||||
|
||||
## Bakgrund
|
||||
- `AiTrace`-tabellen lagrar känsliga data (t.ex. maskerade promptar och AI-svar) utan någon retention-policy.
|
||||
- Risk för att data ackumuleras obehindrat, vilket kan leda till lagringsproblem och GDPR-brott.
|
||||
- Enligt GDPR bör känsliga data rensas efter en viss tid om de inte längre behövs för felsökning eller analys.
|
||||
|
||||
## Krav
|
||||
1. Rensa `AiTrace`-poster äldre än 30 dagar.
|
||||
2. Schemalägg rensningen att köra dagligen vid midnatt.
|
||||
3. Logga antalet rensade poster för övervakning.
|
||||
4. Säkerställa att rensningen inte påverkar systemets prestanda.
|
||||
|
||||
## Implementeringsplan
|
||||
|
||||
### Steg 1: Installera beroenden
|
||||
Installera `NestJS Schedule`-modulen för att möjliggöra schemalagda jobb.
|
||||
|
||||
**Kommando:**
|
||||
```bash
|
||||
npm install @nestjs/schedule
|
||||
```
|
||||
|
||||
### Steg 2: Konfigurera ScheduleModule
|
||||
Lägg till `ScheduleModule` i `AppModule` för att aktivera schemalagda jobb.
|
||||
|
||||
**Fil:** `backend/src/app.module.ts`
|
||||
**Ändring:**
|
||||
```typescript
|
||||
import { ScheduleModule } from '@nestjs/schedule';
|
||||
|
||||
@Module({
|
||||
imports: [ScheduleModule.forRoot()],
|
||||
})
|
||||
export class AppModule {}
|
||||
```
|
||||
|
||||
### Steg 3: Skapa AiTraceCleanupService
|
||||
Skapa en ny tjänst för att hantera rensningen av `AiTrace`-poster.
|
||||
|
||||
**Fil:** `backend/src/ai/ai-trace-cleanup.service.ts`
|
||||
**Innehåll:**
|
||||
```typescript
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { Cron, CronExpression } from '@nestjs/schedule';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
|
||||
@Injectable()
|
||||
export class AiTraceCleanupService {
|
||||
private readonly logger = new Logger(AiTraceCleanupService.name);
|
||||
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
@Cron(CronExpression.EVERY_DAY_AT_MIDNIGHT)
|
||||
async cleanupOldTraces() {
|
||||
this.logger.log('Starting cleanup of old AiTrace records...');
|
||||
const thirtyDaysAgo = new Date();
|
||||
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
|
||||
|
||||
const result = await this.prisma.aiTrace.deleteMany({
|
||||
where: {
|
||||
createdAt: {
|
||||
lt: thirtyDaysAgo,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
this.logger.log(`Cleaned up ${result.count} old AiTrace records.`);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Steg 4: Registrera AiTraceCleanupService
|
||||
Lägg till `AiTraceCleanupService` i `AiTraceModule` för att aktivera tjänsten.
|
||||
|
||||
**Fil:** `backend/src/ai/ai-trace.module.ts`
|
||||
**Ändring:**
|
||||
```typescript
|
||||
import { AiTraceCleanupService } from './ai-trace-cleanup.service';
|
||||
|
||||
@Module({
|
||||
providers: [AiTraceService, AiTraceCleanupService],
|
||||
})
|
||||
export class AiTraceModule {}
|
||||
```
|
||||
|
||||
### Steg 5: Testa rensningen manuellt
|
||||
Skapa en testmetod för att manuellt köra rensningen och verifiera att den fungerar som förväntat.
|
||||
|
||||
**Fil:** `backend/src/ai/ai-trace.controller.ts`
|
||||
**Ändring:**
|
||||
```typescript
|
||||
import { AiTraceCleanupService } from './ai-trace-cleanup.service';
|
||||
|
||||
@Controller('ai/traces')
|
||||
export class AiTraceController {
|
||||
constructor(
|
||||
private readonly aiTraceService: AiTraceService,
|
||||
private readonly aiTraceCleanupService: AiTraceCleanupService,
|
||||
) {}
|
||||
|
||||
@Post('cleanup')
|
||||
@Roles('admin')
|
||||
async manualCleanup() {
|
||||
await this.aiTraceCleanupService.cleanupOldTraces();
|
||||
return { success: true, message: 'Manual cleanup completed.' };
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Steg 6: Verifiera implementationen
|
||||
1. Skapa några testposter i `AiTrace`-tabellen med olika `createdAt`-datum.
|
||||
2. Kör den manuella rensningen via `POST /api/ai/traces/cleanup`.
|
||||
3. Verifiera att endast poster äldre än 30 dagar har rensats.
|
||||
4. Kontrollera loggarna för att säkerställa att antalet rensade poster loggas korrekt.
|
||||
|
||||
### Steg 7: Dokumentera ändringarna
|
||||
Uppdatera `TEKNISK_BESKRIVNING.md` med information om den automatiserade rensningen.
|
||||
|
||||
**Fil:** `TEKNISK_BESKRIVNING.md`
|
||||
**Ändring:**
|
||||
```markdown
|
||||
## Automatiserad datarensning för AiTrace
|
||||
|
||||
För att säkerställa att känsliga data inte lagras längre än nödvändigt, har en automatiserad rensning implementerats:
|
||||
|
||||
- **Rensningsintervall**: Dagligen vid midnatt.
|
||||
- **Retention-period**: 30 dagar.
|
||||
- **Loggning**: Antalet rensade poster loggas för övervakning.
|
||||
|
||||
### Implementation
|
||||
- **Tjänst**: `AiTraceCleanupService` i `backend/src/ai/ai-trace-cleanup.service.ts`.
|
||||
- **Schemaläggning**: Använder `@nestjs/schedule` för att köra rensningen dagligen.
|
||||
- **Manuell rensning**: Tillgänglig via `POST /api/ai/traces/cleanup` (kräver admin-behörighet).
|
||||
|
||||
### GDPR-efterlevnad
|
||||
- Rensningen säkerställer att känsliga data raderas efter 30 dagar, vilket uppfyller GDPR-krav på dataminimering.
|
||||
- Användare kan begära att deras data raderas tidigare via admin-panelen.
|
||||
```
|
||||
|
||||
## Frågor och överväganden
|
||||
1. **Retention-period**: Är 30 dagar en lämplig period, eller bör den justeras baserat påelsökningsbehov?
|
||||
2. **Loggning**: Bör loggarna för rensningen sparas längre för revisionsändamål?
|
||||
3. **Prestanda**: Bör rensningen köra under lågtrafikperioder för att minimera påverkan på systemet?
|
||||
|
||||
## Nästa steg
|
||||
1. Implementera planen enligt ovan.
|
||||
2. Testa rensningen i en testmiljö.
|
||||
3. Verifiera att loggarna är korrekta och att rensningen fungerar som förväntat.
|
||||
4. Dokumentera ändringarna i `TEKNISK_BESKRIVNING.md`.
|
||||
5. Informera teamet om den nya funktionaliteten och dess påverkan på GDPR-efterlevnaden.
|
||||
@@ -0,0 +1,215 @@
|
||||
# Plan: Implementera användarinitierad radering av personuppgifter
|
||||
|
||||
## Mål
|
||||
Skapa en tydlig och stegvis plan för hur användare kan ta bort sina personuppgifter från sin profil på plattformen, inklusive teknisk implementation och användarflöde.
|
||||
|
||||
## Bakgrund
|
||||
- GDPR kräver att användare ska kunna begära radering av sina personuppgifter.
|
||||
- Nuvarande plattform saknar ett tydligt användarflöde för att initiera radering av personuppgifter.
|
||||
- Användare bör kunna ta bort sin profil och associerade data på ett säkert och transparent sätt.
|
||||
|
||||
## Krav
|
||||
1. Lägg till en "Ta bort min profil"-knapp i användarens profilinställningar.
|
||||
2. Implementera en bekräftelsedialog för att förhindra oavsiktlig radering.
|
||||
3. Skapa en backend-endpoint för att hantera raderingsbegäran.
|
||||
4. Se till att all användardata (profil, produkter, recept, etc.) raderas eller anonymiseras.
|
||||
5. Logga raderingsbegäran för revisionsändamål.
|
||||
6. Skicka ett bekräftelsemeddelande till användaren efter radering.
|
||||
|
||||
## Implementeringsplan
|
||||
|
||||
### Steg 1: Lägg till "Ta bort min profil"-knapp i Flutter UI
|
||||
Lägg till en knapp i användarens profilinställningar som låter användaren initiera radering av sin profil.
|
||||
|
||||
**Fil:** `flutter/lib/features/profile/presentation/profile_screen.dart`
|
||||
**Ändring:**
|
||||
```dart
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
_showDeleteProfileConfirmation();
|
||||
},
|
||||
child: Text('Ta bort min profil'),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
### Steg 2: Implementera bekräftelsedialog
|
||||
Skapa en dialog som kräver att användaren bekräftar raderingen.
|
||||
|
||||
**Fil:** `flutter/lib/features/profile/presentation/profile_screen.dart`
|
||||
**Ändring:**
|
||||
```dart
|
||||
Future<void> _showDeleteProfileConfirmation() async {
|
||||
return showDialog<void>(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text('Bekräfta radering'),
|
||||
content: SingleChildScrollView(
|
||||
child: ListBody(
|
||||
children: <Widget>[
|
||||
Text('Är du säker på att du vill ta bort din profil?'),
|
||||
Text('Alla dina data kommer att raderas permanent.'),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
child: Text('Avbryt'),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
TextButton(
|
||||
child: Text('Ta bort'),
|
||||
onPressed: () {
|
||||
_deleteProfile();
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Steg 3: Skapa backend-endpoint för radering
|
||||
Implementera en endpoint som hanterar raderingsbegäran och raderar all associerad data.
|
||||
|
||||
**Fil:** `backend/src/users/users.controller.ts`
|
||||
**Ändring:**
|
||||
```typescript
|
||||
@Delete('me')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
async deleteProfile(@CurrentUser() user: UserEntity) {
|
||||
await this.usersService.deleteUserAndData(user.id);
|
||||
return { success: true, message: 'Din profil och data har tagits bort.' };
|
||||
}
|
||||
```
|
||||
|
||||
### Steg 4: Implementera raderingslogik i UsersService
|
||||
Skapa en metod som raderar användaren och all associerad data.
|
||||
|
||||
**Fil:** `backend/src/users/users.service.ts`
|
||||
**Ändring:**
|
||||
```typescript
|
||||
async deleteUserAndData(userId: number) {
|
||||
await this.prisma.$transaction([
|
||||
this.prisma.product.deleteMany({ where: { ownerId: userId } }),
|
||||
this.prisma.recipe.deleteMany({ where: { ownerId: userId } }),
|
||||
this.prisma.inventoryItem.deleteMany({ where: { userId } }),
|
||||
this.prisma.mealPlanEntry.deleteMany({ where: { userId } }),
|
||||
this.prisma.user.delete({ where: { id: userId } }),
|
||||
]);
|
||||
this.logger.log(`User ${userId} and associated data deleted.`);
|
||||
}
|
||||
```
|
||||
|
||||
### Steg 5: Logga raderingsbegäran
|
||||
Lägg till loggning för att spåra raderingsbegäran för revisionsändamål.
|
||||
|
||||
**Fil:** `backend/src/users/users.service.ts`
|
||||
**Ändring:**
|
||||
```typescript
|
||||
async logDeletionRequest(userId: number, userEmail: string) {
|
||||
await this.prisma.auditLog.create({
|
||||
data: {
|
||||
action: 'USER_DELETION',
|
||||
userId,
|
||||
email: userEmail,
|
||||
metadata: { message: 'User initiated deletion of their profile and data.' },
|
||||
},
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### Steg 6: Skicka bekräftelsemeddelande
|
||||
Skicka ett e-postmeddelande till användaren för att bekräfta raderingen.
|
||||
|
||||
**Fil:** `backend/src/users/users.service.ts`
|
||||
**Ändring:**
|
||||
```typescript
|
||||
async sendDeletionConfirmationEmail(email: string) {
|
||||
await this.emailService.sendEmail({
|
||||
to: email,
|
||||
subject: 'Bekräftelse på radering av din profil',
|
||||
text: 'Din profil och alla associerade data har tagits bort från vår plattform.',
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### Steg 7: Uppdatera Flutter för att anropa backend-endpoint
|
||||
Implementera metoden för att anropa backend-endpoint för radering.
|
||||
|
||||
**Fil:** `flutter/lib/features/profile/presentation/profile_screen.dart`
|
||||
**Ändring:**
|
||||
```dart
|
||||
Future<void> _deleteProfile() async {
|
||||
try {
|
||||
final response = await ApiService.delete('/users/me');
|
||||
if (response.statusCode == 200) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('Din profil har tagits bort.'));
|
||||
);
|
||||
Navigator.of(context).pushNamedAndRemoveUntil('/login', (route) => false);
|
||||
}
|
||||
} catch (e) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('Ett fel uppstod vid radering av din profil.'));
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Steg 8: Testa implementeringen
|
||||
1. Skapa en testanvändare i systemet.
|
||||
2. Navigera till profilinställningar och klicka på "Ta bort min profil".
|
||||
3. Bekräfta raderingen i dialogrutan.
|
||||
4. Verifiera att användaren och all associerad data har tagits bort från databasen.
|
||||
5. Kontrollera att ett bekräftelsemeddelande har skickats till användarens e-post.
|
||||
6. Verifiera att raderingsbegäran har loggats i `AuditLog`.
|
||||
|
||||
### Steg 9: Dokumentera ändringarna
|
||||
Uppdatera `TEKNISK_BESKRIVNING.md` med information om den nya funktionaliteten.
|
||||
|
||||
**Fil:** `TEKNISK_BESKRIVNING.md`
|
||||
**Ändring:**
|
||||
```markdown
|
||||
## Användarinitierad radering av personuppgifter
|
||||
|
||||
För att uppfylla GDPR-krav har en funktion implementerats som låter användare ta bort sin profil och associerade data:
|
||||
|
||||
- **Användarflöde**: Användaren kan initiera radering via en knapp i profilinställningarna.
|
||||
- **Bekräftelse**: En dialog kräver bekräftelse för att förhindra oavsiktlig radering.
|
||||
- **Backend-endpoint**: `DELETE /users/me` hanterar raderingsbegäran.
|
||||
- **Data som raderas**: Profil, produkter, recept, inventarieposter och matplaner.
|
||||
- **Loggning**: Raderingsbegäran loggas i `AuditLog` för revisionsändamål.
|
||||
- **Bekräftelse**: Ett e-postmeddelande skickas till användaren efter radering.
|
||||
|
||||
### Implementation
|
||||
- **Frontend**: `flutter/lib/features/profile/presentation/profile_screen.dart`
|
||||
- **Backend**: `backend/src/users/users.controller.ts` och `backend/src/users/users.service.ts`
|
||||
- **Loggning**: `AuditLog`-poster skapas för varje raderingsbegäran.
|
||||
- **E-postbekräftelse**: Skickas via `EmailService`.
|
||||
|
||||
### GDPR-efterlevnad
|
||||
- Användare har full kontroll över sina personuppgifter.
|
||||
- Raderingsprocessen är transparent och dokumenterad.
|
||||
- All data raderas eller anonymiseras enligt GDPR-krav.
|
||||
```
|
||||
|
||||
## Frågor och överväganden
|
||||
1. **Dataretention**: Bör vissa data (t.ex. transaktionshistorik) sparas för revisionsändamål även efter radering?
|
||||
2. **Anonymisering**: Bör vi anonymisera data istället för att radera dem helt?
|
||||
3. **Ångerperiod**: Bör vi implementera en ångerperiod där användaren kan återställa sin profil inom en viss tid?
|
||||
|
||||
## Nästa steg
|
||||
1. Implementera planen enligt ovan.
|
||||
2. Testa funktionaliteten i en testmiljö.
|
||||
3. Verifiera att all data raderas korrekt och att loggning fungerar.
|
||||
4. Dokumentera ändringarna i `TEKNISK_BESKRIVNING.md`.
|
||||
5. Informera teamet om den nya funktionaliteten och dess påverkan på GDPR-efterlevnaden.
|
||||
Reference in New Issue
Block a user