Skip to content

Commit 17ff156

Browse files
authored
✨ implement user role (#759)
* 🗃️ create migration for Role table * 🗃️ Refactor Role table migration and seed data with UUID primary key * 🗃️ Add UserRole table with permissions for skillz-admins and world roles * ✨ Add role management components and update translations * 🔨 add script to update role in seed
1 parent fbc4b4e commit 17ff156

File tree

17 files changed

+409
-1
lines changed

17 files changed

+409
-1
lines changed

hasura/metadata/tables.yaml

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,42 @@
237237
- role: skillz-admins
238238
permission:
239239
filter: {}
240+
- table:
241+
name: Role
242+
schema: public
243+
insert_permissions:
244+
- role: skillz-admins
245+
permission:
246+
check: {}
247+
columns:
248+
- name
249+
- id
250+
select_permissions:
251+
- role: skillz-admins
252+
permission:
253+
columns:
254+
- name
255+
- id
256+
filter: {}
257+
- role: world
258+
permission:
259+
columns:
260+
- name
261+
- id
262+
filter: {}
263+
allow_aggregations: true
264+
update_permissions:
265+
- role: skillz-admins
266+
permission:
267+
columns:
268+
- name
269+
- id
270+
filter: {}
271+
check: {}
272+
delete_permissions:
273+
- role: skillz-admins
274+
permission:
275+
filter: {}
240276
- table:
241277
name: Skill
242278
schema: public
@@ -905,6 +941,72 @@
905941
- role: skillz-admins
906942
permission:
907943
filter: {}
944+
- table:
945+
name: UserRole
946+
schema: public
947+
insert_permissions:
948+
- role: skillz-admins
949+
permission:
950+
check: {}
951+
columns:
952+
- userEmail
953+
- created_at
954+
- roleId
955+
- role: world
956+
permission:
957+
check:
958+
userEmail:
959+
_eq: x-hasura-user-email
960+
columns:
961+
- userEmail
962+
- created_at
963+
- roleId
964+
select_permissions:
965+
- role: skillz-admins
966+
permission:
967+
columns:
968+
- userEmail
969+
- created_at
970+
- roleId
971+
filter: {}
972+
- role: world
973+
permission:
974+
columns:
975+
- userEmail
976+
- created_at
977+
- roleId
978+
filter: {}
979+
allow_aggregations: true
980+
update_permissions:
981+
- role: skillz-admins
982+
permission:
983+
columns:
984+
- userEmail
985+
- created_at
986+
- roleId
987+
filter: {}
988+
check: {}
989+
- role: world
990+
permission:
991+
columns:
992+
- userEmail
993+
- created_at
994+
- roleId
995+
filter:
996+
userEmail:
997+
_eq: x-hasura-user-email
998+
check:
999+
userEmail:
1000+
_eq: x-hasura-user-email
1001+
delete_permissions:
1002+
- role: skillz-admins
1003+
permission:
1004+
filter: {}
1005+
- role: world
1006+
permission:
1007+
filter:
1008+
userEmail:
1009+
_eq: x-hasura-user-email
9081010
- table:
9091011
name: UserSkillDesire
9101012
schema: public
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
DROP TABLE "public"."Role";
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
CREATE TABLE "public"."Role" ("id" uuid NOT NULL DEFAULT gen_random_uuid(), "name" text NOT NULL, PRIMARY KEY ("id") , UNIQUE ("id"), UNIQUE ("name"));COMMENT ON TABLE "public"."Role" IS E'Role for a user';
2+
CREATE EXTENSION IF NOT EXISTS pgcrypto;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
DROP TABLE "public"."UserRole";
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
CREATE TABLE "public"."UserRole" ("userEmail" text NOT NULL, "roleId" uuid NOT NULL, "created_at" timestamptz NOT NULL DEFAULT now(), PRIMARY KEY ("userEmail","roleId") , FOREIGN KEY ("userEmail") REFERENCES "public"."User"("email") ON UPDATE restrict ON DELETE cascade, FOREIGN KEY ("roleId") REFERENCES "public"."Role"("id") ON UPDATE restrict ON DELETE cascade, UNIQUE ("userEmail", "roleId"));COMMENT ON TABLE "public"."UserRole" IS E'Relations between user and role';

hasura/seeds/10-role.sql

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
INSERT INTO "public"."Role" ("name") VALUES
2+
('Developer / Engineer'),
3+
('Technical Lead'),
4+
('Solution Architect'),
5+
('Technical Expert / Specialist'),
6+
('Product Owner'),
7+
('Product Manager'),
8+
('UX Designer'),
9+
('Engineering Manager'),
10+
('Delivery Manager'),
11+
('Coach (team, organisation)'),
12+
('Consultant')
13+
ON CONFLICT ("name") DO NOTHING;

i18n/en.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export default {
1111
noData: 'No data available.',
1212
requiredField: 'This field is required',
1313
tagRequired: 'Add at least 1 tag',
14+
roleRequired: 'Add at least 1 role',
1415
topicRequired: 'Add at least 1 topic',
1516
duplicatedTag: "This tag already exists, you can't create it",
1617
},
@@ -240,6 +241,7 @@ export default {
240241
agency: 'Agency',
241242
contact: 'Preferred method of contact',
242243
topics: 'Preferred topics',
244+
roles: 'Roles',
243245
certifications: 'Certifications',
244246
selectPlaceholder: 'Agency',
245247
validFrom: 'valid from',

i18n/fr.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export default {
1111
noData: 'Aucune donnée disponible.',
1212
requiredField: 'Ce champs est obligatoire',
1313
tagRequired: 'Ajoutez au minimum 1 tag',
14+
roleRequired: 'Ajoutez au minimum 1 rôle',
1415
topicRequired: 'Ajoutez au minimum 1 sujet',
1516
duplicatedTag: 'Ce tag existe déjà, vous ne pouvez pas le créer',
1617
},
@@ -246,6 +247,7 @@ export default {
246247
agency: 'Agence',
247248
contact: 'Méthode de contact préférée',
248249
topics: 'Sujets préférés',
250+
roles: 'Rôles',
249251
certifications: 'Certifications',
250252
selectPlaceholder: 'Agence',
251253
validFrom: 'valide depuis',
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import fetch from 'node-fetch'
2+
import fs from 'fs'
3+
4+
/*
5+
* ENVIRONMENT CHECK
6+
*/
7+
if (!process.env.NEXT_PUBLIC_BASE_URL) {
8+
throw new Error(
9+
"ERROR: App couldn't start because NEXT_PUBLIC_BASE_URL isn't defined"
10+
)
11+
}
12+
13+
if (!process.env.NEXT_API_BEARER_TOKEN) {
14+
throw new Error(
15+
"ERROR: App couldn't start because NEXT_API_BEARER_TOKEN isn't defined"
16+
)
17+
}
18+
19+
/*
20+
* GET ALL ROLES FROM API
21+
*/
22+
const response = await fetch(`${process.env.NEXT_PUBLIC_BASE_URL}/api/roles`, {
23+
method: 'GET',
24+
headers: {
25+
Authorization: process.env.NEXT_API_BEARER_TOKEN,
26+
},
27+
})
28+
29+
const { roles: rolesData } = await response.json()
30+
31+
/*
32+
* FORMAT DATA
33+
*/
34+
const roles = rolesData.map((role) => `('${role.name}')`)
35+
36+
/*
37+
* WRITE DATA TO FILE
38+
*/
39+
let writer = fs.createWriteStream('../hasura/seeds/10-roles.sql', {
40+
flags: 'w',
41+
})
42+
43+
writer.write('INSERT INTO "public"."Role" ("name") VALUES\n')
44+
45+
writer.write(roles.join(',\n'))
46+
47+
writer.write('\n ON CONFLICT ("name") DO NOTHING;')
48+
49+
writer.close()
50+
51+
console.log(`Referential Roles successfully updated (${roles.length} roles).`)

src/components/atoms/Role.tsx

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { RoleItem } from '../../utils/types'
2+
3+
type RoleType = 'common' | 'selected'
4+
5+
type RoleProps = {
6+
type: RoleType
7+
role: RoleItem
8+
keyId: string | number
9+
readOnly?: boolean
10+
callback?: (role: RoleItem) => void
11+
}
12+
13+
export const roleTypeClasses: Record<RoleType, string> = {
14+
common: 'font-bold gradient-red-faded text-light-ultrawhite hover:shadow-xl hover:shadow-light-graybutton hover:dark:shadow-lg hover:dark:shadow-dark-radargrid',
15+
selected:
16+
'text-light-dark text-light-ultrawhite gradient-red hover:drop-shadow-xl hover:dark:shadow-lg hover:dark:shadow-dark-radargrid',
17+
}
18+
19+
export const roleClasses = {
20+
base: 'text-base font-bold py-1 px-5 rounded-full',
21+
disabled: 'disabled:pointer-events-none',
22+
variant: roleTypeClasses,
23+
}
24+
25+
const Role = ({ type, role, keyId, callback, readOnly = false }: RoleProps) => {
26+
return (
27+
<div className="flex-initial py-2" key={`role-${keyId}`}>
28+
<button
29+
className={`${roleClasses.base} ${roleClasses.disabled} ${roleClasses.variant[type]}`}
30+
disabled={readOnly}
31+
onClick={() => callback(role)}
32+
>
33+
<p className="text-sm">{role.name}</p>
34+
</button>
35+
</div>
36+
)
37+
}
38+
39+
export default Role

0 commit comments

Comments
 (0)