Store readiness: in-app account deletion, privacy policy, terms, listing copy
Closes the remaining store-compliance gaps before App Store / Play submission: - Account deletion (App Store guideline 5.1.1): DELETE /me removes the user row (usage cascades). The app exposes it through one quiet 'Account' caption link under the chat, visible only when signed in, driving two native dialogs (Sign out / Delete account with a destructive confirm) — no new views, no menus, minimalism intact. Deletion signs out and resets the app; copy notes that store subscriptions are cancelled in App Store / Play settings. - docs/store/privacy-policy.md: the complete data inventory (matching the actual schema), transient OpenAI processing with no training, no profiling or ads, GDPR legal bases and rights, in-app erasure. - docs/store/terms-of-service.md: not-therapy positioning with crisis guidance, AI-generated-content caveat, 18+ eligibility, auto-renewal/cancellation terms, liability, Swedish governing law. - docs/store/listing.md: App Store and Play copy written to the honest-claims rule (subtitle 'Think Beyond Thought', keywords, descriptions), plus App Privacy and Data safety questionnaire mappings. - LAUNCH.md updated: host the policy/terms, set store URLs, use the prepared listing copy. - Tests: 30 passing (adds DELETE /me coverage). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0118DaxZR36RpnY524vRqx3z
This commit is contained in:
@@ -75,6 +75,12 @@ export async function incrementUsage(userId: string): Promise<void> {
|
||||
await db.query(`update usage set messages_used = messages_used + 1 where user_id = $1`, [userId]);
|
||||
}
|
||||
|
||||
/** Permanently removes the user; the usage row cascades. */
|
||||
export async function deleteUser(userId: string): Promise<void> {
|
||||
const db = await getPool();
|
||||
await db.query(`delete from users where id = $1`, [userId]);
|
||||
}
|
||||
|
||||
export async function setSubscriptionStatus(
|
||||
userId: string,
|
||||
status: 'free' | 'active',
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
import type { APIGatewayProxyEventV2, APIGatewayProxyResultV2 } from 'aws-lambda';
|
||||
import { generateReply, type ChatMessage } from './chat.js';
|
||||
import { config } from './config.js';
|
||||
import { getOrCreateUser, getUsage, incrementUsage, resetUsage, type UserRow } from './db.js';
|
||||
import {
|
||||
deleteUser,
|
||||
getOrCreateUser,
|
||||
getUsage,
|
||||
incrementUsage,
|
||||
resetUsage,
|
||||
type UserRow,
|
||||
} from './db.js';
|
||||
import { verifyAndApplyPurchase } from './subscription/index.js';
|
||||
import type { VerifyPurchaseRequest } from './subscription/types.js';
|
||||
import { canSendMessage, shouldResetUsage } from './usage.js';
|
||||
@@ -142,6 +149,12 @@ export async function handler(event: APIGatewayProxyEventV2): Promise<APIGateway
|
||||
switch (route) {
|
||||
case 'GET /me':
|
||||
return await handleMe(user);
|
||||
case 'DELETE /me':
|
||||
// In-app account deletion (App Store guideline 5.1.1). Removes the
|
||||
// user and usage rows; store subscriptions are cancelled by the user
|
||||
// in App Store / Play settings.
|
||||
await deleteUser(user.id);
|
||||
return json(200, { deleted: true });
|
||||
case 'POST /chat':
|
||||
return await handleChat(user, event.body);
|
||||
case 'POST /subscription/verify':
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
vi.mock('../src/db.js', () => ({
|
||||
deleteUser: vi.fn(),
|
||||
getOrCreateUser: vi.fn(),
|
||||
getUsage: vi.fn(),
|
||||
incrementUsage: vi.fn(),
|
||||
@@ -14,7 +15,7 @@ vi.mock('../src/subscription/index.js', () => ({
|
||||
}));
|
||||
|
||||
import { generateReply } from '../src/chat.js';
|
||||
import { getOrCreateUser, getUsage, incrementUsage } from '../src/db.js';
|
||||
import { deleteUser, getOrCreateUser, getUsage, incrementUsage } from '../src/db.js';
|
||||
import { handler } from '../src/handler.js';
|
||||
import { verifyAndApplyPurchase } from '../src/subscription/index.js';
|
||||
|
||||
@@ -101,6 +102,15 @@ describe('authorization', () => {
|
||||
});
|
||||
expect(getOrCreateUser).toHaveBeenCalledWith('sub-1', 'a@b.se', 'apple');
|
||||
});
|
||||
|
||||
it('deletes the account on DELETE /me', async () => {
|
||||
const { status, body } = await call('DELETE', '/me', {
|
||||
claims: { sub: 'sub-1', email: 'a@b.se' },
|
||||
});
|
||||
expect(status).toBe(200);
|
||||
expect(body.deleted).toBe(true);
|
||||
expect(deleteUser).toHaveBeenCalledWith('user-1');
|
||||
});
|
||||
});
|
||||
|
||||
describe('chat and the intelligent paywall', () => {
|
||||
|
||||
Reference in New Issue
Block a user