Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion backend/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "backend",
"version": "0.30.0",
"version": "0.30.1",
"description": "",
"author": "",
"private": true,
Expand Down
7 changes: 7 additions & 0 deletions backend/src/database/content-seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -841,6 +841,13 @@ $$

## Version 0.30

<details>
<summary>0.30.1 — 2026-09-24</summary>

- Service central de résolution des permissions (\`PermissionsService\`) : calcule les droits effectifs d'un utilisateur (directs et via ses groupes) sur une page ou globalement — travail préparatoire, pas encore branché sur les contrôles d'accès existants.

</details>

<details>
<summary>0.30.0 — 2026-09-24</summary>

Expand Down
9 changes: 9 additions & 0 deletions backend/src/permissions/permissions.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ import { PageAccessRule } from './entities/page-access-rule.entity.js';
import { UserPermission } from './entities/user-permission.entity.js';
import { TypeormGroupsRepository } from './persistence/typeorm.groups.repository.js';
import { TypeormPageAccessRulesRepository } from './persistence/typeorm.page-access-rules.repository.js';
import { TypeormPageHierarchyRepository } from './persistence/typeorm.page-hierarchy.repository.js';
import { TypeormSubjectPermissionsRepository } from './persistence/typeorm.subject-permissions.repository.js';
import { PermissionsService } from './services/permissions.service.js';

@Module({
imports: [
Expand All @@ -31,11 +33,18 @@ import { TypeormSubjectPermissionsRepository } from './persistence/typeorm.subje
provide: 'PageAccessRulesRepository',
useClass: TypeormPageAccessRulesRepository,
},
{
provide: 'PageHierarchyRepository',
useClass: TypeormPageHierarchyRepository,
},
PermissionsService,
],
exports: [
'GroupsRepository',
'SubjectPermissionsRepository',
'PageAccessRulesRepository',
'PageHierarchyRepository',
PermissionsService,
],
})
export class PermissionsModule {}
1 change: 1 addition & 0 deletions backend/src/permissions/persistence/groups.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ export interface GroupsRepository {
findMemberIds(groupId: string): Promise<string[]>;
findGroupIdsForUser(userId: string): Promise<string[]>;
setMembers(groupId: string, userIds: string[]): Promise<void>;
findByIds(ids: string[]): Promise<Group[]>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,5 @@ export interface PageAccessRulesRepository {
delete(id: string): Promise<void>;
findExclusions(ruleId: string): Promise<string[]>;
setExclusions(ruleId: string, pageIds: string[]): Promise<void>;
findExclusionsForRules(ruleIds: string[]): Promise<Map<string, string[]>>;
}
11 changes: 11 additions & 0 deletions backend/src/permissions/persistence/page-hierarchy.repository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { PageVisibility } from '../../pages/entities/page.entity.js';

export interface PageChain {
pageId: string;
visibility: PageVisibility;
chainIds: string[];
}

export interface PageHierarchyRepository {
findChains(pageIds: string[]): Promise<Map<string, PageChain>>;
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { In, Repository } from 'typeorm';
import { Group } from '../entities/group.entity.js';
import { GroupMember } from '../entities/group-member.entity.js';
import { CreateGroupInput, GroupsRepository } from './groups.repository.js';
Expand Down Expand Up @@ -53,4 +53,11 @@ export class TypeormGroupsRepository implements GroupsRepository {
userIds.map((userId) => this.members.create({ groupId, userId })),
);
}

findByIds(ids: string[]): Promise<Group[]> {
if (ids.length === 0) {
return Promise.resolve([]);
}
return this.groups.findBy({ id: In(ids) });
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { In, Repository } from 'typeorm';
import { PageAction } from '../../common/permissions.js';
import { PageAccessExclusion } from '../entities/page-access-exclusion.entity.js';
import { PageAccessRule } from '../entities/page-access-rule.entity.js';
import {
Expand Down Expand Up @@ -36,7 +37,7 @@ export class TypeormPageAccessRulesRepository implements PageAccessRulesReposito
return this.rules.save(this.rules.create(input));
}

async updateActions(id: string, actions: string[]): Promise<void> {
async updateActions(id: string, actions: PageAction[]): Promise<void> {
await this.rules.update(id, { actions });
}

Expand All @@ -58,4 +59,23 @@ export class TypeormPageAccessRulesRepository implements PageAccessRulesReposito
pageIds.map((pageId) => this.exclusions.create({ ruleId, pageId })),
);
}

async findExclusionsForRules(
ruleIds: string[],
): Promise<Map<string, string[]>> {
const result = new Map<string, string[]>();
if (ruleIds.length === 0) {
return result;
}
const rows = await this.exclusions.findBy({ ruleId: In(ruleIds) });
for (const row of rows) {
const list = result.get(row.ruleId);
if (list) {
list.push(row.pageId);
} else {
result.set(row.ruleId, [row.pageId]);
}
}
return result;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { Injectable } from '@nestjs/common';
import { DataSource } from 'typeorm';
import { PageVisibility } from '../../pages/entities/page.entity.js';
import {
PageChain,
PageHierarchyRepository,
} from './page-hierarchy.repository.js';

interface ChainRow {
root_id: string;
id: string;
visibility: PageVisibility;
depth: number;
}

@Injectable()
export class TypeormPageHierarchyRepository implements PageHierarchyRepository {
constructor(private readonly dataSource: DataSource) {}

async findChains(pageIds: string[]): Promise<Map<string, PageChain>> {
const result = new Map<string, PageChain>();
if (pageIds.length === 0) {
return result;
}

const placeholders = pageIds.map(() => '?').join(', ');
const rows: ChainRow[] = await this.dataSource.query(
`WITH RECURSIVE chain AS (
SELECT id, parent_id, visibility, id AS root_id, 0 AS depth
FROM pages
WHERE id IN (${placeholders}) AND deleted_at IS NULL
UNION ALL
SELECT p.id, p.parent_id, p.visibility, c.root_id, c.depth + 1
FROM pages p
INNER JOIN chain c ON p.id = c.parent_id AND p.deleted_at IS NULL
)
SELECT root_id, id, visibility, depth FROM chain ORDER BY root_id, depth ASC`,
pageIds,
);

for (const row of rows) {
let chain = result.get(row.root_id);
if (!chain) {
chain = {
pageId: row.root_id,
visibility: row.visibility,
chainIds: [],
};
result.set(row.root_id, chain);
}
chain.chainIds.push(row.id);
}

return result;
}
}
Loading
Loading