readme und docs von der tenant app hinzugefügt
This commit is contained in:
318
docs/prp/13-backend-authentication.md
Normal file
318
docs/prp/13-backend-authentication.md
Normal file
@@ -0,0 +1,318 @@
|
|||||||
|
# 13 - Backend Authentication Implementation
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This document outlines the authentication requirements and implementation details for the Fotospiel tenant backend. The system uses OAuth2 with PKCE (Proof Key for Code Exchange) for secure authorization, providing tenant-specific access tokens for API operations.
|
||||||
|
|
||||||
|
## Authentication Flow
|
||||||
|
|
||||||
|
### 1. Authorization Request
|
||||||
|
- **Endpoint**: `POST /oauth/authorize`
|
||||||
|
- **Method**: GET (redirect from frontend)
|
||||||
|
- **Parameters**:
|
||||||
|
- `client_id`: Fixed client ID for tenant-admin-app (`tenant-admin-app`)
|
||||||
|
- `redirect_uri`: Frontend callback URL (e.g., `https://tenant-admin-app.example.com/auth/callback`)
|
||||||
|
- `response_type`: `code`
|
||||||
|
- `scope`: `tenant:read tenant:write` (tenant-specific scopes)
|
||||||
|
- `state`: CSRF protection state parameter
|
||||||
|
- `code_challenge`: PKCE challenge (SHA-256 hash of code verifier)
|
||||||
|
- `code_challenge_method`: `S256`
|
||||||
|
|
||||||
|
**Response**: Redirect to frontend with authorization code and state parameters.
|
||||||
|
|
||||||
|
### 2. Token Exchange
|
||||||
|
- **Endpoint**: `POST /oauth/token`
|
||||||
|
- **Method**: POST
|
||||||
|
- **Content-Type**: `application/x-www-form-urlencoded`
|
||||||
|
- **Parameters**:
|
||||||
|
- `grant_type`: `authorization_code` or `refresh_token`
|
||||||
|
- `client_id`: `tenant-admin-app`
|
||||||
|
- `code`: Authorization code (for initial exchange)
|
||||||
|
- `redirect_uri`: Same as authorization request
|
||||||
|
- `code_verifier`: PKCE verifier (plain text)
|
||||||
|
- `refresh_token`: For token refresh (optional)
|
||||||
|
|
||||||
|
**Response** (JSON):
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||||||
|
"refresh_token": "def50200...",
|
||||||
|
"expires_in": 3600,
|
||||||
|
"token_type": "Bearer",
|
||||||
|
"scope": "tenant:read tenant:write"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Token Refresh
|
||||||
|
- **Endpoint**: `POST /oauth/token`
|
||||||
|
- **Method**: POST
|
||||||
|
- **Content-Type**: `application/x-www-form-urlencoded`
|
||||||
|
- **Parameters**:
|
||||||
|
- `grant_type`: `refresh_token`
|
||||||
|
- `client_id`: `tenant-admin-app`
|
||||||
|
- `refresh_token`: Current refresh token
|
||||||
|
|
||||||
|
**Response**: Same as token exchange (new access and refresh tokens).
|
||||||
|
|
||||||
|
### 4. Token Validation
|
||||||
|
- **Endpoint**: `GET /api/v1/tenant/me`
|
||||||
|
- **Method**: GET
|
||||||
|
- **Authorization**: `Bearer {access_token}`
|
||||||
|
- **Purpose**: Validate token and return tenant information
|
||||||
|
|
||||||
|
**Response** (JSON):
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"tenant_id": "tenant-uuid",
|
||||||
|
"tenant_name": "Event Photo Company",
|
||||||
|
"credits": 150,
|
||||||
|
"role": "admin",
|
||||||
|
"email": "admin@eventphoto.com"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Security Requirements
|
||||||
|
|
||||||
|
### PKCE Implementation
|
||||||
|
- Validate `code_challenge_method` is `S256`
|
||||||
|
- Store `code_challenge` and `code_verifier` association temporarily (Redis, 5-minute expiry)
|
||||||
|
- Verify SHA-256 hash of `code_verifier` matches stored `code_challenge` during token exchange
|
||||||
|
- Reject requests without valid PKCE parameters
|
||||||
|
|
||||||
|
### State Validation
|
||||||
|
- Generate cryptographically secure state parameter
|
||||||
|
- Store state in session or Redis with 5-minute expiry
|
||||||
|
- Validate state parameter matches stored value during callback
|
||||||
|
- Reject requests with state mismatch (CSRF protection)
|
||||||
|
|
||||||
|
### Token Security
|
||||||
|
- Access tokens: JWT with 1-hour expiry, signed with RS256
|
||||||
|
- Refresh tokens: Secure random 128-character strings, stored in database
|
||||||
|
- Refresh tokens: Single-use or rotation (new refresh token with each refresh)
|
||||||
|
- Rate limiting on token endpoints (100 requests/hour per IP)
|
||||||
|
- IP binding for refresh tokens (optional for enhanced security)
|
||||||
|
|
||||||
|
### Scopes and Permissions
|
||||||
|
- `tenant:read`: Access to tenant data (events, photos, members)
|
||||||
|
- `tenant:write`: Create/update/delete tenant resources
|
||||||
|
- `tenant:admin`: Full administrative access (user management, billing)
|
||||||
|
- Token claims include `tenant_id` and `scope` for authorization
|
||||||
|
|
||||||
|
## Database Schema
|
||||||
|
|
||||||
|
### oauth_clients Table
|
||||||
|
```sql
|
||||||
|
CREATE TABLE oauth_clients (
|
||||||
|
id VARCHAR(255) PRIMARY KEY,
|
||||||
|
client_id VARCHAR(255) UNIQUE NOT NULL,
|
||||||
|
client_secret VARCHAR(255),
|
||||||
|
redirect_uris TEXT,
|
||||||
|
scopes TEXT DEFAULT 'tenant:read tenant:write',
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### oauth_codes Table (Temporary)
|
||||||
|
```sql
|
||||||
|
CREATE TABLE oauth_codes (
|
||||||
|
id VARCHAR(255) PRIMARY KEY,
|
||||||
|
client_id VARCHAR(255) NOT NULL,
|
||||||
|
user_id VARCHAR(255) NOT NULL,
|
||||||
|
code VARCHAR(255) UNIQUE NOT NULL,
|
||||||
|
code_challenge VARCHAR(255) NOT NULL,
|
||||||
|
state VARCHAR(255),
|
||||||
|
redirect_uri VARCHAR(255),
|
||||||
|
scope TEXT,
|
||||||
|
expires_at TIMESTAMP NOT NULL,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
INDEX idx_code (code),
|
||||||
|
INDEX idx_expires (expires_at)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### refresh_tokens Table
|
||||||
|
```sql
|
||||||
|
CREATE TABLE refresh_tokens (
|
||||||
|
id VARCHAR(255) PRIMARY KEY,
|
||||||
|
tenant_id VARCHAR(255) NOT NULL,
|
||||||
|
token VARCHAR(255) UNIQUE NOT NULL,
|
||||||
|
access_token VARCHAR(255),
|
||||||
|
expires_at TIMESTAMP NOT NULL,
|
||||||
|
scope TEXT,
|
||||||
|
ip_address VARCHAR(45),
|
||||||
|
user_agent TEXT,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
revoked_at TIMESTAMP NULL,
|
||||||
|
INDEX idx_tenant (tenant_id),
|
||||||
|
INDEX idx_token (token),
|
||||||
|
INDEX idx_expires (expires_at)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### tenant_tokens Table (JWT Blacklist)
|
||||||
|
```sql
|
||||||
|
CREATE TABLE tenant_tokens (
|
||||||
|
id VARCHAR(255) PRIMARY KEY,
|
||||||
|
tenant_id VARCHAR(255) NOT NULL,
|
||||||
|
jti VARCHAR(255) UNIQUE NOT NULL, -- JWT ID
|
||||||
|
token_type VARCHAR(50) NOT NULL,
|
||||||
|
expires_at TIMESTAMP NOT NULL,
|
||||||
|
revoked_at TIMESTAMP NULL,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
INDEX idx_jti (jti),
|
||||||
|
INDEX idx_tenant (tenant_id),
|
||||||
|
INDEX idx_expires (expires_at)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
## API Endpoints
|
||||||
|
|
||||||
|
### Authentication Endpoints
|
||||||
|
| Endpoint | Method | Description | Authentication |
|
||||||
|
|----------|--------|-------------|----------------|
|
||||||
|
| `/oauth/authorize` | GET | Authorization request | None |
|
||||||
|
| `/oauth/token` | POST | Token exchange/refresh | None |
|
||||||
|
| `/api/v1/tenant/me` | GET | Validate token | Bearer Token |
|
||||||
|
|
||||||
|
### Protected Endpoints
|
||||||
|
All tenant API endpoints require `Authorization: Bearer {access_token}` header.
|
||||||
|
|
||||||
|
#### Token Validation Middleware
|
||||||
|
```javascript
|
||||||
|
// Pseudocode
|
||||||
|
function validateTenantToken(req, res, next) {
|
||||||
|
const token = req.headers.authorization?.replace('Bearer ', '');
|
||||||
|
if (!token) return res.status(401).json({ error: 'Missing token' });
|
||||||
|
|
||||||
|
try {
|
||||||
|
const decoded = jwt.verify(token, process.env.JWT_SECRET);
|
||||||
|
const { tenant_id, scope, exp, jti } = decoded;
|
||||||
|
|
||||||
|
// Check if token is blacklisted
|
||||||
|
const blacklisted = await db.query('SELECT * FROM tenant_tokens WHERE jti = ? AND revoked_at IS NULL', [jti]);
|
||||||
|
if (blacklisted.length > 0) {
|
||||||
|
return res.status(401).json({ error: 'Token revoked' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check expiry
|
||||||
|
if (Date.now() >= exp * 1000) {
|
||||||
|
return res.status(401).json({ error: 'Token expired' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set tenant context
|
||||||
|
req.tenant = { id: tenant_id, scope };
|
||||||
|
next();
|
||||||
|
} catch (error) {
|
||||||
|
res.status(401).json({ error: 'Invalid token' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
|
||||||
|
### Backend (.env)
|
||||||
|
```
|
||||||
|
JWT_SECRET=your-super-secret-jwt-signing-key
|
||||||
|
API_BASE_URL=https://api.fotospiel.com
|
||||||
|
OAUTH_CLIENT_SECRET=your-oauth-client-secret
|
||||||
|
DATABASE_URL=your-database-connection-string
|
||||||
|
REDIS_URL=redis://localhost:6379
|
||||||
|
```
|
||||||
|
|
||||||
|
### Frontend (.env)
|
||||||
|
```
|
||||||
|
VITE_API_URL=https://api.fotospiel.com
|
||||||
|
VITE_OAUTH_CLIENT_ID=tenant-admin-app
|
||||||
|
```
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
### Common Error Responses
|
||||||
|
```json
|
||||||
|
// 400 Bad Request
|
||||||
|
{
|
||||||
|
"error": "invalid_request",
|
||||||
|
"error_description": "Missing required parameter: code"
|
||||||
|
}
|
||||||
|
|
||||||
|
// 401 Unauthorized
|
||||||
|
{
|
||||||
|
"error": "invalid_token",
|
||||||
|
"error_description": "Token signature invalid"
|
||||||
|
}
|
||||||
|
|
||||||
|
// 403 Forbidden
|
||||||
|
{
|
||||||
|
"error": "insufficient_scope",
|
||||||
|
"error_description": "Scope tenant:write required"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Implementation Notes
|
||||||
|
|
||||||
|
### 1. PKCE Storage
|
||||||
|
- Use Redis for temporary code_challenge storage (5-minute TTL)
|
||||||
|
- Key format: `pkce:{code_challenge}:{client_id}`
|
||||||
|
- Value: JSON with `code_verifier`, `user_id`, `redirect_uri`, `scope`
|
||||||
|
|
||||||
|
### 2. Refresh Token Rotation
|
||||||
|
- Issue new refresh token with each refresh
|
||||||
|
- Revoke old refresh token immediately
|
||||||
|
- Limit refresh tokens per tenant to 5 active
|
||||||
|
|
||||||
|
### 3. Rate Limiting
|
||||||
|
- Authorization requests: 10/minute per IP
|
||||||
|
- Token exchanges: 5/minute per IP
|
||||||
|
- Token validation: 100/minute per tenant
|
||||||
|
|
||||||
|
### 4. Logging and Monitoring
|
||||||
|
- Log all authentication attempts (success/failure)
|
||||||
|
- Monitor token usage patterns
|
||||||
|
- Alert on unusual activity (multiple failed attempts, token anomalies)
|
||||||
|
- Track refresh token usage for security analysis
|
||||||
|
|
||||||
|
### 5. Database Cleanup
|
||||||
|
- Cron job to remove expired authorization codes (daily)
|
||||||
|
- Remove expired refresh tokens (weekly)
|
||||||
|
- Clean blacklisted tokens after expiry (daily)
|
||||||
|
|
||||||
|
## Testing Requirements
|
||||||
|
|
||||||
|
### Unit Tests
|
||||||
|
- PKCE generation and validation
|
||||||
|
- State parameter security
|
||||||
|
- Token signing and verification
|
||||||
|
- Scope validation middleware
|
||||||
|
|
||||||
|
### Integration Tests
|
||||||
|
- Complete OAuth2 flow (authorize → token → validate)
|
||||||
|
- Token refresh cycle
|
||||||
|
- Error scenarios (invalid code, expired tokens, state mismatch)
|
||||||
|
- Concurrent access testing
|
||||||
|
|
||||||
|
### Security Tests
|
||||||
|
- CSRF protection validation
|
||||||
|
- PKCE bypass attempts
|
||||||
|
- Token replay attacks
|
||||||
|
- Rate limiting enforcement
|
||||||
|
|
||||||
|
## Deployment Considerations
|
||||||
|
|
||||||
|
### 1. Secrets Management
|
||||||
|
- Store JWT secret and OAuth client secret in secure vault (AWS Secrets Manager, HashiCorp Vault)
|
||||||
|
- Rotate secrets every 90 days
|
||||||
|
- Use different secrets for dev/staging/production
|
||||||
|
|
||||||
|
### 2. Certificate Management
|
||||||
|
- Use Let's Encrypt or commercial SSL certificates
|
||||||
|
- Rotate certificates before expiry
|
||||||
|
- Enable HSTS headers
|
||||||
|
|
||||||
|
### 3. Monitoring
|
||||||
|
- Track authentication success/failure rates
|
||||||
|
- Monitor token expiry patterns
|
||||||
|
- Alert on PKCE validation failures
|
||||||
|
- Log all security-related events
|
||||||
|
|
||||||
|
This implementation provides secure, scalable authentication for the Fotospiel tenant system, following OAuth2 best practices with PKCE for public clients.
|
||||||
183
docs/prp/14-freemium-business-model.md
Normal file
183
docs/prp/14-freemium-business-model.md
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
# 14 - Freemium Business Model Implementation
|
||||||
|
|
||||||
|
## Executive Summary
|
||||||
|
|
||||||
|
This document details the Freemium business model for the Fotospiel tenant app, combining free access with in-app purchases for event credits. The model prioritizes user acquisition through a free app download while monetizing through value-driven upgrades. Key metrics: 5-10% conversion rate, ARPU €10-15, scalable to 100k+ users.
|
||||||
|
|
||||||
|
## Model Analysis
|
||||||
|
|
||||||
|
### Model A: Paid App (€4.99) with Free First Event
|
||||||
|
**Pros:**
|
||||||
|
- Higher paying user conversion (20-30% retention)
|
||||||
|
- Premium perception for professional users
|
||||||
|
- Stable revenue from app sales (70% net after store fees)
|
||||||
|
- No freeloader problem
|
||||||
|
|
||||||
|
**Cons:**
|
||||||
|
- Lower acquisition (<5% download conversion)
|
||||||
|
- High churn if first event doesn't impress
|
||||||
|
- Slower scaling, higher marketing costs per install (€2-5)
|
||||||
|
|
||||||
|
### Model B: Free App with In-App Purchases (Recommended Base)
|
||||||
|
**Pros:**
|
||||||
|
- 10x more downloads, viral potential
|
||||||
|
- Low acquisition costs (€0.50-1 per install)
|
||||||
|
- Flexible pricing (starter €4.99, pro €9.99)
|
||||||
|
- Better App Store ranking for free apps
|
||||||
|
|
||||||
|
**Cons:**
|
||||||
|
- 80-95% freeloader rate
|
||||||
|
- Complex IAP setup and testing
|
||||||
|
- Requires strong onboarding to drive conversions
|
||||||
|
|
||||||
|
### Hybrid Freemium Recommendation
|
||||||
|
**Core Strategy:** Free app with limited first event (50 photos, basic features), unlimited upgrades via IAP credits/subscriptions.
|
||||||
|
|
||||||
|
**Pricing Structure:**
|
||||||
|
- **Free Tier:** 1 basic event (50 photos, standard tasks, no custom branding)
|
||||||
|
- **Consumable Credits:**
|
||||||
|
- Starter Pack: €4.99 for 5 events (100 photos each)
|
||||||
|
- Pro Pack: €14.99 for 20 events (unlimited photos)
|
||||||
|
- **Subscriptions:**
|
||||||
|
- Pro Unlimited: €4.99/month (all features, unlimited events)
|
||||||
|
- Agency: €19.99/month (multi-tenant, analytics, white-label)
|
||||||
|
- **Non-Consumables:** Lifetime Unlimited: €49.99 (one-time purchase)
|
||||||
|
|
||||||
|
**Expected Metrics:**
|
||||||
|
- Downloads: 50k/year
|
||||||
|
- Conversion: 5-8% (2,500-4,000 paying users)
|
||||||
|
- ARPU: €12 (mix of one-time + recurring)
|
||||||
|
- Annual Revenue: €30,000-48,000 (after 30% store fees)
|
||||||
|
|
||||||
|
## User Acquisition Strategy
|
||||||
|
|
||||||
|
### App Store Optimization (ASO)
|
||||||
|
- **Keywords:** "Event Foto App", "Hochzeit Galerie", "Party Foto Sharing", "Event Planner kostenlos"
|
||||||
|
- **Screenshots:** 5-6 screens showing value (event creation, photo upload, QR sharing)
|
||||||
|
- **Description:** 4,000 chars emphasizing "Free start, upgrade anytime"
|
||||||
|
- **App Preview Video:** 30s demo of creating/sharing first event
|
||||||
|
|
||||||
|
### Marketing Channels
|
||||||
|
- **Paid Ads:** Facebook/Instagram targeting wedding planners (€1-2/install)
|
||||||
|
- **Organic:** Wedding forums, photographer communities, local event groups
|
||||||
|
- **Partnerships:** Affiliate program for event agencies (20% commission)
|
||||||
|
- **Content Marketing:** Blog posts "How to create digital photo galleries for events"
|
||||||
|
|
||||||
|
### Retention & Re-engagement
|
||||||
|
- **Push Notifications:** "Your event ends soon - extend for €2.99" (opt-in only)
|
||||||
|
- **Email Marketing:** Post-event surveys with upgrade offers
|
||||||
|
- **In-App Messaging:** Contextual upgrade prompts after value demonstration
|
||||||
|
|
||||||
|
## Conversion Optimization
|
||||||
|
|
||||||
|
### Onboarding Funnel
|
||||||
|
1. **Download → First Launch:** 90% completion target
|
||||||
|
2. **Onboarding Wizard → First Event:** 80% completion
|
||||||
|
3. **First Event Success → Upgrade Prompt:** 10% conversion
|
||||||
|
4. **Repeat Usage → Subscription:** 20% of one-time buyers
|
||||||
|
|
||||||
|
### A/B Testing Plan
|
||||||
|
- **CTA Text:** "Get Started Free" vs "Create First Event"
|
||||||
|
- **Pricing Display:** Static prices vs dynamic local currency
|
||||||
|
- **Upgrade Timing:** After first photo upload vs after event sharing
|
||||||
|
- **Tools:** Firebase Remote Config + RevenueCat Experiments
|
||||||
|
|
||||||
|
## Technical Requirements
|
||||||
|
|
||||||
|
### Frontend (React/Capacitor)
|
||||||
|
- **IAP Integration:** RevenueCat SDK for cross-platform purchases
|
||||||
|
- **State Management:** Context API for credits, React Query for API sync
|
||||||
|
- **Offline Support:** Capacitor Storage for temporary event data
|
||||||
|
- **Analytics:** Firebase for funnel tracking, RevenueCat for purchase events
|
||||||
|
|
||||||
|
### Backend API Extensions
|
||||||
|
- **Credit Management:** `/api/v1/tenant/credits` endpoints
|
||||||
|
- **Purchase Validation:** Webhook receiver from RevenueCat
|
||||||
|
- **Event Limiting:** Middleware checking credit balance before creation
|
||||||
|
- **Subscription Sync:** Real-time updates via WebSockets (optional)
|
||||||
|
|
||||||
|
### Database Schema Additions
|
||||||
|
```sql
|
||||||
|
-- tenant_credits table
|
||||||
|
CREATE TABLE tenant_credits (
|
||||||
|
tenant_id VARCHAR(255) PRIMARY KEY,
|
||||||
|
balance INTEGER DEFAULT 1, -- 1 free event
|
||||||
|
total_purchased INTEGER DEFAULT 0,
|
||||||
|
subscription_active BOOLEAN DEFAULT FALSE,
|
||||||
|
subscription_tier VARCHAR(50),
|
||||||
|
last_sync TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- purchase_history table
|
||||||
|
CREATE TABLE purchase_history (
|
||||||
|
id VARCHAR(255) PRIMARY KEY,
|
||||||
|
tenant_id VARCHAR(255) NOT NULL,
|
||||||
|
package_id VARCHAR(255) NOT NULL,
|
||||||
|
credits_added INTEGER,
|
||||||
|
price DECIMAL(10,2),
|
||||||
|
currency VARCHAR(3),
|
||||||
|
platform VARCHAR(50), -- 'ios' or 'android'
|
||||||
|
transaction_id VARCHAR(255),
|
||||||
|
purchased_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (tenant_id) REFERENCES tenants(id)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Risk Mitigation
|
||||||
|
|
||||||
|
### Store Compliance
|
||||||
|
- **No Paywalls Before Value:** First event creation always free
|
||||||
|
- **Clear Descriptions:** App Store screenshots show free tier capabilities
|
||||||
|
- **Receipt Validation:** All purchases server-side verified
|
||||||
|
- **Refund Policy:** 14-day money-back for subscriptions
|
||||||
|
|
||||||
|
### Technical Risks
|
||||||
|
- **IAP Failures:** Fallback to manual credit allocation via support
|
||||||
|
- **Sync Issues:** Offline-first approach, sync on reconnect
|
||||||
|
- **Store Rejections:** Beta testing with TestFlight/Internal Testing
|
||||||
|
- **Revenue Recognition:** Use RevenueCat for proper accounting
|
||||||
|
|
||||||
|
### Business Risks
|
||||||
|
- **Low Conversion:** Monthly A/B testing and user surveys
|
||||||
|
- **Churn:** Re-engagement campaigns for inactive users
|
||||||
|
- **Competition:** Regular competitor analysis and feature updates
|
||||||
|
- **Platform Changes:** Monitor Apple/Google policy updates
|
||||||
|
|
||||||
|
## Launch Timeline
|
||||||
|
|
||||||
|
### Phase 1: MVP (4 weeks)
|
||||||
|
- Week 1: RevenueCat integration, basic credit system
|
||||||
|
- Week 2: IAP store page, purchase flow
|
||||||
|
- Week 3: Backend endpoints, database schema
|
||||||
|
- Week 4: Testing, beta distribution
|
||||||
|
|
||||||
|
### Phase 2: Optimization (Ongoing)
|
||||||
|
- Month 1: A/B testing of upgrade prompts
|
||||||
|
- Month 2: Push notifications, email marketing
|
||||||
|
- Month 3: Subscription model introduction
|
||||||
|
- Quarterly: Feature updates based on user feedback
|
||||||
|
|
||||||
|
## Success Metrics
|
||||||
|
|
||||||
|
### Acquisition
|
||||||
|
- Downloads: 10k in first 3 months
|
||||||
|
- Cost per Install: <€1.50
|
||||||
|
- App Store Rating: >4.5 stars
|
||||||
|
|
||||||
|
### Conversion
|
||||||
|
- Free → Paid: 5% within 30 days
|
||||||
|
- First Event → Upgrade: 15% conversion
|
||||||
|
- Average Order Value: €8-12
|
||||||
|
|
||||||
|
### Retention
|
||||||
|
- Day 1 Retention: >40%
|
||||||
|
- Day 7 Retention: >25%
|
||||||
|
- Monthly Active Users: 20% of downloads
|
||||||
|
|
||||||
|
### Revenue
|
||||||
|
- Month 1 Revenue: €1,000-2,000
|
||||||
|
- ARPU: €0.50-1.00 overall
|
||||||
|
- LTV >3x acquisition cost
|
||||||
|
|
||||||
|
This Freemium model balances user acquisition with sustainable revenue growth, leveraging the event-based nature of the app for recurring purchases while maintaining an accessible entry point.
|
||||||
43
docs/prp/tenant-app-specs/README.md
Normal file
43
docs/prp/tenant-app-specs/README.md
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
# Detaillierte PRP für Tenant Admin App (Capacitor + Framework7)
|
||||||
|
|
||||||
|
## Status
|
||||||
|
- **Aktiv**: Erste Version (2025-09-13)
|
||||||
|
- **Version**: 1.0.0
|
||||||
|
- **Autor**: Sonoma (AI Architect)
|
||||||
|
- **Supersedes**: docs/prp/06-tenant-admin-pwa.md (erweitert und detailliert)
|
||||||
|
|
||||||
|
## Überblick
|
||||||
|
Diese detaillierte Product Requirement Plan (PRP) beschreibt die Spezifikationen für die Tenant Admin App. Die App ist eine store-ready mobile Anwendung, die mit Capacitor für iOS und Trusted Web Activity (TWA) für Android gepackt wird. Die UI basiert auf Framework7 für ein natives Mobile-Erlebnis. Die App ermöglicht Tenant-Admins (z.B. Event-Organisatoren) die vollständige Verwaltung ihrer Events, Galerien, Mitglieder, Einstellungen und Käufe über eine API-first Backend-Integration.
|
||||||
|
|
||||||
|
Die App ersetzt das frühere Filament-basierte Tenant-Panel und fokussiert auf Mobile-First-UX mit Offline-Fähigkeiten, Push-Notifications und sicherer Authentifizierung. Sie respektiert das Multi-Tenancy-Modell und GDPR-Anforderungen.
|
||||||
|
|
||||||
|
## Kernziele
|
||||||
|
- **Deliverables**: Voll funktionsfähige App mit CRUD-Operationen für Tenant-Ressourcen (Events, Photos, Tasks, etc.).
|
||||||
|
- **UI/UX**: Framework7-Komponenten für konsistente, native Mobile-Interfaces (iOS/Android).
|
||||||
|
- **Technologie-Stack**: React/Vite (Core), Framework7 (UI), Capacitor (Native), OAuth2 + PKCE (Auth).
|
||||||
|
- **Distribution**: App Store (iOS), Google Play (Android), PWA-Install (Web).
|
||||||
|
|
||||||
|
## Struktur dieser PRP
|
||||||
|
- **README.md**: Dieser Überblick.
|
||||||
|
- **functional-specs.md**: Funktionale Anforderungen, Capabilities und API-Integration.
|
||||||
|
- **pages-ui.md**: Detaillierte Seitenbeschreibungen, Framework7-Komponenten und Wireframe-Ideen.
|
||||||
|
- **settings-config.md**: App- und Tenant-spezifische Einstellungen, Capacitor-Plugins.
|
||||||
|
- **capacitor-setup.md**: Packaging, Distribution und Native-Features.
|
||||||
|
|
||||||
|
## Referenzen
|
||||||
|
- **Haupt-PRP**: docs/prp/README.md
|
||||||
|
- **Tenancy**: docs/prp/02-tenancy.md
|
||||||
|
- **API**: docs/prp/03-api.md
|
||||||
|
- **Bestehende Tenant PWA**: docs/prp/06-tenant-admin-pwa.md
|
||||||
|
- **Addendum**: docs/prp-addendum-2025-09-08-tenant-admin-pwa.md
|
||||||
|
- **ADR**: docs/adr/ADR-0006-tenant-admin-pwa.md
|
||||||
|
- **Billing**: docs/prp/08-billing.md
|
||||||
|
- **Glossar**: docs/prp/99-glossary.md
|
||||||
|
|
||||||
|
## Änderungen und Erweiterungen
|
||||||
|
Diese PRP erweitert die knappe Beschreibung in 06-tenant-admin-pwa.md um:
|
||||||
|
- Spezifische Seiten und UI-Elemente mit Framework7.
|
||||||
|
- Detaillierte Settings und Capacitor-Integration.
|
||||||
|
- Mobile-spezifische Features wie Push-Notifications und Offline-Sync.
|
||||||
|
|
||||||
|
Für Feedback oder Änderungen: Siehe TODO.md oder Issues.
|
||||||
257
docs/prp/tenant-app-specs/api-usage.md
Normal file
257
docs/prp/tenant-app-specs/api-usage.md
Normal file
@@ -0,0 +1,257 @@
|
|||||||
|
# API-Nutzung der Tenant Admin App
|
||||||
|
|
||||||
|
Diese Dokumentation beschreibt alle API-Endpunkte, die die Tenant Admin App mit der Backend-Hauptapp kommuniziert. Alle Requests sind tenant-scoped und erfordern JWT-Authentifizierung.
|
||||||
|
|
||||||
|
## Authentifizierung
|
||||||
|
|
||||||
|
### OAuth2 Flow (PKCE)
|
||||||
|
- **Start**: `GET /oauth/authorize` (Browser-Redirect mit PKCE-Challenge)
|
||||||
|
- **Params**: `client_id`, `redirect_uri`, `scope=tenant:read tenant:write`, `state`, `code_challenge`, `code_challenge_method=S256`
|
||||||
|
- **Response**: Authorization Code
|
||||||
|
|
||||||
|
- **Token Exchange**: `POST /oauth/token`
|
||||||
|
- **Body**: `grant_type=authorization_code`, `client_id`, `code`, `redirect_uri`, `code_verifier`
|
||||||
|
- **Response**: `{ access_token, refresh_token, expires_in, token_type }`
|
||||||
|
- **Headers**: `Content-Type: application/x-www-form-urlencoded`
|
||||||
|
|
||||||
|
- **Token Refresh**: `POST /oauth/token`
|
||||||
|
- **Body**: `grant_type=refresh_token`, `client_id`, `refresh_token`
|
||||||
|
- **Response**: Neuer Access/Refresh-Token
|
||||||
|
|
||||||
|
- **Token Validation**: `GET /api/v1/tenant/me`
|
||||||
|
- **Headers**: `Authorization: Bearer {access_token}`
|
||||||
|
- **Response**: `{ id, email, tenant_id, role, name }`
|
||||||
|
|
||||||
|
## Dashboard
|
||||||
|
|
||||||
|
### Stats laden
|
||||||
|
- **GET /api/v1/tenant/dashboard**
|
||||||
|
- **Headers**: `Authorization: Bearer {token}`
|
||||||
|
- **Response**: `{ credits, active_events, new_photos, task_progress }`
|
||||||
|
- **Zweck**: Übersicht-Daten für Dashboard-Cards
|
||||||
|
|
||||||
|
## Events
|
||||||
|
|
||||||
|
### Events-Liste
|
||||||
|
- **GET /api/v1/tenant/events**
|
||||||
|
- **Headers**: `Authorization: Bearer {token}`
|
||||||
|
- **Params**:
|
||||||
|
- `page=1` (Pagination)
|
||||||
|
- `per_page=50` (max für Mobile)
|
||||||
|
- `status=draft|active|archived` (Filter)
|
||||||
|
- `search=query` (Suche in Titel/Ort)
|
||||||
|
- **Response**: `{ data: Event[], current_page, last_page, total }`
|
||||||
|
- **Event-Shape**: `{ id, title, date, location, status, photoCount, slug }`
|
||||||
|
|
||||||
|
### Event erstellen
|
||||||
|
- **POST /api/v1/tenant/events**
|
||||||
|
- **Headers**: `Authorization: Bearer {token}`, `Content-Type: application/json`
|
||||||
|
- **Body**: `{ title, date, location, description }`
|
||||||
|
- **Response**: 201 Created mit erstelltem Event
|
||||||
|
- **Validierung**: Prüft Credit-Balance (1 Credit pro Event)
|
||||||
|
|
||||||
|
### Event-Details
|
||||||
|
- **GET /api/v1/tenant/events/{id}**
|
||||||
|
- **Headers**: `Authorization: Bearer {token}`
|
||||||
|
- **Response**: Erweitertes Event mit `{ tasks[], members, stats { likes, views, uploads } }`
|
||||||
|
|
||||||
|
### Event updaten
|
||||||
|
- **PATCH /api/v1/tenant/events/{id}**
|
||||||
|
- **Headers**: `Authorization: Bearer {token}`, `Content-Type: application/json`, `If-Match: {etag}`
|
||||||
|
- **Body**: Partial Event-Daten (title, date, location, description)
|
||||||
|
- **Response**: Updated Event
|
||||||
|
|
||||||
|
### Event archivieren
|
||||||
|
- **DELETE /api/v1/tenant/events/{id}**
|
||||||
|
- **Headers**: `Authorization: Bearer {token}`, `If-Match: {etag}`
|
||||||
|
- **Response**: 204 No Content (soft-delete)
|
||||||
|
|
||||||
|
## Photos
|
||||||
|
|
||||||
|
### Photos laden
|
||||||
|
- **GET /api/v1/tenant/events/{event_id}/photos**
|
||||||
|
- **Headers**: `Authorization: Bearer {token}`
|
||||||
|
- **Params**:
|
||||||
|
- `page=1`, `per_page=50`
|
||||||
|
- `status=pending|approved|rejected|featured`
|
||||||
|
- `sort=date|likes`
|
||||||
|
- `since=cursor` (für Infinite Scroll)
|
||||||
|
- **Response**: `{ data: Photo[], current_page, last_page }`
|
||||||
|
- **Photo-Shape**: `{ id, eventId, url, thumbnail, uploadedAt, status, likes, views, uploader, etag }`
|
||||||
|
|
||||||
|
### Upload-URL anfordern
|
||||||
|
- **POST /api/v1/tenant/events/{event_id}/photos**
|
||||||
|
- **Headers**: `Authorization: Bearer {token}`, `Content-Type: application/json`
|
||||||
|
- **Body**: `{ file_name, description? }`
|
||||||
|
- **Response**: `{ id, upload_url (S3 signed), thumbnail_url }`
|
||||||
|
|
||||||
|
### Photo moderieren
|
||||||
|
- **PATCH /api/v1/tenant/photos/{id}**
|
||||||
|
- **Headers**: `Authorization: Bearer {token}`, `Content-Type: application/json`, `If-Match: {etag}`
|
||||||
|
- **Body**: `{ status: 'approved'|'rejected'|'featured', featured?, reason? }`
|
||||||
|
- **Response**: Updated Photo
|
||||||
|
|
||||||
|
### Photo löschen
|
||||||
|
- **DELETE /api/v1/tenant/photos/{id}**
|
||||||
|
- **Headers**: `Authorization: Bearer {token}`, `If-Match: {etag}`
|
||||||
|
- **Response**: 204 No Content
|
||||||
|
|
||||||
|
## Members
|
||||||
|
|
||||||
|
### Mitglieder laden
|
||||||
|
- **GET /api/v1/tenant/events/{event_id}/members**
|
||||||
|
- **Headers**: `Authorization: Bearer {token}`
|
||||||
|
- **Params**: `page`, `per_page`, `status=pending|active|invited`
|
||||||
|
- **Response**: `{ data: Member[], current_page, last_page }`
|
||||||
|
- **Member-Shape**: `{ id, name, email, role, joinedAt, avatar?, status }`
|
||||||
|
|
||||||
|
### Mitglied einladen
|
||||||
|
- **POST /api/v1/tenant/events/{event_id}/members**
|
||||||
|
- **Headers**: `Authorization: Bearer {token}`, `Content-Type: application/json`
|
||||||
|
- **Body**: `{ email, role: 'member'|'guest', name? }`
|
||||||
|
- **Response**: 201 Created, E-Mail wird versendet
|
||||||
|
|
||||||
|
## Tasks
|
||||||
|
|
||||||
|
### Tasks laden
|
||||||
|
- **GET /api/v1/tasks**
|
||||||
|
- **Headers**: `Authorization: Bearer {token}`
|
||||||
|
- **Params**:
|
||||||
|
- `global=true/false` (globale vs. tenant Tasks)
|
||||||
|
- `tenant_id=me` (nur eigene Tasks)
|
||||||
|
- `category=setup|photo|event|cleanup`
|
||||||
|
- **Response**: `{ data: Task[], global: boolean }`
|
||||||
|
- **Task-Shape**: `{ id, title, description?, category, isGlobal, tenantId?, createdAt, color? }`
|
||||||
|
|
||||||
|
### Event-Tasks laden
|
||||||
|
- **GET /api/v1/tenant/events/{event_id}/tasks**
|
||||||
|
- **Headers**: `Authorization: Bearer {token}`
|
||||||
|
- **Response**: `{ data: EventTask[], overall_progress }`
|
||||||
|
- **EventTask-Shape**: `{ id, eventId, taskId, task: Task, order, completed, assignedTo?, progress }`
|
||||||
|
|
||||||
|
### Tasks bulk zuweisen
|
||||||
|
- **POST /api/v1/tenant/events/{event_id}/tasks/bulk**
|
||||||
|
- **Headers**: `Authorization: Bearer {token}`, `Content-Type: application/json`
|
||||||
|
- **Body**: `{ task_ids: string[], order: number[] }`
|
||||||
|
- **Response**: Updated EventTasks mit neuer Reihenfolge
|
||||||
|
|
||||||
|
## Settings
|
||||||
|
|
||||||
|
### Settings laden
|
||||||
|
- **GET /api/v1/tenant/settings**
|
||||||
|
- **Headers**: `Authorization: Bearer {token}`
|
||||||
|
- **Response**: `{ primaryColor, tenantName, maxEventsPerMonth, enableTasks, enableEmotions, legalPages { impressumUrl, privacyUrl } }`
|
||||||
|
|
||||||
|
### Settings updaten
|
||||||
|
- **PATCH /api/v1/tenant/settings**
|
||||||
|
- **Headers**: `Authorization: Bearer {token}`, `Content-Type: application/json`
|
||||||
|
- **Body**: Partial Settings-Daten
|
||||||
|
- **Response**: Updated Settings
|
||||||
|
|
||||||
|
## Billing
|
||||||
|
|
||||||
|
### Balance laden
|
||||||
|
- **GET /api/v1/tenant/credits/balance**
|
||||||
|
- **Headers**: `Authorization: Bearer {token}`
|
||||||
|
- **Response**: `{ balance: number }`
|
||||||
|
|
||||||
|
### Ledger-Verlauf
|
||||||
|
- **GET /api/v1/tenant/ledger**
|
||||||
|
- **Headers**: `Authorization: Bearer {token}`
|
||||||
|
- **Params**: `page`, `per_page` (Pagination)
|
||||||
|
- **Response**: `{ data: LedgerEntry[], current_page, last_page }`
|
||||||
|
- **LedgerEntry**: `{ id, type, amount, credits, date, description, transactionId? }`
|
||||||
|
|
||||||
|
### Kauf-Intent erstellen
|
||||||
|
- **POST /api/v1/tenant/purchases/intent**
|
||||||
|
- **Headers**: `Authorization: Bearer {token}`, `Content-Type: application/json`
|
||||||
|
- **Body**: `{ package_id }`
|
||||||
|
- **Response**: `{ checkout_url: string }` (Stripe-Checkout)
|
||||||
|
- **Nach dem Kauf**: Webhook-Handling auf Backend für Balance-Update
|
||||||
|
|
||||||
|
## Allgemeine Headers
|
||||||
|
|
||||||
|
Alle API-Requests enthalten:
|
||||||
|
- **Authorization**: `Bearer {access_token}` (JWT mit tenant_id)
|
||||||
|
- **Content-Type**: `application/json` (für POST/PATCH)
|
||||||
|
- **If-Match**: `{etag}` (für Concurrency-Control bei Updates)
|
||||||
|
- **Accept**: `application/json`
|
||||||
|
|
||||||
|
## Error-Handling
|
||||||
|
|
||||||
|
**Standard-Error-Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"error": {
|
||||||
|
"code": "INSUFFICIENT_CREDITS",
|
||||||
|
"message": "Nicht genügend Credits verfügbar"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**HTTP Status Codes:**
|
||||||
|
- 200: Erfolg
|
||||||
|
- 201: Created
|
||||||
|
- 400: Bad Request (Validierungsfehler)
|
||||||
|
- 401: Unauthorized (Token-Refresh wird versucht)
|
||||||
|
- 403: Forbidden (RBAC-Verletzung)
|
||||||
|
- 404: Not Found
|
||||||
|
- 422: Unprocessable Entity
|
||||||
|
- 429: Rate Limited (Client retry mit Backoff)
|
||||||
|
|
||||||
|
## Pagination
|
||||||
|
|
||||||
|
Alle Listen-Endpunkte unterstützen:
|
||||||
|
- **page**: Aktuelle Seite (default 1)
|
||||||
|
- **per_page**: Einträge pro Seite (default 20, max 50 für Mobile)
|
||||||
|
- **Response**: `{ data: [], current_page, last_page, per_page, total }`
|
||||||
|
|
||||||
|
## Headers für Concurrency
|
||||||
|
|
||||||
|
Mutierende Endpunkte (PATCH/DELETE) erfordern:
|
||||||
|
- **If-Match**: `{etag}` aus GET-Response
|
||||||
|
- **Response**: 412 Precondition Failed bei Conflict
|
||||||
|
|
||||||
|
## Sicherheit
|
||||||
|
|
||||||
|
- **Tenant-Isolation**: Alle Endpunkte prüfen JWT-tenant_id gegen request tenant_id
|
||||||
|
- **RBAC**: Nur tenant_admin kann mutieren, member kann nur lesen/hochladen
|
||||||
|
- **Rate Limiting**: 100 Requests/Minute pro Tenant
|
||||||
|
- **ETag**: Automatische Concurrency-Control
|
||||||
|
- **No PII-Logging**: Keine sensiblen Daten in Logs
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
### API-Test-Setup
|
||||||
|
1. **Backend starten**: Stelle sicher, dass die Hauptapp läuft und Endpunkte verfügbar sind.
|
||||||
|
2. **Token erzeugen**: Verwende Postman mit gültigem Access-Token.
|
||||||
|
3. **Endpoints testen**: Jeder Endpunkt einzeln mit curl oder Postman.
|
||||||
|
|
||||||
|
### Beispiel curl (mit Token)
|
||||||
|
```bash
|
||||||
|
curl -H "Authorization: Bearer {token}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
https://api.fotospiel.com/api/v1/tenant/events
|
||||||
|
```
|
||||||
|
|
||||||
|
### Frontend-Test
|
||||||
|
1. `.env` mit korrekter API-URL konfigurieren.
|
||||||
|
2. `npm run dev` starten.
|
||||||
|
3. Browser-Network-Tab überprüfen für API-Calls.
|
||||||
|
|
||||||
|
## Deployment
|
||||||
|
|
||||||
|
### Environment-Variablen
|
||||||
|
- **REACT_APP_API_URL**: Backend-API-URL (Pflicht)
|
||||||
|
- **REACT_APP_OAUTH_CLIENT_ID**: OAuth-Client-ID (Pflicht)
|
||||||
|
|
||||||
|
### Build & Deploy
|
||||||
|
1. **Development**: `npm run dev`
|
||||||
|
2. **Production**: `npm run build`
|
||||||
|
3. **Vorschau**: `npm run preview`
|
||||||
|
|
||||||
|
### PWA-Installation
|
||||||
|
- App ist PWA-fähig (Manifest, Service Worker).
|
||||||
|
- Installierbar auf Desktop/Mobile via "Zum Startbildschirm hinzufügen".
|
||||||
|
|
||||||
|
Für weitere Details siehe die spezifischen Dokumentationsdateien.
|
||||||
155
docs/prp/tenant-app-specs/capacitor-setup.md
Normal file
155
docs/prp/tenant-app-specs/capacitor-setup.md
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
# Capacitor Setup für die Tenant Admin App
|
||||||
|
|
||||||
|
## Status
|
||||||
|
- **Version**: 1.0.0 (2025-09-13)
|
||||||
|
- **Fokus**: Mobile Packaging, Distribution und Native-Integration basierend auf ADR-0006.
|
||||||
|
|
||||||
|
## Überblick
|
||||||
|
Die Tenant Admin App wird als Progressive Web App (PWA) entwickelt und mit Capacitor für native Mobile-Distribution gepackt. Dies ermöglicht:
|
||||||
|
- **Android**: Trusted Web Activity (TWA) für Chrome-basierte Installation (Google Play) oder voller Capacitor-Build bei Bedarf an Native APIs.
|
||||||
|
- **iOS**: Capacitor-App für App Store-Distribution mit Push-Notifications und Keychain-Support.
|
||||||
|
- **Web/PWA**: Fallback für Browser-Installation (A2HS) mit Service Worker.
|
||||||
|
|
||||||
|
Der Build-Prozess integriert sich in das bestehende Vite-Setup (resources/js/admin). Nach `npm run build` wird `npx cap sync` ausgeführt, um die Web-Assets in native Projekte zu kopieren.
|
||||||
|
|
||||||
|
## Projektstruktur
|
||||||
|
```
|
||||||
|
apps/admin-pwa/ # React/Vite Source (Haupt-App)
|
||||||
|
├── src/ # Components, Pages, API
|
||||||
|
├── vite.config.ts # Framework7 + Capacitor-Integration
|
||||||
|
├── capacitor.config.ts # Native Config
|
||||||
|
├── android/ # Capacitor Android-Projekt (TWA oder App)
|
||||||
|
├── ios/ # Capacitor iOS-Projekt (Xcode)
|
||||||
|
└── package.json # Dependencies (framework7, @capacitor/*)
|
||||||
|
|
||||||
|
packages/mobile/ # Shared Native-Config (optional)
|
||||||
|
├── fastlane/ # iOS/Android Deployment
|
||||||
|
├── assetlinks.json # TWA Digital Asset Links
|
||||||
|
└── privacy-manifest.json # iOS Privacy
|
||||||
|
```
|
||||||
|
|
||||||
|
## Packaging und Build-Prozess
|
||||||
|
|
||||||
|
### 1. Vite + Framework7 Setup
|
||||||
|
- **Dependencies**: `framework7`, `framework7-react`, `@capacitor/core`, `@capacitor/cli`.
|
||||||
|
- **vite.config.ts** (Auszug):
|
||||||
|
```typescript
|
||||||
|
import { defineConfig } from 'vite';
|
||||||
|
import react from '@vitejs/plugin-react';
|
||||||
|
import { framework7 } from 'framework7/vite';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react(), framework7()],
|
||||||
|
build: {
|
||||||
|
outDir: 'dist',
|
||||||
|
sourcemap: true,
|
||||||
|
},
|
||||||
|
define: {
|
||||||
|
__CAPACITOR__: true, // Enable Capacitor globals
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
- **Build-Skript** (package.json): `"build": "vite build && npx cap sync"`.
|
||||||
|
|
||||||
|
### 2. Capacitor Initialisierung
|
||||||
|
- **Befehle**:
|
||||||
|
```
|
||||||
|
npx cap init com.fotospiel.tenantadmin "Event Photo Admin"
|
||||||
|
npx cap add android
|
||||||
|
npx cap add ios
|
||||||
|
npx cap sync # Kopiert dist/ in native Projekte
|
||||||
|
```
|
||||||
|
- **capacitor.config.ts** (siehe settings-config.md für erweiterte Plugins).
|
||||||
|
|
||||||
|
### 3. Android Packaging (TWA bevorzugt)
|
||||||
|
- **Trusted Web Activity (TWA)**: Für store-ready Distribution ohne vollen Native-Wrapper.
|
||||||
|
- **Voraussetzungen**: App bound an `admin.fotospiel.app` (HTTPS); Digital Asset Links.
|
||||||
|
- **assetlinks.json** (public/.well-known/assetlinks.json):
|
||||||
|
```json
|
||||||
|
[{
|
||||||
|
"relation": ["delegate_permission/common.handle_all_urls"],
|
||||||
|
"target": {
|
||||||
|
"namespace": "android_app",
|
||||||
|
"package_name": "com.fotospiel.tenantadmin",
|
||||||
|
"sha256_cert_fingerprints": ["DE:AD:BE:EF:..."] // Von Play Console
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
```
|
||||||
|
- **Build**: `npx cap open android` → Android Studio → Generate Signed Bundle (AAB für Play Store).
|
||||||
|
- **Vorteile**: Kleiner Footprint, Web-Push via Chrome; Fallback zu Capacitor bei Bedarf (z.B. Biometrie).
|
||||||
|
- **TWA-Tools**: `npm i -g @bubblewrap/cli` → `bubblewrap init --manifest=https://admin.fotospiel.app/manifest.json`.
|
||||||
|
|
||||||
|
- **Vollständiger Capacitor-Build** (falls Native-Plugins benötigt):
|
||||||
|
- `npx cap sync android`
|
||||||
|
- `npx cap open android` → Build APK/AAB mit ProGuard.
|
||||||
|
|
||||||
|
### 4. iOS Packaging (Capacitor)
|
||||||
|
- **Xcode-Setup**: `npx cap open ios` → Signing mit Apple Developer Account.
|
||||||
|
- **Privacy Manifest** (ios/App/App/PrivacyInfo.xcprivacy):
|
||||||
|
```xml
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>NSPrivacyAccessedAPITypes</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>NSPrivacyAccessedAPIType</key>
|
||||||
|
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
|
||||||
|
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||||
|
<array>
|
||||||
|
<string>CA92.1</string> <!-- Preferences für Settings -->
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
<!-- Push, Camera, etc. -->
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
|
```
|
||||||
|
- **Build**: Archive in Xcode → Upload zu App Store Connect.
|
||||||
|
- **Entitlements**: Push-Notifications (APNs), Background App Refresh für Sync.
|
||||||
|
|
||||||
|
### 5. Distribution und CI/CD
|
||||||
|
- **Google Play**:
|
||||||
|
- **TWA**: Internal Testing Track; Target SDK 34+; Feature: Installable.
|
||||||
|
- **Fastlane**: `packages/mobile/fastlane/android` mit `supply` für Metadata/Screenshots.
|
||||||
|
- **Versioning**: Align mit Backend (z.B. 1.0.0); Feature Flags via API.
|
||||||
|
|
||||||
|
- **Apple App Store**:
|
||||||
|
- **Capacitor**: Review-Guidelines beachten (HTTPS-only, No Crash-Reporting ohne Consent).
|
||||||
|
- **Fastlane**: `packages/mobile/fastlane/ios` mit `deliver` für Upload.
|
||||||
|
- **Privacy**: Usage Descriptions in Info.plist (z.B. "Kamera für QR-Scans").
|
||||||
|
|
||||||
|
- **PWA-Fallback** (Web):
|
||||||
|
- **manifest.json**: `start_url: '/admin/'`, `display: 'standalone'`.
|
||||||
|
- **Service Worker**: Caching von Assets; Background Sync für Mutations.
|
||||||
|
- **Distribution**: Hosting auf `admin.fotospiel.app` mit A2HS-Prompt.
|
||||||
|
|
||||||
|
### Native Features (Erweiterung zu settings-config.md)
|
||||||
|
- **Push-Notifications**:
|
||||||
|
- **Android**: FCM-Integration; Token an Backend (`POST /tenant/push-tokens`).
|
||||||
|
- **iOS**: APNs; Silent-Pushes für Sync-Triggers.
|
||||||
|
- **Cross-Platform**: Capacitor Push-Plugin handhabt Plattform-Unterschiede.
|
||||||
|
|
||||||
|
- **Secure Storage**:
|
||||||
|
- **Tokens**: Capacitor Preferences mit Auto-Encryption.
|
||||||
|
- **Offline Data**: IndexedDB (Web) + Native Storage; Encryption via Web Crypto API.
|
||||||
|
|
||||||
|
- **Background Processing**:
|
||||||
|
- **Sync**: Capacitor App-State-Listener für Foreground/Background; Queue Mutations.
|
||||||
|
- **iOS**: Background Fetch (via Plugin); Android: WorkManager für periodische Syncs.
|
||||||
|
|
||||||
|
- **Biometrie (optional)**: `@capacitor-community/biometric-auth` für App-Lock.
|
||||||
|
|
||||||
|
### Testing und Deployment
|
||||||
|
- **E2E-Tests**: Cypress für Web; Detox/Appium für Native (z.B. Push-Handling).
|
||||||
|
- **CI/CD**: GitHub Actions oder Gogs-Integration (siehe docs/prp/11-ops-ci-cd.md).
|
||||||
|
- Steps: Build → Test → Cap Sync → Fastlane Deploy (Staging → Production).
|
||||||
|
- **Version Alignment**: App-Version matcht Backend-API-Version; Changelog in Store-Listing.
|
||||||
|
|
||||||
|
### Constraints & Red-Lines
|
||||||
|
- **GDPR**: Keine implizite Tracking; Explizite Consent für Push/Camera.
|
||||||
|
- **Security**: HTTPS-only; Token-Rotation alle 24h; No Jailbreak-Detection.
|
||||||
|
- **Performance**: Bundle-Size < 10MB (Web-Assets komprimiert); Lazy-Loading.
|
||||||
|
|
||||||
|
Diese Setup ergänzt die funktionalen Specs und UI-Beschreibungen. Für Repo-Integration siehe ADR-0006.
|
||||||
100
docs/prp/tenant-app-specs/functional-specs.md
Normal file
100
docs/prp/tenant-app-specs/functional-specs.md
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
# Funktionale Spezifikationen für die Tenant Admin App
|
||||||
|
|
||||||
|
## Status
|
||||||
|
- **Version**: 1.0.0 (2025-09-13)
|
||||||
|
- **Supersedes**: Teile von docs/prp/06-tenant-admin-pwa.md und docs/prp-addendum-2025-09-08-tenant-admin-pwa.md
|
||||||
|
|
||||||
|
## Deliverables
|
||||||
|
Die Tenant Admin App muss folgende Kernfunktionen bereitstellen:
|
||||||
|
- **Event-Management**: CRUD-Operationen für Events (Erstellen, Bearbeiten, Archivieren, Veröffentlichen).
|
||||||
|
- **Gallery-Management**: Hochladen, Moderieren, Featured-Setten von Photos; Thumbnail-Generierung.
|
||||||
|
- **Member-Management**: Hinzufügen/Entfernen von Event-Mitgliedern; Rollen (Admin, Member).
|
||||||
|
- **Task & Emotion Management**: Zuweisen von Tasks und Emotions zu Events; Overrides für Tenant-spezifische Bibliotheken.
|
||||||
|
- **Settings-Management**: Tenant-spezifische Einstellungen (Theme, Limits, Legal Pages).
|
||||||
|
- **Billing & Purchases**: Kaufen von Event-Credits; Ledger-Übersicht; Integration mit Stripe.
|
||||||
|
- **Notifications**: Push-Benachrichtigungen für neue Photos, Event-Updates, niedrigen Credit-Balance.
|
||||||
|
- **Offline-Support**: Caching von Events und Photos; Queuing von Uploads/Mutations mit Sync bei Online-Wiederkehr.
|
||||||
|
- **Audit & Compliance**: Logging kritischer Aktionen; ETag-basierte Conflict-Resolution; GDPR-konforme Datenlöschung.
|
||||||
|
|
||||||
|
Die App ist API-first und interagiert ausschließlich über den Backend-API-Endpunkt `/api/v1/tenant/*`.
|
||||||
|
|
||||||
|
## Capabilities
|
||||||
|
### Authentifizierung & Autorisierung
|
||||||
|
- **OAuth2 Flow**: Authorization Code + PKCE für sichere Token-Erfassung.
|
||||||
|
- **Token-Management**: Refresh-Tokens mit automatischer Rotation; Secure Storage (Keychain/Keystore).
|
||||||
|
- **Tenant-Scoping**: Alle Requests enthalten `tenant_id` aus Token; Backend-Policies enforcen Isolation.
|
||||||
|
- **Roles**: Unterstützung für `tenant_admin` (volle CRUD), `member` (read-only + Uploads).
|
||||||
|
|
||||||
|
### Core Features
|
||||||
|
- **Event Lifecycle**:
|
||||||
|
- Erstellen: Erfordert mind. 1 Event-Credit; Slug-Generierung (unique pro Tenant).
|
||||||
|
- Bearbeiten: Update von Datum, Ort, Tasks, Emotions, Join-Link.
|
||||||
|
- Veröffentlichen: Generiert QR-Code und Share-Link; aktiviert Guest-PWA-Zugriff.
|
||||||
|
- Archivieren: Soft-Delete mit Retention-Periode (GDPR); Credit-Rückerstattung optional.
|
||||||
|
- **Photo Management**:
|
||||||
|
- Upload: Signed URLs für direkte S3-Uploads; automatisierte Thumbnail-Generierung.
|
||||||
|
- Moderation: Approve/Reject/Feature; Bulk-Operations.
|
||||||
|
- Analytics: Stats zu Likes, Views, Uploads pro Event.
|
||||||
|
- **Task & Emotion System**:
|
||||||
|
- Bibliothek: Globale + Tenant-spezifische Tasks/Emotions.
|
||||||
|
- Zuweisung: Drag-and-Drop zu Events; Fortschritts-Tracking.
|
||||||
|
- **Billing Integration**:
|
||||||
|
- Credit-Balance: Anzeige und Kauf von Packs (z.B. 5 Events für 29€).
|
||||||
|
- Ledger: Historie von Käufen, Consumptions, Refunds.
|
||||||
|
- Stripe-Checkout: Server-side Intent-Erstellung; Webhook-Handling für Confirmation.
|
||||||
|
|
||||||
|
### Offline & Sync
|
||||||
|
- **Service Worker**: Cache von App-Shell, Events, Photos (Cache-Control: max-age=5min für dynamische Daten).
|
||||||
|
- **Background Sync**: Queued Mutations (z.B. Photo-Approvals) syncen bei Connectivity.
|
||||||
|
- **Conflict Resolution**: ETag/If-Match Headers; Optimistic Updates mit Rollback bei Conflicts.
|
||||||
|
|
||||||
|
### Error Handling & UX
|
||||||
|
- **Rate Limits**: 429-Responses handhaben mit Retry-Logic und User-Feedback ("Zu viele Anfragen, versuche es später").
|
||||||
|
- **Offline Mode**: Degradiertes UI (Read-Only); Sync-Status-Indikator.
|
||||||
|
- **i18n**: Unterstützung für de/en; Locale aus User-Profile.
|
||||||
|
|
||||||
|
## API-Integration
|
||||||
|
Die App konsumiert den API-Contract aus docs/prp/03-api.md. Schlüssel-Endpunkte:
|
||||||
|
|
||||||
|
### Auth
|
||||||
|
- `POST /oauth/token`: PKCE-Code für Access/Refresh-Token.
|
||||||
|
- `POST /oauth/token/refresh`: Token-Rotation.
|
||||||
|
|
||||||
|
### Events
|
||||||
|
- `GET /tenant/events`: Liste (paginiert, filterbar nach Status/Datum).
|
||||||
|
- `POST /tenant/events`: Erstellen (validiert Credit-Balance).
|
||||||
|
- `GET /tenant/events/{id}`: Details inkl. Tasks, Stats.
|
||||||
|
- `PATCH /tenant/events/{id}`: Update (ETag für Concurrency).
|
||||||
|
- `DELETE /tenant/events/{id}`: Archivieren.
|
||||||
|
|
||||||
|
### Photos
|
||||||
|
- `GET /tenant/events/{event_id}/photos?since={cursor}`: Inkrementelle Liste.
|
||||||
|
- `POST /tenant/events/{event_id}/photos`: Metadata + signed Upload-URL.
|
||||||
|
- `PATCH /tenant/photos/{id}`: Moderation (approve, feature).
|
||||||
|
- `DELETE /tenant/photos/{id}`: Löschung mit Audit.
|
||||||
|
|
||||||
|
### Tasks & Emotions
|
||||||
|
- `GET /tenant/tasks`: Tenant-Overrides + globale Bibliothek.
|
||||||
|
- `POST /tenant/events/{id}/tasks`: Zuweisung.
|
||||||
|
- Ähnlich für Emotions.
|
||||||
|
|
||||||
|
### Settings
|
||||||
|
- `GET /tenant/settings`: Tenant-Konfig (Theme, Limits, Legal-Links).
|
||||||
|
- `PATCH /tenant/settings`: Update.
|
||||||
|
|
||||||
|
### Billing
|
||||||
|
- `GET /tenant/ledger`: Credit-Historie.
|
||||||
|
- `POST /tenant/purchases/intent`: Stripe-Checkout-Session erstellen.
|
||||||
|
- `GET /tenant/credits/balance`: Aktueller Stand.
|
||||||
|
|
||||||
|
### Pagination & Errors
|
||||||
|
- Standard: `page`, `per_page` (max 50 für Mobile).
|
||||||
|
- Errors: Parsen von `{ error: { code, message } }`; User-freundliche Messages (z.B. "Nicht genug Credits").
|
||||||
|
|
||||||
|
## Non-Functional Requirements
|
||||||
|
- **Performance**: Ladezeiten < 2s; Lazy-Loading für Galleries.
|
||||||
|
- **Sicherheit**: Kein PII-Logging; Token-Expiration-Handling; CSRF-Schutz via PKCE.
|
||||||
|
- **Accessibility**: Framework7 ARIA-Support; Dark Mode (System-Preference).
|
||||||
|
- **Testing**: Unit-Tests für API-Calls; E2E-Tests für Flows (Cypress).
|
||||||
|
|
||||||
|
Für detaillierte UI-Seiten siehe pages-ui.md; für Settings siehe settings-config.md.
|
||||||
186
docs/prp/tenant-app-specs/pages-ui.md
Normal file
186
docs/prp/tenant-app-specs/pages-ui.md
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
# Seiten und UI-Design für die Tenant Admin App
|
||||||
|
|
||||||
|
## Status
|
||||||
|
- **Version**: 1.0.0 (2025-09-13)
|
||||||
|
- **Fokus**: Mobile-First Design mit Framework7 v8+ für native iOS/Android-Look & Feel.
|
||||||
|
|
||||||
|
## Allgemeines UI-Prinzipien
|
||||||
|
- **Framework7-Komponenten**: Toolbar (Navigation), List (Datenlisten), Card (Karten für Events/Photos), Modal (Details/Actions), Pull-to-Refresh (Sync), Infinite-Scroll (Pagination).
|
||||||
|
- **Theming**: System-Dark-Mode-Support; Tenant-spezifische Farben (Primary/Secondary aus Settings).
|
||||||
|
- **Navigation**: Tabbar unten (Dashboard, Events, Photos, Settings); Side-Menu für Profile/Logout.
|
||||||
|
- **Offline-Indikator**: Banner oben ("Offline-Modus: Änderungen werden synchronisiert").
|
||||||
|
- **Loading**: Spinner für API-Calls; Skeleton-Screens für Listen.
|
||||||
|
- **i18n**: Rechts-nach-Links für de/en; Icons von Framework7-Icons (Material).
|
||||||
|
|
||||||
|
## Benötigte Seiten und Komponenten
|
||||||
|
|
||||||
|
### 1. Auth-Seiten (OAuth-Flow)
|
||||||
|
#### Login-Seite
|
||||||
|
- **Zweck**: OAuth-Authorization (PKCE-Challenge generieren, Redirect zu /oauth/authorize).
|
||||||
|
- **Layout**:
|
||||||
|
- Zentrale Card mit Logo, App-Name, "Mit Google/Email anmelden"-Buttons.
|
||||||
|
- Footer: "Noch kein Account? Registrieren" (Redirect zu Register).
|
||||||
|
- **Framework7-Komponenten**:
|
||||||
|
- `f7-page` mit `f7-navbar` (Titel: "Willkommen zur Event Photo App").
|
||||||
|
- `f7-block` für Content; `f7-button` (large, filled) für OAuth-Start.
|
||||||
|
- `f7-preloader` während Redirect.
|
||||||
|
- **Wireframe-Beschreibung**:
|
||||||
|
```
|
||||||
|
[Navbar: Logo | Willkommen]
|
||||||
|
[Block: App-Beschreibung]
|
||||||
|
[Button: Anmelden mit Google]
|
||||||
|
[Button: Anmelden mit Email]
|
||||||
|
[Footer: Registrieren-Link]
|
||||||
|
```
|
||||||
|
- **API**: Kein direkter Call; Browser-Redirect zu Backend.
|
||||||
|
|
||||||
|
#### Register-Seite (ähnlich Login)
|
||||||
|
- **Unterschiede**: Form für Email/Password + Terms-Checkbox; Submit zu `/oauth/register`.
|
||||||
|
|
||||||
|
### 2. Dashboard (Home)
|
||||||
|
- **Zweck**: Übersicht über aktive Events, Stats, schnelle Actions.
|
||||||
|
- **Layout**:
|
||||||
|
- Top: Willkommens-Banner mit Tenant-Name, Credit-Balance.
|
||||||
|
- Stats-Cards: Aktive Events, Ungeprüfte Photos, Tasks-Fortschritt.
|
||||||
|
- Quick-Actions: "Neues Event erstellen", "Photos moderieren".
|
||||||
|
- **Framework7-Komponenten**:
|
||||||
|
- `f7-toolbar` unten: Tabs (Home, Events, Photos, Settings).
|
||||||
|
- `f7-card` für Stats (mit `f7-icon` und Zahlen).
|
||||||
|
- `f7-list` für Quick-Actions (link mit Arrow).
|
||||||
|
- `f7-pull-to-refresh` für Sync.
|
||||||
|
- **Wireframe-Beschreibung**:
|
||||||
|
```
|
||||||
|
[Toolbar: Home | Events | Photos | Settings]
|
||||||
|
[Banner: Hallo [Name]! 3 Credits übrig]
|
||||||
|
[Row: Card(Events: 2 aktiv) | Card(Photos: 15 neu) | Card(Tasks: 80%)]
|
||||||
|
[List: > Neues Event | > Moderieren | > Einstellungen]
|
||||||
|
```
|
||||||
|
- **API**: `GET /tenant/dashboard` (Stats); `GET /tenant/credits/balance`.
|
||||||
|
|
||||||
|
### 3. Events-Übersicht
|
||||||
|
- **Zweck**: Liste aller Events mit Filter (aktiv/archiviert), Suche.
|
||||||
|
- **Layout**:
|
||||||
|
- Navbar: Suche-Feld, Filter-Button (Dropdown: Status, Datum).
|
||||||
|
- Infinite-Scroll-Liste von Event-Cards (Titel, Datum, Status-Tag, Photo-Count).
|
||||||
|
- FAB: "+" für Neues Event.
|
||||||
|
- **Framework7-Komponenten**:
|
||||||
|
- `f7-searchbar` in Navbar.
|
||||||
|
- `f7-list` mit `f7-list-item` (Thumbnail, Title, Subtitle: Datum, Badge: Status).
|
||||||
|
- `f7-fab` (floating action button).
|
||||||
|
- `f7-segmented` für Filter-Tabs.
|
||||||
|
- **Wireframe-Beschreibung**:
|
||||||
|
```
|
||||||
|
[Navbar: Suche | Filter ▼]
|
||||||
|
[Segmented: Alle | Aktiv | Archiviert]
|
||||||
|
[List-Item: [Thumb] Hochzeit Müller (15.09.) [Tag: Aktiv] 45 Photos]
|
||||||
|
[List-Item: ...]
|
||||||
|
[+ FAB unten rechts]
|
||||||
|
```
|
||||||
|
- **API**: `GET /tenant/events?page=1&status=active` (paginiert).
|
||||||
|
|
||||||
|
### 4. Event-Details-Seite
|
||||||
|
- **Zweck**: Vollständige Event-Info, Edit-Modus, zugehörige Tasks/Photos.
|
||||||
|
- **Layout**:
|
||||||
|
- Tabs: Details, Tasks, Photos, Members, Stats.
|
||||||
|
- Edit-Button (öffnet Modal für Update).
|
||||||
|
- QR-Code-Section für Join-Link.
|
||||||
|
- **Framework7-Komponenten**:
|
||||||
|
- `f7-tabs` mit `f7-tab` für Subseiten.
|
||||||
|
- `f7-card` für Details (Datum, Ort, Beschreibung).
|
||||||
|
- `f7-qrcode` (via Plugin) für Share-Link.
|
||||||
|
- `f7-button` (edit-icon) → `f7-modal` mit Form.
|
||||||
|
- **Wireframe-Beschreibung**:
|
||||||
|
```
|
||||||
|
[Navbar: Event-Titel | Edit-Icon]
|
||||||
|
[Tabs: Details | Tasks | Photos | Members | Stats]
|
||||||
|
[Details-Tab: Card(Datum: 15.09., Ort: Berlin) | QR-Code: Scan zum Beitreten]
|
||||||
|
[Tasks-Tab: Checklist (Drag-to-reorder)]
|
||||||
|
[Photos-Tab: Grid von Thumbs]
|
||||||
|
```
|
||||||
|
- **API**: `GET /tenant/events/{id}`; `PATCH /tenant/events/{id}` (ETag).
|
||||||
|
|
||||||
|
### 5. Photo-Gallery-Seite (pro Event)
|
||||||
|
- **Zweck**: Moderation von Photos; Grid-View mit Lightbox.
|
||||||
|
- **Layout**:
|
||||||
|
- Filter: Neu/Ungeprüft/Featured; Sortierung (Datum, Likes).
|
||||||
|
- Masonry-Grid von Photo-Cards (Thumb, Timestamp, Like-Count).
|
||||||
|
- Long-Press: Multi-Select für Bulk-Actions (Approve/Delete).
|
||||||
|
- **Framework7-Komponenten**:
|
||||||
|
- `f7-photoset` oder custom Grid mit `f7-card` (small).
|
||||||
|
- `f7-lightbox` für Fullscreen-View.
|
||||||
|
- `f7-checkbox` für Multi-Select.
|
||||||
|
- `f7-popover` für Actions (Approve, Feature, Delete).
|
||||||
|
- **Wireframe-Beschreibung**:
|
||||||
|
```
|
||||||
|
[Navbar: Photos (Event) | Filter ▼]
|
||||||
|
[Grid: 3xN Cards [Thumb | Zeit | Likes] [Checkbox]]
|
||||||
|
[Lightbox: Fullscreen Photo mit Zoom, Actions]
|
||||||
|
[Bottom-Bar (bei Select): Approve All | Delete Selected]
|
||||||
|
```
|
||||||
|
- **API**: `GET /tenant/events/{id}/photos`; `PATCH /tenant/photos/{id}` (Batch).
|
||||||
|
|
||||||
|
### 6. Members-Seite
|
||||||
|
- **Zweck**: Verwalten von Event-Mitgliedern (Hinzufügen per Email, Rollen).
|
||||||
|
- **Layout**: Liste von User-Cards (Name, Rolle, Joined-At); Invite-Button.
|
||||||
|
- **Framework7-Komponenten**:
|
||||||
|
- `f7-list` mit `f7-list-item` (Avatar, Name, Badge: Rolle).
|
||||||
|
- `f7-modal` für Invite-Form (Email-Input, Send-Button).
|
||||||
|
- **Wireframe-Beschreibung**:
|
||||||
|
```
|
||||||
|
[Navbar: Members | + Invite]
|
||||||
|
[List: Anna (Admin) | Ben (Member) | ...]
|
||||||
|
[Modal: Email eingeben | Rolle wählen | Senden]
|
||||||
|
```
|
||||||
|
- **API**: `GET /tenant/events/{id}/members`; `POST /tenant/events/{id}/members`.
|
||||||
|
|
||||||
|
### 7. Tasks-Seite
|
||||||
|
- **Zweck**: Bibliothek verwalten, zu Events zuweisen, Fortschritt tracken.
|
||||||
|
- **Layout**: Tabs: Meine Tasks, Bibliothek; Drag-and-Drop zwischen Listen.
|
||||||
|
- **Framework7-Komponenten**:
|
||||||
|
- `f7-tabs`; `f7-sortable-list` für Drag-and-Drop.
|
||||||
|
- `f7-checkbox` für Zuweisung.
|
||||||
|
- **Wireframe-Beschreibung**:
|
||||||
|
```
|
||||||
|
[Tabs: Events | Bibliothek]
|
||||||
|
[Sortable List: Task1 [Checkbox] | Task2 ...]
|
||||||
|
[Drag: Von Bibliothek zu Event-Liste]
|
||||||
|
```
|
||||||
|
- **API**: `GET /tenant/tasks`; `POST /tenant/events/{id}/tasks`.
|
||||||
|
|
||||||
|
### 8. Settings-Seite
|
||||||
|
- **Zweck**: Tenant-Einstellungen bearbeiten (Theme, Limits, Legal).
|
||||||
|
- **Layout**: Accordion-Sections (Theme, Notifications, Legal, App).
|
||||||
|
- **Framework7-Komponenten**:
|
||||||
|
- `f7-accordion` für Sections.
|
||||||
|
- `f7-toggle`, `f7-select`, `f7-color-picker` für Optionen.
|
||||||
|
- **Wireframe-Beschreibung**:
|
||||||
|
```
|
||||||
|
[Navbar: Einstellungen]
|
||||||
|
[Accordion: Theme ▼ [Color-Picker] | Notifications [Toggle Push]]
|
||||||
|
[Legal: Impressum-Link bearbeiten]
|
||||||
|
[App: Logout-Button]
|
||||||
|
```
|
||||||
|
- **API**: `GET/PATCH /tenant/settings`. Details in settings-config.md.
|
||||||
|
|
||||||
|
### 9. Billing-Seite
|
||||||
|
- **Zweck**: Credit-Balance anzeigen, Packs kaufen.
|
||||||
|
- **Layout**: Balance-Card; Liste von Purchase-Options; Ledger-Historie.
|
||||||
|
- **Framework7-Komponenten**:
|
||||||
|
- `f7-card` für Balance (mit Warning bei niedrig).
|
||||||
|
- `f7-list` für Packs (Preis, Events-Count, Buy-Button).
|
||||||
|
- `f7-infinite-scroll` für Ledger.
|
||||||
|
- **Wireframe-Beschreibung**:
|
||||||
|
```
|
||||||
|
[Card: Balance: 3 Credits | [Warning: Niedrig!]]
|
||||||
|
[List: 5 Events (29€) [Buy] | 10 Events (49€) [Buy]]
|
||||||
|
[Ledger: Kauf 29.09. +5 | Event-Erstellung -1]
|
||||||
|
```
|
||||||
|
- **API**: `GET /tenant/credits/balance`; `POST /tenant/purchases/intent`.
|
||||||
|
|
||||||
|
## Zusätzliche UI-Elemente
|
||||||
|
- **Modals**: Confirm-Delete, Photo-Preview, Error-Alerts.
|
||||||
|
- **Notifications**: `f7-notification` für Sync-Erfolg, neue Photos.
|
||||||
|
- **Offline-Handling**: `f7-block` mit "Syncing..." Progress-Bar.
|
||||||
|
- **Accessibility**: ARIA-Labels für alle interaktiven Elemente; VoiceOver-Support.
|
||||||
|
|
||||||
|
Für Capacitor-spezifische UI-Anpassungen siehe capacitor-setup.md.
|
||||||
124
docs/prp/tenant-app-specs/settings-config.md
Normal file
124
docs/prp/tenant-app-specs/settings-config.md
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
# Settings und Konfiguration für die Tenant Admin App
|
||||||
|
|
||||||
|
## Status
|
||||||
|
- **Version**: 1.0.0 (2025-09-13)
|
||||||
|
- **Fokus**: App-interne und tenant-spezifische Einstellungen; Integration mit Capacitor-Plugins.
|
||||||
|
|
||||||
|
## App-interne Settings
|
||||||
|
Diese Settings werden lokal in der App gespeichert (via Capacitor Preferences oder IndexedDB) und beeinflussen das Verhalten der App:
|
||||||
|
|
||||||
|
### Core App Settings
|
||||||
|
- **language**: String (default: 'de') – UI-Sprache; Sync mit User-Locale.
|
||||||
|
- **themeMode**: String ('system' | 'light' | 'dark') – Dark Mode-Präferenz; Framework7-Theming.
|
||||||
|
- **offlineMode**: Boolean (default: true) – Aktiviert Offline-Caching und Background-Sync.
|
||||||
|
- **pushNotifications**: Boolean (default: true) – Erlaubt Push-Registrierung.
|
||||||
|
- **autoSyncInterval**: Number (default: 300) – Sekunden bis automatischer Sync (min: 60).
|
||||||
|
- **maxPhotoCache**: Number (default: 100) – Anzahl gecachter Photos pro Event.
|
||||||
|
|
||||||
|
### User-spezifische Settings
|
||||||
|
- **notificationPreferences**: Object
|
||||||
|
- `newPhotos`: Boolean – Benachrichtigung bei neuen Uploads.
|
||||||
|
- `eventUpdates`: Boolean – Bei Event-Änderungen (z.B. Veröffentlicht).
|
||||||
|
- `lowCredits`: Boolean – Warnung bei < 2 Credits.
|
||||||
|
- **privacySettings**: Object
|
||||||
|
- `shareAnalytics`: Boolean (default: false) – Anonyme Stats teilen.
|
||||||
|
- `dataRetention`: Number (default: 30) – Tage bis Auto-Delete von Cache.
|
||||||
|
|
||||||
|
**Speicherung**: Capacitor Preferences API (`@capacitor/preferences`) für sichere, persistente Speicherung. Bei Web-Fallback: localStorage mit Encryption (via Crypto API).
|
||||||
|
|
||||||
|
## Tenant-spezifische Optionen
|
||||||
|
Diese werden über API (`GET/PATCH /tenant/settings`) geladen und überschreiben globale Defaults. Sie definieren das Verhalten für den Tenant und seine Events:
|
||||||
|
|
||||||
|
### Theme & Branding
|
||||||
|
- **primaryColor**: String (default: '#007AFF') – Hauptfarbe (iOS-Blau); verwendet in Framework7 CSS-Vars (`--f7-primary`).
|
||||||
|
- **secondaryColor**: String (default: '#5856D6') – Sekundärfarbe für Buttons/Accents.
|
||||||
|
- **logoUrl**: String – Custom Logo für App-Banner (URL zu S3/CDN).
|
||||||
|
- **tenantName**: String – Anzeigename (z.B. "Müller Hochzeit"); in Dashboard-Banner.
|
||||||
|
|
||||||
|
### Event Limits & Features
|
||||||
|
- **maxEventsPerMonth**: Number (default: 5) – Limit pro Monat (enforced via Credits).
|
||||||
|
- **maxPhotosPerEvent**: Number (default: 1000) – Soft-Limit; Warnung bei Überschreitung.
|
||||||
|
- **enableTasks**: Boolean (default: true) – Tasks-System aktivieren.
|
||||||
|
- **enableEmotions**: Boolean (default: true) – Emotions/Reactions erlauben.
|
||||||
|
- **autoApprovePhotos**: Boolean (default: false) – Neue Uploads sofort freigeben (Risiko: Spam).
|
||||||
|
|
||||||
|
### Legal & Compliance
|
||||||
|
- **legalPages**: Object (von Backend geladen, siehe docs/prp/02-tenancy.md)
|
||||||
|
- `impressumUrl`: String – Pflicht in DE; Customizable Link.
|
||||||
|
- `privacyUrl`: String – Datenschutzerklärung.
|
||||||
|
- `agbUrl`: String – Allgemeine Geschäftsbedingungen.
|
||||||
|
- **gdprRetentionDays**: Number (default: 30) – Automatische Löschung alter Photos/Events.
|
||||||
|
- **contactEmail**: String – Support-Email für In-App-Feedback.
|
||||||
|
|
||||||
|
### Notifications & Integration
|
||||||
|
- **pushEnabled**: Boolean – Tenant-weit Push aktivieren (erfordert Plugin-Registrierung).
|
||||||
|
- **stripePublicKey**: String – Für Client-side Checkout (aus Env, nicht persistent).
|
||||||
|
- **eventJoinDomain**: String (default: 'events.fotospiel.app') – Custom Domain für Guest-PWA.
|
||||||
|
|
||||||
|
**API-Handling**: Laden bei Login; Cache mit ETag; Update triggert UI-Refresh (z.B. Theme-Wechsel). Validation: Backend enforct Limits (z.B. Colors als HEX).
|
||||||
|
|
||||||
|
## Capacitor-Plugins
|
||||||
|
Die App integriert folgende Plugins für native Features. Installation via `npx cap add` und Sync nach Build.
|
||||||
|
|
||||||
|
### Essentielle Plugins
|
||||||
|
1. **@capacitor/preferences** (v6+)
|
||||||
|
- **Zweck**: Sichere Speicherung von App- und User-Settings.
|
||||||
|
- **Usage**: `Preferences.set({ key: 'themeMode', value: 'dark' })`; Migrate von localStorage.
|
||||||
|
- **iOS/Android**: Keychain/Keystore für Encryption.
|
||||||
|
|
||||||
|
2. **@capacitor/push-notifications** (v6+)
|
||||||
|
- **Zweck**: Native Push für neue Photos, Event-Updates, Low-Credits.
|
||||||
|
- **Setup**: Registrierung bei App-Start (`PushNotifications.register()`); Token an Backend senden (`POST /tenant/devices`).
|
||||||
|
- **Events**: `registrationToken` (senden), `pushNotificationReceived` (In-App-Handling), `pushNotificationActionPerformed` (Tap-Actions).
|
||||||
|
- **Permissions**: Request bei erstem Login; Fallback zu Web Push in PWA/TWA.
|
||||||
|
|
||||||
|
3. **@capacitor/storage** (v6+)
|
||||||
|
- **Zweck**: Offline-Caching von Events/Photos (als Alternative zu IndexedDB).
|
||||||
|
- **Usage**: `Storage.set({ key: 'event-123', value: JSON.stringify(data) })`; Size-Limit beachten (50MB).
|
||||||
|
- **Fallback**: Für Web; Sync mit Background-Sync.
|
||||||
|
|
||||||
|
4. **@capacitor/camera** (v6+)
|
||||||
|
- **Zweck**: Direkte Kamera-Zugriff für Event-QR-Scans oder schnelle Photo-Uploads (Admin-Selfies).
|
||||||
|
- **Usage**: `Camera.getPhoto({ quality: 90, allowEditing: true })`; Upload via signed URL.
|
||||||
|
- **Permissions**: Camera/Mic; Privacy-Manifest für App Store.
|
||||||
|
|
||||||
|
5. **@capacitor/network** (v6+)
|
||||||
|
- **Zweck**: Connectivity-Status überwachen; Offline-Modus triggern.
|
||||||
|
- **Usage**: `Network.addListener('networkStatusChange', handleOffline)`; UI-Update bei 'offline'.
|
||||||
|
- **Integration**: Deaktiviere Sync-Buttons; Zeige Cached-Data.
|
||||||
|
|
||||||
|
### Optionale Plugins
|
||||||
|
- **@capacitor/haptics** (v6+): Vibration-Feedback bei Actions (z.B. Photo-Approve).
|
||||||
|
- **@capacitor/share** (v6+): Teilen von Event-QR-Codes via Native Share-Sheet.
|
||||||
|
- **@capacitor/device** (v6+): Geräte-Info (Model, OS) für Analytics; Token zu Backend.
|
||||||
|
|
||||||
|
### Plugin-Konfiguration (capacitor.config.ts)
|
||||||
|
```typescript
|
||||||
|
import { CapacitorConfig } from '@capacitor/cli';
|
||||||
|
|
||||||
|
const config: CapacitorConfig = {
|
||||||
|
appId: 'com.fotospiel.tenantadmin',
|
||||||
|
appName: 'Event Photo Admin',
|
||||||
|
webDir: 'dist',
|
||||||
|
bundledWebRuntime: false,
|
||||||
|
plugins: {
|
||||||
|
PushNotifications: {
|
||||||
|
presentationOptions: ["badge", "sound", "alert"]
|
||||||
|
},
|
||||||
|
Camera: {
|
||||||
|
permissions: {
|
||||||
|
camera: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Security & Privacy
|
||||||
|
- **Plugin-Permissions**: Explizite Requests; Erkläre Zweck im Onboarding (z.B. "Push für neue Photos").
|
||||||
|
- **Data Storage**: Kein PII in Preferences; Tokens encrypted (siehe Auth-Specs).
|
||||||
|
- **App Store Compliance**: Privacy Manifest mit Usage-Descriptions (z.B. NSPhotoLibraryUsageDescription).
|
||||||
|
|
||||||
|
Für detailliertes Packaging siehe capacitor-setup.md; UI-Integration in pages-ui.md.
|
||||||
Reference in New Issue
Block a user