godcrm/backend/services/public/resolveLandingProject.js
GOD CRM Release f89e074dd1
Some checks failed
CI / Lint / Typecheck / Test / Build (push) Has been cancelled
CI / PostgreSQL Integration Tests (push) Has been cancelled
GOD CRM — public scrubbed snapshot
Governed substrate for autonomous agents: scoped identity (passports),
audited actions, MCP workspace. Infra IPs and secrets redacted for public release.
2026-08-10 04:01:45 +03:00

114 lines
3.7 KiB
JavaScript

// ADR-0060-A P7/A2 — Resolve the landing project for a public space.
//
// Public `/s/:slug` mounts a project's dashboard directly (ADR-0060-A
// supersedes the P-track card-grid). To pick which project, we use a
// 3-tier fallback so the page is meaningful even when the owner has
// not configured a main project yet:
//
// 1. `spaces.main_project_id` if it is set, the project is_public, and
// it is NOT the per-space System Data project (AC15/16 from ADR-0060
// §6 — hard-banned regardless of is_public).
// 2. else the FIRST public project in the space ordered by
// (order_index, id), skipping any System Data project.
// 3. else null — the landing page renders a tiny empty state.
//
// For the resolved project we additionally surface its first public
// dashboard (`main_dashboard_id`), so the frontend can mount
// <DashboardGrid> in a single round trip without a second resolve.
import { dbGet, dbAll } from '../../database/connection.js';
// Mirror of `isSystemDataProject` in backend/routes/v3/public.js. Kept in
// sync deliberately — same defensive matcher (type='system_data', legacy
// 'system', or name === 'System Data') so this helper is rename-safe.
function isSystemDataProject(row) {
if (!row) return false;
const t = row.type;
if (t === 'system_data' || t === 'system') return true;
if (row.name === 'System Data') return true;
return false;
}
async function pickFirstPublicDashboard(projectId) {
const row = await dbGet(
`SELECT id
FROM dashboards
WHERE project_id = ?
AND is_public IS NOT FALSE
ORDER BY order_index, id
LIMIT 1`,
[projectId]
);
return row ? row.id : null;
}
async function loadCandidateProject(projectId) {
if (projectId == null) return null;
return dbGet(
`SELECT id, space_id, name, type, is_public
FROM projects
WHERE id = ?`,
[projectId]
);
}
/**
* Resolve the landing project + dashboard for a public space.
*
* @param {number|string} spaceId - the public space's id
* @returns {Promise<{ main_project_id: number|null, main_dashboard_id: number|null }>}
*/
export async function resolveLandingProject(spaceId) {
if (spaceId == null) {
return { main_project_id: null, main_dashboard_id: null };
}
const space = await dbGet(
`SELECT id, main_project_id FROM spaces WHERE id = ?`,
[spaceId]
);
if (!space) {
return { main_project_id: null, main_dashboard_id: null };
}
// Tier 1 — explicit main_project_id, gated by is_public + System Data ban.
if (space.main_project_id != null) {
const candidate = await loadCandidateProject(space.main_project_id);
if (
candidate &&
String(candidate.space_id) === String(spaceId) &&
candidate.is_public !== false &&
!isSystemDataProject(candidate)
) {
const dashboardId = await pickFirstPublicDashboard(candidate.id);
return {
main_project_id: candidate.id,
main_dashboard_id: dashboardId
};
}
// fallthrough — the configured main project is no longer eligible.
}
// Tier 2 — first public project in the space (deterministic).
const projects = await dbAll(
`SELECT id, name, type, is_public
FROM projects
WHERE space_id = ?
AND is_public IS NOT FALSE
ORDER BY order_index, id`,
[spaceId]
);
const firstEligible = projects.find(p => !isSystemDataProject(p));
if (!firstEligible) {
return { main_project_id: null, main_dashboard_id: null };
}
const dashboardId = await pickFirstPublicDashboard(firstEligible.id);
return {
main_project_id: firstEligible.id,
main_dashboard_id: dashboardId
};
}
// Internal helper exposed for tests only.
export const __test__ = { isSystemDataProject };