Skip to content

Commit d90a224

Browse files
committed
feat(api): users controller
1 parent 49f31c7 commit d90a224

File tree

16 files changed

+752
-15
lines changed

16 files changed

+752
-15
lines changed

modules/libs/domain/permissions/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ export const PermissionKeys = [
1515
'schools:read',
1616
'schools:update',
1717
'schools:delete',
18+
19+
// Users
20+
'users:read',
21+
'users:update',
22+
'users:delete',
1823
] as const;
1924

2025
export type PermissionKey = typeof PermissionKeys[number];

modules/libs/protocol/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ export * from './routes'
55
export * from './roles'
66
export * from './userRoles'
77
export * from './organizations'
8+
export * from './users'

modules/libs/protocol/routes.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ export const Routes = (baseUrl: string = '') => ({
2727
delete: (id: string) => `${baseUrl}/edu/roles/${id}`,
2828
},
2929
user: (userId: string) => ({
30+
get: () => `${baseUrl}/edu/users/${userId}`,
31+
find: () => `${baseUrl}/edu/users`,
32+
update: () => `${baseUrl}/edu/users/${userId}`,
33+
delete: () => `${baseUrl}/edu/users/${userId}`,
3034
roles: {
3135
all: () => `${baseUrl}/edu/users/${userId}/roles`,
3236
create: () => `${baseUrl}/edu/users/${userId}/roles`,

modules/libs/protocol/users.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import * as crud from './crud'
2+
3+
/* -------------------------------------------------------------------------- */
4+
/* Models */
5+
/* -------------------------------------------------------------------------- */
6+
7+
export type UserSummary = {
8+
id: string;
9+
name: string;
10+
}
11+
12+
export type UserDetailsRole = {
13+
id: string;
14+
name?: string;
15+
}
16+
17+
export type UserDetails = UserSummary & {
18+
email: string;
19+
phone?: string;
20+
roles: UserDetailsRole[];
21+
}
22+
23+
/* -------------------------------------------------------------------------- */
24+
/* Read */
25+
/* -------------------------------------------------------------------------- */
26+
27+
export type GetUserResponse =
28+
crud.GetItemResponse<UserDetails>;
29+
30+
export type GetUsersResponse =
31+
crud.GetItemsListResponse<UserSummary>;
32+
33+
/* -------------------------------------------------------------------------- */
34+
/* Update */
35+
/* -------------------------------------------------------------------------- */
36+
37+
export type UpdateUserRequest =
38+
crud.UpdateItemRequest<Omit<UserDetails, 'id'>>;
39+
40+
export type UpdateUserResponse =
41+
crud.UpdateItemResponse<UserDetails>;
42+
43+
/* -------------------------------------------------------------------------- */
44+
/* Delete */
45+
/* -------------------------------------------------------------------------- */
46+
47+
export type DeleteUserResponse =
48+
crud.DeleteItemResponse;

modules/services/api/src/auth/utils/user-permissions.util.ts

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,26 +10,30 @@ export class UserPermissions {
1010
* on the provided organization and school. Throws an error
1111
* if user does not have permission.
1212
* @param permissions Required permissions to check
13-
* @param organizationId Organization id to check
14-
* @param schoolId School id to check
13+
* @param scope Scope (or scopes) to check permissions for
1514
*/
1615
public check(
1716
permissions: domain.PermissionKey[],
18-
scope?: { organizationId: string; schoolId?: string },
17+
scope?:
18+
| { organizationId: string; schoolId?: string }
19+
| { organizationId: string; schoolId?: string }[],
1920
) {
20-
const scopes = this.getScopes(permissions);
21-
if (!scopes.length) {
21+
// get user and resource scopes
22+
const resourceScopes = Array.isArray(scope) ? scope : [scope];
23+
const userScopes = this.getScopes(permissions);
24+
if (!userScopes.length) {
2225
throw new ForbiddenException('User does not have permission');
2326
}
2427

2528
if (!scope) {
2629
return;
2730
}
2831

29-
const permitted = scopes.some(
30-
(s) =>
31-
s.organizationId == scope?.organizationId &&
32-
s.schoolId == scope?.schoolId,
32+
const permitted = userScopes.some((us) =>
33+
resourceScopes.some(
34+
(rs) =>
35+
us.organizationId == rs.organizationId && us.schoolId == rs.schoolId,
36+
),
3337
);
3438

3539
if (!permitted) {
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export * from './organizations/organizations.controller';
22
export * from './roles/roles.controller';
33
export * from './users/userRoles.controller';
4+
export * from './users/users.controller';
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
import { faker } from '@faker-js/faker';
2+
import { INestApplication } from '@nestjs/common';
3+
import {
4+
OrganizationsService,
5+
RolesService,
6+
SchoolsService,
7+
UsersService,
8+
} from '@vidya/api/edu/services';
9+
import { Organization, Role, School, User } from '@vidya/entities';
10+
11+
export type Context = {
12+
one: {
13+
org: Organization;
14+
roles: {
15+
orgAdmin: Role;
16+
};
17+
schoolOne: {
18+
school: School;
19+
roles: {
20+
schoolOneAdmin: Role;
21+
};
22+
};
23+
schoolTwo: {
24+
school: School;
25+
roles: {
26+
schoolTwoAdmin: Role;
27+
};
28+
};
29+
users: {
30+
orgAdmin: User;
31+
schoolOneAdmin: User;
32+
schoolTwoAdmin: User;
33+
};
34+
};
35+
two: {
36+
org: Organization;
37+
roles: {
38+
orgAdmin: Role;
39+
};
40+
users: {
41+
orgAdmin: User;
42+
};
43+
};
44+
};
45+
46+
export const createContext = async (
47+
app: INestApplication,
48+
): Promise<Context> => {
49+
const orgsService = app.get(OrganizationsService);
50+
const rolesService = app.get(RolesService);
51+
const schoolsService = app.get(SchoolsService);
52+
const usersService = app.get(UsersService);
53+
54+
/* -------------------------------------------------------------------------- */
55+
/* Organizations */
56+
/* -------------------------------------------------------------------------- */
57+
58+
const orgOne = await orgsService.create({
59+
name: 'Org One',
60+
});
61+
62+
const orgTwo = await orgsService.create({
63+
name: 'Org Two',
64+
});
65+
66+
/* -------------------------------------------------------------------------- */
67+
/* Schools */
68+
/* -------------------------------------------------------------------------- */
69+
70+
const schoolOne = await schoolsService.create({
71+
name: 'School Two',
72+
organizationId: orgOne.id,
73+
});
74+
75+
const schoolTwo = await schoolsService.create({
76+
name: 'School Two',
77+
organizationId: orgOne.id,
78+
});
79+
80+
/* -------------------------------------------------------------------------- */
81+
/* Roles */
82+
/* -------------------------------------------------------------------------- */
83+
84+
const orgAdmin = await rolesService.create({
85+
name: 'Org Admin',
86+
description: 'Organization Admin',
87+
organizationId: orgOne.id,
88+
permissions: ['users:read'],
89+
});
90+
91+
const schoolOneAdmin = await rolesService.create({
92+
name: 'School One Admin',
93+
description: 'School One Admin',
94+
organizationId: orgOne.id,
95+
schoolId: schoolOne.id,
96+
permissions: ['users:read'],
97+
});
98+
99+
const schoolTwoAdmin = await rolesService.create({
100+
name: 'School Two Admin',
101+
description: 'School Two Admin',
102+
organizationId: orgOne.id,
103+
schoolId: schoolTwo.id,
104+
permissions: ['users:read'],
105+
});
106+
107+
const orgTwoAdmin = await rolesService.create({
108+
name: 'Org Two Admin',
109+
description: 'Organization Two Admin',
110+
organizationId: orgTwo.id,
111+
permissions: ['users:read'],
112+
});
113+
114+
/* -------------------------------------------------------------------------- */
115+
/* Users */
116+
/* -------------------------------------------------------------------------- */
117+
118+
const orgAdminUser = await usersService.create({
119+
name: 'Org Admin',
120+
email: faker.internet.email(),
121+
roles: [orgAdmin],
122+
});
123+
124+
const schoolOneAdminUser = await usersService.create({
125+
name: 'School One Admin',
126+
email: faker.internet.email(),
127+
roles: [schoolOneAdmin],
128+
});
129+
130+
const schoolTwoAdminUser = await usersService.create({
131+
name: 'School Two Admin',
132+
email: faker.internet.email(),
133+
roles: [schoolTwoAdmin],
134+
});
135+
136+
const orgTwoAdminUser = await usersService.create({
137+
name: 'Org Two Admin',
138+
email: faker.internet.email(),
139+
roles: [orgTwoAdmin],
140+
});
141+
142+
/* -------------------------------------------------------------------------- */
143+
/* Return */
144+
/* -------------------------------------------------------------------------- */
145+
146+
return {
147+
one: {
148+
org: orgOne,
149+
roles: {
150+
orgAdmin: orgAdmin,
151+
},
152+
schoolOne: {
153+
school: schoolOne,
154+
roles: {
155+
schoolOneAdmin: schoolOneAdmin,
156+
},
157+
},
158+
schoolTwo: {
159+
school: schoolTwo,
160+
roles: {
161+
schoolTwoAdmin: schoolTwoAdmin,
162+
},
163+
},
164+
users: {
165+
orgAdmin: orgAdminUser,
166+
schoolOneAdmin: schoolOneAdminUser,
167+
schoolTwoAdmin: schoolTwoAdminUser,
168+
},
169+
},
170+
two: {
171+
org: orgTwo,
172+
roles: {
173+
orgAdmin: orgTwoAdmin,
174+
},
175+
users: {
176+
orgAdmin: orgTwoAdminUser,
177+
},
178+
},
179+
};
180+
};

0 commit comments

Comments
 (0)