<think>Let me analyze the staged changes to generate a proper conventional commit message.
Looking at the changes: 1. **New files**: - `app/(app)/(tabs)/profile/account-deletion.tsx` - account deletion screen - `app/(app)/(tabs)/profile/privacy-settings.tsx` - privacy settings screen - `src/screens/profile/AccountDeletionScreen.tsx` - account deletion implementation - `src/screens/profile/PrivacySettingsScreen.tsx` - privacy settings implementation 2. **Modified files**: - Database changes: `LocalDataSource.ts`, `DatabaseManager.ts`, `DatabaseConfig.ts`, `MigrationManager.ts` - refactoring database layer (removing LockManager, simplified initialization, added retry logic) - Auth store: `authStore.ts` - changed `initDatabase` to `switchDatabase` - Auth service: `authService.ts` - added privacy settings and account deletion methods - DTOs: `dto.ts` - added PrivacySettingsDTO, DeletionStatusDTO, VoteOptionDTO, VoteResultDTO types - Navigation: `hrefs.ts` - added new href functions for privacy settings and account deletion - Profile screens: `SettingsScreen.tsx`, `index.ts`, `_layout.tsx` - added new menu items and routes - Various message screens - added keyboard handling props - `app/_layout.tsx` - added CSS for mobile touch handling - `CreatePostScreen.tsx` - fixed vote_options format - `PostDetailScreen.tsx` - added type annotations for vote handling - `MediaCacheManager.ts` - updated to use new expo-file-system API - `VerificationSettingsScreen.tsx` - fixed type safety - `AccountSecurityScreen.tsx` - styling refactoring This is a multi-faceted change involving: Database refactoring, new privacy/account deletion features, bug fixes, and UI improvements. The database changes appear to be refactoring without new functionality. The most significant changes are the privacy settings and account deletion features, plus bug fixes like the vote options format and mobile touch handling. </think> feat(profile): add privacy settings and account deletion screens Add privacy settings and account deletion functionality with new screens and APIs. Refactor database initialization to use switchDatabase for user switching. Fix vote options format in post creation and add type safety to vote handling. Improve mobile touch handling in layout and message screens. Update media cache to use new expo-file-system API. BREAKING CHANGE: Database initialization now uses switchDatabase instead of initDatabase for user switching
This commit is contained in:
@@ -1,14 +1,6 @@
|
||||
export const DB_CONFIG = {
|
||||
MAX_RETRIES: 4,
|
||||
WEB_RETRY_DELAY_BASE: 500,
|
||||
NATIVE_RETRY_DELAY: 100,
|
||||
WEB_CLOSE_DELAY: 300,
|
||||
WEB_FINAL_CLOSE_DELAY: 200,
|
||||
LOCK_PREFIX: 'db-',
|
||||
DEFAULT_DB_NAME: 'carrot_bbs.db',
|
||||
DB_NAME_PREFIX: 'carrot_bbs_',
|
||||
} as const;
|
||||
const DEFAULT_DB_NAME = 'carrot_bbs.db';
|
||||
const DB_NAME_PREFIX = 'carrot_bbs_';
|
||||
|
||||
export function getDbName(userId: string | null): string {
|
||||
return userId ? `${DB_CONFIG.DB_NAME_PREFIX}${userId}.db` : DB_CONFIG.DEFAULT_DB_NAME;
|
||||
return userId ? `${DB_NAME_PREFIX}${userId}.db` : DEFAULT_DB_NAME;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as SQLite from 'expo-sqlite';
|
||||
import { LockManager } from './LockManager';
|
||||
import { DB_CONFIG, getDbName } from './DatabaseConfig';
|
||||
import { Platform } from 'react-native';
|
||||
import { getDbName } from './DatabaseConfig';
|
||||
import { runMigrations } from '../schema/MigrationManager';
|
||||
|
||||
type SQLiteDatabase = SQLite.SQLiteDatabase;
|
||||
@@ -10,8 +10,6 @@ class DatabaseManager {
|
||||
private db: SQLiteDatabase | null = null;
|
||||
private currentUserId: string | null = null;
|
||||
private initPromise: Promise<void> | null = null;
|
||||
private initTargetUserId: string | null = null;
|
||||
private isInitialized = false;
|
||||
|
||||
static getInstance(): DatabaseManager {
|
||||
if (!DatabaseManager.instance) {
|
||||
@@ -22,119 +20,81 @@ class DatabaseManager {
|
||||
|
||||
async initialize(userId?: string | null): Promise<void> {
|
||||
const targetUserId = userId || null;
|
||||
const callId = Date.now();
|
||||
|
||||
console.log(`[DatabaseManager ${callId}] initialize 开始, userId=${targetUserId}, 当前db=${!!this.db}, 当前userId=${this.currentUserId}`);
|
||||
|
||||
if (this.db && this.currentUserId === targetUserId) {
|
||||
console.log(`[DatabaseManager ${callId}] 已是目标数据库,直接返回`);
|
||||
this.isInitialized = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.initPromise && this.initTargetUserId === targetUserId) {
|
||||
console.log(`[DatabaseManager ${callId}] 等待进行中的初始化`);
|
||||
await this.initPromise;
|
||||
console.log(`[DatabaseManager ${callId}] 初始化完成`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.initPromise) {
|
||||
console.log(`[DatabaseManager ${callId}] 等待其他初始化完成`);
|
||||
await this.initPromise.catch(() => {});
|
||||
await this.initPromise;
|
||||
if (this.db && this.currentUserId === targetUserId) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.db && this.currentUserId === targetUserId) {
|
||||
console.log(`[DatabaseManager ${callId}] 其他初始化已完成目标,直接返回`);
|
||||
this.isInitialized = true;
|
||||
this.initPromise = this.doInitialize(targetUserId);
|
||||
|
||||
try {
|
||||
await this.initPromise;
|
||||
} finally {
|
||||
this.initPromise = null;
|
||||
}
|
||||
}
|
||||
|
||||
private async doInitialize(targetUserId: string | null): Promise<void> {
|
||||
if (this.db) {
|
||||
this.forceClose(this.db);
|
||||
this.db = null;
|
||||
this.currentUserId = null;
|
||||
}
|
||||
|
||||
const dbName = getDbName(targetUserId);
|
||||
const isWeb = Platform.OS === 'web';
|
||||
|
||||
if (isWeb) {
|
||||
try {
|
||||
const db = await this.tryOpenDatabase(dbName);
|
||||
await runMigrations(db);
|
||||
this.db = db;
|
||||
this.currentUserId = targetUserId;
|
||||
} catch (error) {
|
||||
console.warn('[DatabaseManager] Web数据库打开失败,使用内存数据库:', error);
|
||||
const db = await SQLite.openDatabaseAsync(':memory:');
|
||||
await runMigrations(db);
|
||||
this.db = db;
|
||||
this.currentUserId = targetUserId;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
this.initTargetUserId = targetUserId;
|
||||
console.log(`[DatabaseManager ${callId}] 开始实际初始化`);
|
||||
|
||||
this.initPromise = LockManager.withMutex(async () => {
|
||||
const mutexId = callId;
|
||||
console.log(`[DatabaseManager ${mutexId}] 获得互斥锁`);
|
||||
|
||||
if (this.db && this.currentUserId === targetUserId) {
|
||||
console.log(`[DatabaseManager ${mutexId}] 互斥锁内检查:已是目标数据库`);
|
||||
this.isInitialized = true;
|
||||
return;
|
||||
}
|
||||
|
||||
const isWeb = typeof window !== 'undefined';
|
||||
|
||||
if (this.db) {
|
||||
console.log(`[DatabaseManager ${mutexId}] 关闭旧数据库连接...`);
|
||||
try {
|
||||
await this.db.closeAsync();
|
||||
console.log(`[DatabaseManager ${mutexId}] 旧连接已关闭`);
|
||||
} catch (e) {
|
||||
console.warn(`[DatabaseManager ${mutexId}] 关闭旧连接失败:`, e);
|
||||
}
|
||||
this.db = null;
|
||||
this.currentUserId = null;
|
||||
this.isInitialized = false;
|
||||
|
||||
if (isWeb) {
|
||||
await new Promise(r => setTimeout(r, DB_CONFIG.WEB_CLOSE_DELAY));
|
||||
}
|
||||
}
|
||||
|
||||
const dbName = getDbName(targetUserId);
|
||||
console.log(`[DatabaseManager ${mutexId}] 打开数据库: ${dbName}`);
|
||||
|
||||
const database = await this.openDatabaseWithRetry(dbName);
|
||||
console.log(`[DatabaseManager ${mutexId}] 数据库已打开,执行迁移`);
|
||||
|
||||
await runMigrations(database);
|
||||
console.log(`[DatabaseManager ${mutexId}] 迁移完成`);
|
||||
|
||||
this.db = database;
|
||||
this.currentUserId = targetUserId;
|
||||
this.isInitialized = true;
|
||||
console.log(`[DatabaseManager ${mutexId}] 初始化完成`);
|
||||
}).finally(() => {
|
||||
console.log(`[DatabaseManager ${callId}] 清理初始化状态`);
|
||||
this.initPromise = null;
|
||||
this.initTargetUserId = null;
|
||||
});
|
||||
|
||||
await this.initPromise;
|
||||
console.log(`[DatabaseManager ${callId}] initialize 返回`);
|
||||
const db = await this.tryOpenDatabase(dbName);
|
||||
await runMigrations(db);
|
||||
this.db = db;
|
||||
this.currentUserId = targetUserId;
|
||||
}
|
||||
|
||||
private async openDatabaseWithRetry(dbName: string): Promise<SQLiteDatabase> {
|
||||
const isWeb = typeof window !== 'undefined';
|
||||
|
||||
return LockManager.withWebLock(`${DB_CONFIG.LOCK_PREFIX}${dbName}`, async () => {
|
||||
let lastError: unknown;
|
||||
for (let attempt = 0; attempt <= DB_CONFIG.MAX_RETRIES; attempt++) {
|
||||
try {
|
||||
const database = await SQLite.openDatabaseAsync(dbName);
|
||||
console.log(`[DatabaseManager] 成功打开数据库 (尝试 ${attempt + 1}/${DB_CONFIG.MAX_RETRIES + 1})`);
|
||||
return database;
|
||||
} catch (error) {
|
||||
lastError = error;
|
||||
const msg = String(error);
|
||||
console.error(`[DatabaseManager] 打开失败 (尝试 ${attempt + 1}/${DB_CONFIG.MAX_RETRIES + 1}):`, msg);
|
||||
|
||||
if (isWeb && msg.includes('cannot create file')) {
|
||||
console.log('[DatabaseManager] OPFS 不可用,回退到内存数据库');
|
||||
const memoryDb = await SQLite.openDatabaseAsync(':memory:');
|
||||
return memoryDb;
|
||||
}
|
||||
|
||||
if (attempt < DB_CONFIG.MAX_RETRIES) {
|
||||
const delay = isWeb ? DB_CONFIG.WEB_RETRY_DELAY_BASE * (attempt + 1) : DB_CONFIG.NATIVE_RETRY_DELAY;
|
||||
console.warn(`[DatabaseManager] ${delay}ms 后重试...`);
|
||||
await new Promise(r => setTimeout(r, delay));
|
||||
}
|
||||
}
|
||||
private async tryOpenDatabase(dbName: string): Promise<SQLiteDatabase> {
|
||||
try {
|
||||
return await SQLite.openDatabaseAsync(dbName, {
|
||||
useNewConnection: true,
|
||||
});
|
||||
} catch (error) {
|
||||
const msg = String(error);
|
||||
if (msg.includes('cannot create file') || msg.includes('SQLITE_CANTOPEN') || msg.includes('database is locked')) {
|
||||
return await SQLite.openDatabaseAsync(':memory:');
|
||||
}
|
||||
throw lastError;
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private forceClose(db: SQLiteDatabase): void {
|
||||
try {
|
||||
db.closeSync();
|
||||
} catch {
|
||||
try {
|
||||
db.closeAsync().catch(() => {});
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
|
||||
async close(): Promise<void> {
|
||||
@@ -142,43 +102,22 @@ class DatabaseManager {
|
||||
await this.initPromise.catch(() => {});
|
||||
}
|
||||
|
||||
const dbName = getDbName(this.currentUserId);
|
||||
|
||||
await LockManager.withWebLock(`${DB_CONFIG.LOCK_PREFIX}${dbName}`, async () => {
|
||||
await LockManager.withMutex(async () => {
|
||||
if (!this.db) return;
|
||||
|
||||
try {
|
||||
await this.db.closeAsync();
|
||||
console.log('[DatabaseManager] 数据库已关闭');
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
await new Promise(r => setTimeout(r, DB_CONFIG.WEB_FINAL_CLOSE_DELAY));
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('[DatabaseManager] 关闭失败:', e);
|
||||
}
|
||||
this.db = null;
|
||||
this.currentUserId = null;
|
||||
this.isInitialized = false;
|
||||
});
|
||||
});
|
||||
if (this.db) {
|
||||
this.forceClose(this.db);
|
||||
this.db = null;
|
||||
this.currentUserId = null;
|
||||
}
|
||||
}
|
||||
|
||||
async getDb(timeout: number = 10000): Promise<SQLiteDatabase> {
|
||||
if (this.isInitialized && this.db) return this.db;
|
||||
async getDb(): Promise<SQLiteDatabase> {
|
||||
if (this.db) return this.db;
|
||||
|
||||
if (this.initPromise) {
|
||||
const timeoutPromise = new Promise<null>((_, reject) =>
|
||||
setTimeout(() => reject(new Error('等待数据库初始化超时')), timeout)
|
||||
);
|
||||
|
||||
await Promise.race([this.initPromise, timeoutPromise]);
|
||||
|
||||
await this.initPromise;
|
||||
if (this.db) return this.db;
|
||||
}
|
||||
|
||||
throw new Error('数据库未初始化,请先调用 initialize(userId)');
|
||||
throw new Error('数据库未初始化');
|
||||
}
|
||||
|
||||
getDbSync(): SQLiteDatabase | null {
|
||||
@@ -190,11 +129,7 @@ class DatabaseManager {
|
||||
}
|
||||
|
||||
isReady(): boolean {
|
||||
return this.isInitialized && this.db !== null;
|
||||
}
|
||||
|
||||
async withMutex<T>(fn: () => Promise<T>): Promise<T> {
|
||||
return LockManager.withMutex(fn);
|
||||
return this.db !== null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user