diff --git a/src/AccountDO.ts b/src/AccountDO.ts index 97f0d28..c95d037 100644 --- a/src/AccountDO.ts +++ b/src/AccountDO.ts @@ -162,12 +162,7 @@ export class AccountDO extends DurableObject { this.sql.exec('INSERT OR REPLACE INTO members (user_id, role, joined_at) VALUES (?, ?, ?)', user_id, role, now); // Update SystemDO index - try { - const systemStub = this.env.SYSTEM.get(this.env.SYSTEM.idFromName('global')); - await systemStub.incrementMemberCount(this.ctx.id.toString()); - } catch (e) { - console.error('Failed to update member count in SystemDO', e); - } + await this.syncMemberCount(); // Sync with User DO try { @@ -198,12 +193,7 @@ export class AccountDO extends DurableObject { this.sql.exec('DELETE FROM members WHERE user_id = ?', userId); // Update SystemDO index - try { - const systemStub = this.env.SYSTEM.get(this.env.SYSTEM.idFromName('global')); - await systemStub.decrementMemberCount(this.ctx.id.toString()); - } catch (e) { - console.error('Failed to update member count in SystemDO', e); - } + await this.syncMemberCount(); // Sync with User DO try { @@ -216,6 +206,19 @@ export class AccountDO extends DurableObject { return { success: true }; } + private async syncMemberCount() { + try { + const result = this.sql.exec('SELECT COUNT(*) as count FROM members'); + const row = result.next().value as any; + const count = row ? row.count : 0; + + const systemStub = this.env.SYSTEM.get(this.env.SYSTEM.idFromName('global')); + await systemStub.updateMemberCount(this.ctx.id.toString(), count); + } catch (e) { + console.error('Failed to update member count in SystemDO', e); + } + } + async delete() { // Get all members to notify their UserDOs const members = Array.from(this.sql.exec('SELECT user_id FROM members')); diff --git a/src/SystemDO.ts b/src/SystemDO.ts index 9328a7e..e0739e8 100644 --- a/src/SystemDO.ts +++ b/src/SystemDO.ts @@ -214,12 +214,8 @@ export class SystemDO extends DurableObject { return { success: true }; } - async incrementMemberCount(accountId: string) { - this.sql.exec('UPDATE accounts SET member_count = member_count + 1 WHERE id = ?', accountId); - } - - async decrementMemberCount(accountId: string) { - this.sql.exec('UPDATE accounts SET member_count = member_count - 1 WHERE id = ?', accountId); + async updateMemberCount(accountId: string, count: number) { + this.sql.exec('UPDATE accounts SET member_count = ? WHERE id = ?', count, accountId); } async updateAccount(accountId: string, data: any) { diff --git a/test/admin.spec.ts b/test/admin.spec.ts index 5b89125..7d6f4c9 100644 --- a/test/admin.spec.ts +++ b/test/admin.spec.ts @@ -358,6 +358,58 @@ describe('Admin Administration', () => { expect(account.member_count).toBe(0); }); + it('should restore member_count in SystemDO from AccountDO truth', async () => { + // 1. Get an admin user + const adminId = env.USER.idFromName('admin'); + const adminStub = env.USER.get(adminId); + const adminIdStr = adminId.toString(); + + const { sessionId } = await adminStub.createSession(); + const cookieHeader = `session_id=${await cookieManager.encrypt(`${sessionId}:${adminIdStr}`)}`; + + // 2. Create an account + const createRes = await SELF.fetch('http://example.com/users/admin/api/accounts', { + method: 'POST', + headers: { + Cookie: cookieHeader, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ name: 'Sync Test Account' }), + }); + const { id: accountId } = (await createRes.json()) as any; + + // 3. Manually break the count in SystemDO (set it to 100) + const systemStub = env.SYSTEM.get(env.SYSTEM.idFromName('global')); + await systemStub.updateMemberCount(accountId, 100); + + // Verify it is broken + let listRes = await SELF.fetch('http://example.com/users/admin/api/accounts', { + headers: { Cookie: cookieHeader }, + }); + let accounts = (await listRes.json()) as any[]; + let account = accounts.find((a) => a.id === accountId); + expect(account.member_count).toBe(100); + + // 4. Add a member via API, which should trigger syncMemberCount() + const userId = env.USER.newUniqueId().toString(); + await SELF.fetch(`http://example.com/users/admin/api/accounts/${accountId}/members`, { + method: 'POST', + headers: { + Cookie: cookieHeader, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ user_id: userId, role: 0 }), + }); + + // 5. Verify count is restored to 1 (actual number of members in AccountDO) + listRes = await SELF.fetch('http://example.com/users/admin/api/accounts', { + headers: { Cookie: cookieHeader }, + }); + accounts = (await listRes.json()) as any[]; + account = accounts.find((a) => a.id === accountId); + expect(account.member_count).toBe(1); + }); + it('should delete an account via admin API', async () => { // 1. Get an admin user const adminId = env.USER.idFromName('admin');