rojects/source/server/projectAccess.ts

47 lines
1.7 KiB
TypeScript

import { Project, ProjectMember, projects, projectMembers } from './db';
import { JwtUser } from './middleware/auth';
export function isOwner( project: Project, user: JwtUser ): boolean
{
return project.owner_id === user.userId;
}
function memberMatchesUser( member: ProjectMember, user: JwtUser ): boolean
{
if ( member.member_type !== 'user' ) return false;
// Interim (Option B): member_id stores the email address.
// When migrating to ID-based lookup, change this one line to: member.member_id === user.userId
return member.member_id === user.email;
}
export function getMemberRole( members: ProjectMember[], user: JwtUser ): string | null
{
return members.find( m => memberMatchesUser( m, user ) )?.role ?? null;
}
export function canView( project: Project, members: ProjectMember[], user: JwtUser ): boolean
{
return isOwner( project, user ) || getMemberRole( members, user ) !== null;
}
export function canEdit( project: Project, members: ProjectMember[], user: JwtUser ): boolean
{
if ( isOwner( project, user ) ) return true;
const role = getMemberRole( members, user );
return role === 'editor' || role === 'admin';
}
export type AccessResult =
| { ok: true }
| { ok: false; status: number; error: string };
export function checkAccess( projectId: string, user: JwtUser, mode: 'view' | 'edit' ): AccessResult
{
const project = projects.findById( projectId );
if ( !project ) return { ok: false, status: 404, error: 'Not found' };
const members = projectMembers.forProject( projectId );
const allowed = mode === 'view' ? canView( project, members, user ) : canEdit( project, members, user );
if ( !allowed ) return { ok: false, status: 403, error: 'Forbidden' };
return { ok: true };
}