feat: bloquear is_community/isCommunityAnnounce y filtrar consultas

Co-authored-by: aider (openrouter/openai/gpt-5) <aider@aider.chat>
webui
brobert 1 week ago
parent 542e1f03a4
commit e7ae220377

@ -20,7 +20,7 @@ export const GET: RequestHandler = async (event) => {
ON gm.group_id = g.id AND gm.user_id = ? AND gm.is_active = 1
INNER JOIN allowed_groups ag
ON ag.group_id = g.id AND ag.status = 'allowed'
WHERE COALESCE(g.active, 1) = 1
WHERE COALESCE(g.active, 1) = 1 AND COALESCE(g.is_community, 0) = 0 AND COALESCE(g.archived, 0) = 0
ORDER BY (g.name IS NULL) ASC, g.name ASC, g.id ASC`
)
.all(userId) as Array<{ id: string; name: string | null }>;

@ -43,7 +43,7 @@ export const POST: RequestHandler = async (event) => {
ON gm.group_id = g.id AND gm.user_id = ? AND gm.is_active = 1
INNER JOIN allowed_groups ag
ON ag.group_id = g.id AND ag.status = 'allowed'
WHERE g.id = ? AND COALESCE(g.active, 1) = 1
WHERE g.id = ? AND COALESCE(g.active, 1) = 1 AND COALESCE(g.is_community, 0) = 0 AND COALESCE(g.archived, 0) = 0
LIMIT 1`
)
.get(userId, groupId) as any;

@ -49,7 +49,7 @@ export const GET: RequestHandler = async ({ params, request }) => {
.prepare(
`SELECT t.id, t.description, t.due_date, g.name AS group_name
FROM tasks t
INNER JOIN groups g ON g.id = t.group_id AND COALESCE(g.active,1)=1 AND COALESCE(g.archived,0)=0
INNER JOIN groups g ON g.id = t.group_id AND COALESCE(g.active,1)=1 AND COALESCE(g.archived,0)=0 AND COALESCE(g.is_community,0)=0
INNER JOIN group_members gm ON gm.group_id = t.group_id AND gm.user_id = ? AND gm.is_active = 1
INNER JOIN allowed_groups ag ON ag.group_id = t.group_id AND ag.status = 'allowed'
WHERE COALESCE(t.completed, 0) = 0

@ -42,9 +42,9 @@ export const GET: RequestHandler = async ({ params, request }) => {
// Validar estado del grupo (activo y no archivado); en caso contrario, tratar como feed caducado
const gRow = db
.prepare(`SELECT COALESCE(active,1) as active, COALESCE(archived,0) as archived FROM groups WHERE id = ?`)
.prepare(`SELECT COALESCE(active,1) as active, COALESCE(archived,0) as archived, COALESCE(is_community,0) as is_community FROM groups WHERE id = ?`)
.get(row.group_id) as any;
if (!gRow || Number(gRow.active || 0) !== 1 || Number(gRow.archived || 0) === 1) {
if (!gRow || Number(gRow.active || 0) !== 1 || Number(gRow.archived || 0) === 1 || Number(gRow.is_community || 0) === 1) {
db.prepare(`UPDATE calendar_tokens SET last_used_at = ? WHERE id = ?`).run(toIsoSql(), row.id);
return new Response('Gone', { status: 410 });
}

@ -56,7 +56,7 @@ export const GET: RequestHandler = async ({ params, request }) => {
AND t.due_date IS NOT NULL
AND t.due_date >= ? AND t.due_date <= ?
AND EXISTS (SELECT 1 FROM task_assignments a WHERE a.task_id = t.id AND a.user_id = ?)
AND (t.group_id IS NULL OR (gm.user_id IS NOT NULL AND ag.group_id IS NOT NULL AND COALESCE(g.active,1)=1))
AND (t.group_id IS NULL OR (gm.user_id IS NOT NULL AND ag.group_id IS NOT NULL AND COALESCE(g.active,1)=1 AND COALESCE(g.is_community,0)=0 AND COALESCE(g.archived,0)=0))
ORDER BY t.due_date ASC, t.id ASC`
)
.all(row.user_id, startYmd, endYmd, row.user_id) as Array<{ id: number; description: string; due_date: string; group_name: string | null }>;

@ -436,5 +436,22 @@ export const migrations: Migration[] = [
END;
`);
}
},
{
version: 16,
name: 'groups-is-community',
checksum: 'v16-groups-is-community-2025-10-19',
up: (db: Database) => {
try {
const cols = db.query(`PRAGMA table_info(groups)`).all() as any[];
const hasCol = Array.isArray(cols) && cols.some((c: any) => String(c.name) === 'is_community');
if (!hasCol) {
db.exec(`ALTER TABLE groups ADD COLUMN is_community BOOLEAN NOT NULL DEFAULT 0;`);
}
} catch {}
try {
db.exec(`CREATE INDEX IF NOT EXISTS idx_groups_is_community ON groups (is_community);`);
} catch {}
}
}
];

@ -87,30 +87,30 @@ export class GroupSyncService {
console.log(' Grupos crudos de la API:', JSON.stringify(groups, null, 2));
console.log(' Sin filtrar por comunidad (modo multicomunidad). Total grupos:', groups.length);
const dbGroupsBefore = this.dbInstance.prepare('SELECT id, active, COALESCE(archived,0) AS archived, name FROM groups').all();
const dbGroupsBefore = this.dbInstance.prepare('SELECT id, active, COALESCE(archived,0) AS archived, COALESCE(is_community,0) AS is_community, name FROM groups').all();
console.log(' Grupos en DB antes de upsert:', dbGroupsBefore);
const result = await this.upsertGroups(groups);
const dbGroupsAfter = this.dbInstance.prepare('SELECT id, active, COALESCE(archived,0) AS archived, name FROM groups').all();
const dbGroupsAfter = this.dbInstance.prepare('SELECT id, active, COALESCE(archived,0) AS archived, COALESCE(is_community,0) AS is_community, name FROM groups').all();
console.log(' Grupos en DB después de upsert:', dbGroupsAfter);
// Detectar grupos que pasaron de activos a inactivos (y no están archivados) en este sync
try {
const beforeMap = new Map<string, { active: number; archived: number; name?: string | null }>();
const beforeMap = new Map<string, { active: number; archived: number; is_community: number; name?: string | null }>();
for (const r of dbGroupsBefore as any[]) {
beforeMap.set(String(r.id), { active: Number(r.active || 0), archived: Number(r.archived || 0), name: r.name ? String(r.name) : null });
beforeMap.set(String(r.id), { active: Number(r.active || 0), archived: Number(r.archived || 0), is_community: Number((r as any).is_community || 0), name: r.name ? String(r.name) : null });
}
const afterMap = new Map<string, { active: number; archived: number; name?: string | null }>();
const afterMap = new Map<string, { active: number; archived: number; is_community: number; name?: string | null }>();
for (const r of dbGroupsAfter as any[]) {
afterMap.set(String(r.id), { active: Number(r.active || 0), archived: Number(r.archived || 0), name: r.name ? String(r.name) : null });
afterMap.set(String(r.id), { active: Number(r.active || 0), archived: Number(r.archived || 0), is_community: Number((r as any).is_community || 0), name: r.name ? String(r.name) : null });
}
const newlyDeactivated: Array<{ id: string; name: string | null }> = [];
for (const [id, b] of beforeMap.entries()) {
const a = afterMap.get(id);
if (!a) continue;
if (Number(b.active) === 1 && Number(a.active) === 0 && Number(a.archived) === 0) {
if (Number(b.active) === 1 && Number(a.active) === 0 && Number(a.archived) === 0 && Number(a.is_community || 0) === 0 && Number(b.is_community || 0) === 0) {
newlyDeactivated.push({ id, name: a.name ?? b.name ?? null });
}
}
@ -274,7 +274,7 @@ export class GroupSyncService {
}
private static cacheActiveGroups(): void {
const groups = this.dbInstance.prepare('SELECT id, name FROM groups WHERE active = TRUE').all();
const groups = this.dbInstance.prepare('SELECT id, name FROM groups WHERE active = TRUE AND COALESCE(is_community,0) = 0 AND COALESCE(archived,0) = 0').all();
this.activeGroupsCache.clear();
for (const group of groups) {
this.activeGroupsCache.set(group.id, group.name);
@ -288,6 +288,9 @@ export class GroupSyncService {
if (!Array.isArray(allGroups) || allGroups.length === 0) return 0;
const nameById = new Map<string, string>();
for (const g of allGroups) {
// Omitir grupos "comunidad/announce" no operativos
const isComm = !!((g as any)?.isCommunity || (g as any)?.is_community || (g as any)?.isCommunityAnnounce || (g as any)?.is_community_announce);
if (isComm) continue;
if (!g?.id) continue;
const name = String(g.subject || '').trim();
if (!name) continue;
@ -324,7 +327,7 @@ export class GroupSyncService {
}
private static getActiveGroupsCount(): number {
const result = this.dbInstance.prepare('SELECT COUNT(*) as count FROM groups WHERE active = TRUE').get();
const result = this.dbInstance.prepare('SELECT COUNT(*) as count FROM groups WHERE active = TRUE AND COALESCE(is_community,0) = 0 AND COALESCE(archived,0) = 0').get();
return result?.count || 0;
}
@ -412,21 +415,49 @@ export class GroupSyncService {
const existing = this.dbInstance.prepare('SELECT 1 FROM groups WHERE id = ?').get(group.id);
console.log('Checking group:', group.id, 'exists:', !!existing);
const isCommunityFlag = !!(((group as any)?.isCommunity) || ((group as any)?.is_community) || ((group as any)?.isCommunityAnnounce) || ((group as any)?.is_community_announce));
if (existing) {
const updateResult = this.dbInstance.prepare(
'UPDATE groups SET name = ?, community_id = COALESCE(?, community_id), active = TRUE, last_verified = CURRENT_TIMESTAMP WHERE id = ?'
).run(group.subject, group.linkedParent || null, group.id);
'UPDATE groups SET name = ?, community_id = COALESCE(?, community_id), is_community = ?, active = TRUE, last_verified = CURRENT_TIMESTAMP WHERE id = ?'
).run(group.subject, group.linkedParent || null, isCommunityFlag ? 1 : 0, group.id);
console.log('Updated group:', group.id, 'result:', updateResult);
updated++;
} else {
const insertResult = this.dbInstance.prepare(
'INSERT INTO groups (id, community_id, name, active) VALUES (?, ?, ?, TRUE)'
).run(group.id, (group.linkedParent ?? ''), group.subject);
'INSERT INTO groups (id, community_id, name, active, is_community) VALUES (?, ?, ?, TRUE, ?)'
).run(group.id, (group.linkedParent ?? ''), group.subject, isCommunityFlag ? 1 : 0);
console.log('Added group:', group.id, 'result:', insertResult);
added++;
}
// Propagar subject como label a allowed_groups (no degrada estado; actualiza label si cambia)
try { (AllowedGroups as any).dbInstance = this.dbInstance; AllowedGroups.upsertPending(group.id, group.subject, null); } catch {}
// Propagar subject a allowed_groups:
// - Si es grupo "comunidad/announce", bloquearlo.
// - En caso contrario, upsert pending y label.
try {
(AllowedGroups as any).dbInstance = this.dbInstance;
if (isCommunityFlag) {
AllowedGroups.setStatus(group.id, 'blocked', group.subject);
} else {
AllowedGroups.upsertPending(group.id, group.subject, null);
}
} catch {}
// Si es grupo de comunidad, limpiar residuos: revocar tokens y desactivar membresías
if (isCommunityFlag) {
try {
this.dbInstance.prepare(`
UPDATE calendar_tokens
SET revoked_at = strftime('%Y-%m-%d %H:%M:%f','now')
WHERE group_id = ? AND revoked_at IS NULL
`).run(group.id);
} catch {}
try {
this.dbInstance.prepare(`
UPDATE group_members
SET is_active = 0
WHERE group_id = ? AND is_active = 1
`).run(group.id);
} catch {}
}
}
return { added, updated };
@ -1014,6 +1045,8 @@ export class GroupSyncService {
FROM group_members gm
JOIN groups g ON g.id = gm.group_id
WHERE gm.user_id = ? AND gm.is_active = 1 AND g.active = 1
AND COALESCE(g.is_community,0) = 0
AND COALESCE(g.archived,0) = 0
`).all(userId) as any[];
const set = new Set<string>();
for (const r of rows) {

@ -78,7 +78,7 @@ export class TaskService {
} catch {}
if (groupIdToInsert) {
const exists = this.dbInstance.prepare(`SELECT 1 FROM groups WHERE id = ?`).get(groupIdToInsert);
const exists = this.dbInstance.prepare(`SELECT 1 FROM groups WHERE id = ? AND COALESCE(is_community,0) = 0`).get(groupIdToInsert);
if (!exists) {
groupIdToInsert = null;
}
@ -606,6 +606,7 @@ export class TaskService {
WHERE g2.id = t.group_id
AND COALESCE(g2.active,1)=1
AND COALESCE(g2.archived,0)=0
AND COALESCE(g2.is_community,0)=0
))
ORDER BY
CASE WHEN t.due_date IS NULL THEN 1 ELSE 0 END,
@ -636,6 +637,7 @@ export class TaskService {
WHERE g2.id = t.group_id
AND COALESCE(g2.active,1)=1
AND COALESCE(g2.archived,0)=0
AND COALESCE(g2.is_community,0)=0
))
`)
.get() as any;

Loading…
Cancel
Save