managarten/services/mana-credits/src/routes/sync.ts
Till JS 5c2ea614cd feat(credits): add sync billing — monthly credit subscription for cloud sync
Cloud Sync is now a paid feature: 30 credits/month (90/quarter, 360/year).
Users start in local-only mode and opt-in via Settings > Cloud Sync.
1 Credit = 1 Cent, so sync costs ~0.30€/month.

When credits run out, sync is paused (not deleted) and an in-app banner
prompts the user to top up. Local data is always preserved.

Backend (mana-credits):
- New sync_subscriptions table in credits schema
- SyncBillingService with activate/deactivate/chargeRecurring
- User-facing routes: GET/POST /api/v1/sync/{status,activate,deactivate,change-interval}
- Internal routes for server-side checks and cron triggers

Frontend (mana web):
- Sync API client + reactive sync-billing store
- syncEnabled parameter gates createUnifiedSync() — sync only starts when active
- Settings sync page with interval selection and activate/deactivate
- Pause banner in app layout when credits insufficient

Also: removed CALDAV_SYNC/GOOGLE_SYNC operations (not needed),
updated CLOUD_SYNC cost from 5 to 30 credits/month.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 22:21:58 +02:00

34 lines
1.3 KiB
TypeScript

/**
* Sync billing routes — user-facing endpoints (JWT auth)
*/
import { Hono } from 'hono';
import type { SyncBillingService } from '../services/sync-billing';
import type { AuthUser } from '../middleware/jwt-auth';
import { activateSyncSchema, changeSyncIntervalSchema } from '../lib/validation';
export function createSyncRoutes(syncBillingService: SyncBillingService) {
return new Hono<{ Variables: { user: AuthUser } }>()
.get('/status', async (c) => {
const user = c.get('user');
const status = await syncBillingService.getSyncStatus(user.userId);
return c.json(status);
})
.post('/activate', async (c) => {
const user = c.get('user');
const body = activateSyncSchema.parse(await c.req.json());
const result = await syncBillingService.activateSync(user.userId, body.interval);
return c.json(result);
})
.post('/deactivate', async (c) => {
const user = c.get('user');
const result = await syncBillingService.deactivateSync(user.userId);
return c.json(result);
})
.post('/change-interval', async (c) => {
const user = c.get('user');
const body = changeSyncIntervalSchema.parse(await c.req.json());
const result = await syncBillingService.changeBillingInterval(user.userId, body.interval);
return c.json(result);
});
}