mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-21 23:26:42 +02:00
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>
34 lines
1.3 KiB
TypeScript
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);
|
|
});
|
|
}
|