Firestore Schema - GDPR Compliant Profile System
This document outlines the Firestore database schema for the three separate profile types in Sponsor-Dugnad, designed with GDPR compliance in mind.
1. Companies Collection
Collection Path: companies/{companyId}
Access Rules:
- Read/Write: Company owner (uid matches companyId) + GKIT support
- Public Read: Company profile pages (company_profiles subcollection)
Schema:
{
companyId: string; // Firebase Auth UID
name: string; // "Rema 1000 Oslo"
logo: string; // Emoji or URL
industry: string; // "Dagligvare", "Teknologi", etc.
contactEmail: string; // Primary contact
phone: string; // "+47 123 45 678"
createdAt: timestamp;
updatedAt: timestamp;
// Stats (aggregated from missions)
stats: {
totalSpent: number; // Total kr invested
missionsPosted: number; // Count of posted missions
clubsWorkedWith: number; // Unique clubs
avgRating: number; // Average 1-5 rating
};
// References
ownerId: string; // Firebase Auth UID of owner
adminIds: string[]; // Array of admin UIDs
}
Subcollections:
- missions/{missionId} - Posted missions
- applications/{applicationId} - Received applications
2. Clubs Collection
Collection Path: clubs/{clubId}
Access Rules: - Read/Write: Club admins (uid in adminIds array) + GKIT support - Read Only: Club members (uid in memberIds array)
Schema:
{
clubId: string; // Firebase Auth UID
name: string; // "Ski Golf Klubb"
logo: string; // Emoji or URL
category: string; // "Idrettslag", "Korps", "Speider", etc.
adminEmail: string; // Primary admin contact
phone: string; // "+47 987 65 432"
createdAt: timestamp;
updatedAt: timestamp;
// Stats (aggregated from missions)
stats: {
totalEarned: number; // Total kr earned
completedMissions: number; // Count of completed missions
activeMembers: number; // Count of active members
avgRating: number; // Average 1-5 rating
};
// Access control
adminIds: string[]; // Firebase Auth UIDs of admins
memberIds: string[]; // Firebase Auth UIDs of members
// Bank info (for payouts)
bankAccount: string; // Encrypted account number
organizationNumber: string; // "123 456 789"
}
Subcollections:
- applications/{applicationId} - Applied missions
- completed_missions/{missionId} - Completed missions with earnings
3. Club Members Collection ⚠️ GDPR PROTECTED
Collection Path: club_members/{memberId}
Access Rules: - Read/Write: ONLY the member themselves (uid === memberId) + GKIT support - NO public access - NO club admin access to personal data
Schema:
{
memberId: string; // Firebase Auth UID
// Personal Information (GDPR SENSITIVE)
personalInfo: {
name: string; // "Ole Hansen"
email: string; // "ole.hansen@example.com"
phone: string; // "+47 123 45 678"
dateOfBirth: string; // "1995-05-15" (ISO format)
address: string; // "Storgata 1, 0123 Oslo"
};
// Club affiliation (reference only)
clubAffiliation: {
clubId: string; // Reference to clubs/{clubId}
clubName: string; // Denormalized for display
joinedDate: timestamp;
role: string; // "admin" | "member"
memberNumber: string; // "SKI-2024-042"
};
// Stats (personal performance)
stats: {
totalEarnings: number; // Total kr earned personally
missionsCompleted: number; // Count of missions
hoursWorked: number; // Total hours
avgRating: number; // Average 1-5 rating
};
// Timestamps
createdAt: timestamp;
updatedAt: timestamp;
lastActive: timestamp;
}
Subcollections:
- mission_history/{missionId} - Personal mission participation records
Security Rules (Firestore)
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Helper function to check if user is GKIT support
function isGKITSupport() {
return request.auth.token.email.matches('.*@gkit.no$');
}
// Companies - owner + GKIT support only
match /companies/{companyId} {
allow read, write: if request.auth.uid == companyId || isGKITSupport();
}
// Clubs - admins + GKIT support
match /clubs/{clubId} {
allow read: if request.auth.uid in resource.data.memberIds || isGKITSupport();
allow write: if request.auth.uid in resource.data.adminIds || isGKITSupport();
}
// Club Members - STRICTEST ACCESS (GDPR)
match /club_members/{memberId} {
allow read, write: if request.auth.uid == memberId || isGKITSupport();
}
// Public company profiles (for /company/:id pages)
match /company_profiles/{companyId} {
allow read: if true; // Public
allow write: if request.auth.uid == companyId || isGKITSupport();
}
}
}
Data Privacy Compliance
GDPR Requirements Met:
- Data Minimization ✅
- Only collect necessary personal data
-
Separate sensitive data into protected collection
-
Access Control ✅
- Member data only accessible by member + support
-
Clear role-based permissions
-
Right to Access ✅
-
Members can view all their data via
/profil/member -
Right to Erasure ✅
- Implement delete functionality (future)
-
Cascading deletes for mission history
-
Data Portability ✅
-
Export function (future implementation)
-
Purpose Limitation ✅
- Data used only for platform operations
- Clear privacy policy (to be added)
Next Steps for GDPR Compliance
- Add privacy policy page
- Implement data export functionality
- Implement account deletion with cascade
- Add consent checkboxes during signup
- Implement data retention policies
- Add audit logging for sensitive data access