@yangsaiyam/helper 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +94 -0
- package/package.json +9 -0
- package/src/career/index.ts +39 -0
- package/src/contact-form/index.ts +15 -0
- package/src/files/index.ts +11 -0
- package/src/index.ts +6 -0
- package/src/roles/index.test.ts +59 -0
- package/src/roles/index.ts +144 -0
- package/src/shared/helpers.ts +34 -0
- package/src/shared/types.ts +5 -0
- package/tsconfig.json +11 -0
package/README.md
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# korre-mapping
|
|
2
|
+
|
|
3
|
+
Shared key-value mappings for Korre projects.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
SSH:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm add git+ssh://git@github.com:your-org/korre-mapping.git
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
HTTPS:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pnpm add github:your-org/korre-mapping
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Use
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
import { CAREER_LOCATION, getLabelByKey } from "@korre/mapping";
|
|
23
|
+
|
|
24
|
+
const label = getLabelByKey(CAREER_LOCATION, 1);
|
|
25
|
+
// KualaLumpur
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
```tsx
|
|
29
|
+
import { CAREER_JOB_TYPE, getOptionByKey } from "@korre/mapping";
|
|
30
|
+
|
|
31
|
+
type CareerCardProps = {
|
|
32
|
+
career: {
|
|
33
|
+
title: string;
|
|
34
|
+
jobType: number;
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export default function CareerCard({ career }: CareerCardProps) {
|
|
39
|
+
const jobType = getOptionByKey(CAREER_JOB_TYPE, career.jobType);
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<div>
|
|
43
|
+
<h2>{career.title}</h2>
|
|
44
|
+
<p>{jobType?.value ?? "Unknown"}</p>
|
|
45
|
+
</div>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Structure
|
|
51
|
+
|
|
52
|
+
```text
|
|
53
|
+
src/
|
|
54
|
+
├─ career/
|
|
55
|
+
│ └─ index.ts
|
|
56
|
+
├─ contact-form/
|
|
57
|
+
│ └─ index.ts
|
|
58
|
+
├─ files/
|
|
59
|
+
│ └─ index.ts
|
|
60
|
+
├─ shared/
|
|
61
|
+
│ ├─ helpers.ts
|
|
62
|
+
│ └─ types.ts
|
|
63
|
+
└─ index.ts
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Exports
|
|
67
|
+
|
|
68
|
+
- `CAREER_JOB_TYPE`
|
|
69
|
+
- `CAREER_CATEGORY`
|
|
70
|
+
- `CAREER_MODE`
|
|
71
|
+
- `CAREER_LOCATION`
|
|
72
|
+
- `CONTACT_FORM_ENQUIRY_TYPE`
|
|
73
|
+
- `FILE_STATUS`
|
|
74
|
+
- `FILE_ORIGIN`
|
|
75
|
+
- `getOptionByKey`
|
|
76
|
+
- `getLabelByKey`
|
|
77
|
+
- `getI18nKeyByKey`
|
|
78
|
+
- `toRecord`
|
|
79
|
+
|
|
80
|
+
## Update mappings
|
|
81
|
+
|
|
82
|
+
Update `src/index.ts`, then commit and push:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
git add .
|
|
86
|
+
git commit -m "update mappings"
|
|
87
|
+
git push
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
In consuming repos:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
pnpm update @korre/mapping
|
|
94
|
+
```
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { MappingOption } from "../shared/types";
|
|
2
|
+
|
|
3
|
+
export const CAREER_JOB_TYPE = [
|
|
4
|
+
{ key: 1, value: "Full-time", i18nKey: "career.jobType.1" },
|
|
5
|
+
{ key: 2, value: "Part-time", i18nKey: "career.jobType.2" },
|
|
6
|
+
{ key: 3, value: "Contract", i18nKey: "career.jobType.3" },
|
|
7
|
+
{ key: 4, value: "Internship", i18nKey: "career.jobType.4" },
|
|
8
|
+
] as const satisfies readonly MappingOption[];
|
|
9
|
+
|
|
10
|
+
export const CAREER_CATEGORY = [
|
|
11
|
+
{ key: 1, value: "Engineering", i18nKey: "career.category.1" },
|
|
12
|
+
{ key: 2, value: "Design", i18nKey: "career.category.2" },
|
|
13
|
+
{ key: 3, value: "Product", i18nKey: "career.category.3" },
|
|
14
|
+
{ key: 4, value: "Marketing", i18nKey: "career.category.4" },
|
|
15
|
+
] as const satisfies readonly MappingOption[];
|
|
16
|
+
|
|
17
|
+
export const CAREER_MODE = [
|
|
18
|
+
{ key: 1, value: "On-site", i18nKey: "career.mode.1" },
|
|
19
|
+
{ key: 2, value: "Remote", i18nKey: "career.mode.2" },
|
|
20
|
+
{ key: 3, value: "Hybrid", i18nKey: "career.mode.3" },
|
|
21
|
+
] as const satisfies readonly MappingOption[];
|
|
22
|
+
|
|
23
|
+
export const CAREER_LOCATION = [
|
|
24
|
+
{ key: 1, value: "KualaLumpur", i18nKey: "career.location.1" },
|
|
25
|
+
{ key: 2, value: "Selangor", i18nKey: "career.location.2" },
|
|
26
|
+
{ key: 3, value: "Johor", i18nKey: "career.location.3" },
|
|
27
|
+
{ key: 4, value: "Penang", i18nKey: "career.location.4" },
|
|
28
|
+
{ key: 5, value: "Sabah", i18nKey: "career.location.5" },
|
|
29
|
+
{ key: 6, value: "Sarawak", i18nKey: "career.location.6" },
|
|
30
|
+
{ key: 7, value: "NegeriSembilan", i18nKey: "career.location.7" },
|
|
31
|
+
{ key: 8, value: "Kedah", i18nKey: "career.location.8" },
|
|
32
|
+
{ key: 9, value: "Kelantan", i18nKey: "career.location.9" },
|
|
33
|
+
{ key: 10, value: "Perak", i18nKey: "career.location.10" },
|
|
34
|
+
{ key: 11, value: "Terengganu", i18nKey: "career.location.11" },
|
|
35
|
+
{ key: 12, value: "Perlis", i18nKey: "career.location.12" },
|
|
36
|
+
{ key: 13, value: "Melaka", i18nKey: "career.location.13" },
|
|
37
|
+
{ key: 14, value: "Putrajaya", i18nKey: "career.location.14" },
|
|
38
|
+
{ key: 15, value: "Labuan", i18nKey: "career.location.15" },
|
|
39
|
+
] as const satisfies readonly MappingOption[];
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { MappingOption } from "../shared/types";
|
|
2
|
+
|
|
3
|
+
export const CONTACT_FORM_ENQUIRY_TYPE = [
|
|
4
|
+
{
|
|
5
|
+
key: 1,
|
|
6
|
+
value: "Software Development",
|
|
7
|
+
i18nKey: "contactForm.enquiryType.1",
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
key: 2,
|
|
11
|
+
value: "Advertising Platform",
|
|
12
|
+
i18nKey: "contactForm.enquiryType.2",
|
|
13
|
+
},
|
|
14
|
+
{ key: 3, value: "MCN Business", i18nKey: "contactForm.enquiryType.3" },
|
|
15
|
+
] as const satisfies readonly MappingOption[];
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { MappingOption } from "../shared/types";
|
|
2
|
+
|
|
3
|
+
export const FILE_STATUS = [
|
|
4
|
+
{ key: 1, value: "pending", i18nKey: "files.status.1" },
|
|
5
|
+
{ key: 2, value: "submitted", i18nKey: "files.status.2" },
|
|
6
|
+
] as const satisfies readonly MappingOption[];
|
|
7
|
+
|
|
8
|
+
export const FILE_ORIGIN = [
|
|
9
|
+
{ key: 1, value: "job applications", i18nKey: "files.origin.1" },
|
|
10
|
+
{ key: 2, value: "submitted", i18nKey: "files.origin.2" },
|
|
11
|
+
] as const satisfies readonly MappingOption[];
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
PERMISSION_I18N_KEY,
|
|
5
|
+
Permission,
|
|
6
|
+
ROLE,
|
|
7
|
+
ROLE_PERMISSIONS,
|
|
8
|
+
hasPermission,
|
|
9
|
+
normalizeRole,
|
|
10
|
+
permissionLabelKey,
|
|
11
|
+
roleLabelKey,
|
|
12
|
+
roleToId,
|
|
13
|
+
} from "./index.ts";
|
|
14
|
+
|
|
15
|
+
assert.equal(normalizeRole(0), "super-admin");
|
|
16
|
+
assert.equal(normalizeRole("admin"), "admin");
|
|
17
|
+
assert.equal(normalizeRole(2), "staff");
|
|
18
|
+
assert.equal(normalizeRole("hr"), "hr");
|
|
19
|
+
assert.equal(normalizeRole("unknown"), undefined);
|
|
20
|
+
assert.equal(normalizeRole(undefined), undefined);
|
|
21
|
+
|
|
22
|
+
assert.equal(roleToId("super-admin"), 0);
|
|
23
|
+
assert.equal(roleToId("admin"), 1);
|
|
24
|
+
assert.equal(roleToId("staff"), 2);
|
|
25
|
+
assert.equal(roleToId("hr"), 3);
|
|
26
|
+
assert.equal(roleToId("unknown"), undefined);
|
|
27
|
+
|
|
28
|
+
assert.equal(roleLabelKey(0), "users.roleSuperAdmin");
|
|
29
|
+
assert.equal(roleLabelKey("admin"), "users.roleAdmin");
|
|
30
|
+
assert.equal(roleLabelKey(2), "users.roleStaff");
|
|
31
|
+
assert.equal(roleLabelKey("hr"), "users.roleHr");
|
|
32
|
+
assert.equal(roleLabelKey(undefined), "users.roleStaff");
|
|
33
|
+
|
|
34
|
+
assert.deepEqual(ROLE[0], {
|
|
35
|
+
key: 0,
|
|
36
|
+
value: "super-admin",
|
|
37
|
+
i18nKey: "users.roleSuperAdmin",
|
|
38
|
+
});
|
|
39
|
+
assert.equal(ROLE[3]?.i18nKey, "users.roleHr");
|
|
40
|
+
|
|
41
|
+
assert.equal(
|
|
42
|
+
PERMISSION_I18N_KEY[Permission.ALLOWED_EMAILS_READ],
|
|
43
|
+
"permissions.allowedEmailsRead",
|
|
44
|
+
);
|
|
45
|
+
assert.equal(
|
|
46
|
+
permissionLabelKey(Permission.CONTACT_FORM_DELETE),
|
|
47
|
+
"permissions.contactFormDelete",
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
assert.ok(ROLE_PERMISSIONS["super-admin"].includes(Permission.USERS_DELETE));
|
|
51
|
+
assert.ok(ROLE_PERMISSIONS.admin.includes(Permission.CONTACT_FORM_DELETE));
|
|
52
|
+
assert.ok(ROLE_PERMISSIONS.hr.includes(Permission.JOB_APPLICATIONS_READ));
|
|
53
|
+
assert.deepEqual(ROLE_PERMISSIONS.staff, [Permission.CAREERS_READ]);
|
|
54
|
+
|
|
55
|
+
assert.equal(hasPermission("super-admin", Permission.USERS_WRITE), true);
|
|
56
|
+
assert.equal(hasPermission(1, Permission.USERS_WRITE), false);
|
|
57
|
+
assert.equal(hasPermission("hr", Permission.CAREERS_WRITE), true);
|
|
58
|
+
assert.equal(hasPermission("staff", Permission.CAREERS_WRITE), false);
|
|
59
|
+
assert.equal(hasPermission(undefined, Permission.CAREERS_READ), false);
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import type { MappingOption } from "../shared/types";
|
|
2
|
+
|
|
3
|
+
export type CanonicalRole = "super-admin" | "admin" | "hr" | "staff";
|
|
4
|
+
export type RoleValue = CanonicalRole | number;
|
|
5
|
+
export type RoleInput = RoleValue | string;
|
|
6
|
+
|
|
7
|
+
export const ROLE = [
|
|
8
|
+
{ key: 0, value: "super-admin", i18nKey: "users.roleSuperAdmin" },
|
|
9
|
+
{ key: 1, value: "admin", i18nKey: "users.roleAdmin" },
|
|
10
|
+
{ key: 2, value: "staff", i18nKey: "users.roleStaff" },
|
|
11
|
+
{ key: 3, value: "hr", i18nKey: "users.roleHr" },
|
|
12
|
+
] as const satisfies readonly MappingOption[];
|
|
13
|
+
|
|
14
|
+
export function normalizeRole(
|
|
15
|
+
role: RoleInput | undefined,
|
|
16
|
+
): CanonicalRole | undefined {
|
|
17
|
+
switch (role) {
|
|
18
|
+
case 0:
|
|
19
|
+
case "super-admin":
|
|
20
|
+
return "super-admin";
|
|
21
|
+
case 1:
|
|
22
|
+
case "admin":
|
|
23
|
+
return "admin";
|
|
24
|
+
case 2:
|
|
25
|
+
case "staff":
|
|
26
|
+
return "staff";
|
|
27
|
+
case 3:
|
|
28
|
+
case "hr":
|
|
29
|
+
return "hr";
|
|
30
|
+
default:
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function roleLabelKey(role: RoleInput | undefined): string {
|
|
36
|
+
switch (normalizeRole(role)) {
|
|
37
|
+
case "super-admin":
|
|
38
|
+
return "users.roleSuperAdmin";
|
|
39
|
+
case "admin":
|
|
40
|
+
return "users.roleAdmin";
|
|
41
|
+
case "hr":
|
|
42
|
+
return "users.roleHr";
|
|
43
|
+
case "staff":
|
|
44
|
+
return "users.roleStaff";
|
|
45
|
+
default:
|
|
46
|
+
return "users.roleStaff";
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function roleToId(role: RoleInput | undefined): number | undefined {
|
|
51
|
+
switch (normalizeRole(role)) {
|
|
52
|
+
case "super-admin":
|
|
53
|
+
return 0;
|
|
54
|
+
case "admin":
|
|
55
|
+
return 1;
|
|
56
|
+
case "staff":
|
|
57
|
+
return 2;
|
|
58
|
+
case "hr":
|
|
59
|
+
return 3;
|
|
60
|
+
default:
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export const Permission = {
|
|
66
|
+
ALLOWED_EMAILS_READ: "allowed-emails:read",
|
|
67
|
+
ALLOWED_EMAILS_WRITE: "allowed-emails:write",
|
|
68
|
+
USERS_READ: "users:read",
|
|
69
|
+
USERS_WRITE: "users:write",
|
|
70
|
+
USERS_DELETE: "users:delete",
|
|
71
|
+
CAREERS_READ: "careers:read",
|
|
72
|
+
CAREERS_WRITE: "careers:write",
|
|
73
|
+
CAREERS_DELETE: "careers:delete",
|
|
74
|
+
CONTACT_FORM_READ: "contact-form:read",
|
|
75
|
+
CONTACT_FORM_DELETE: "contact-form:delete",
|
|
76
|
+
JOB_APPLICATIONS_READ: "job-applications:read",
|
|
77
|
+
} as const;
|
|
78
|
+
|
|
79
|
+
export type Permission = (typeof Permission)[keyof typeof Permission];
|
|
80
|
+
|
|
81
|
+
export const PERMISSION_I18N_KEY: Record<Permission, string> = {
|
|
82
|
+
[Permission.ALLOWED_EMAILS_READ]: "permissions.allowedEmailsRead",
|
|
83
|
+
[Permission.ALLOWED_EMAILS_WRITE]: "permissions.allowedEmailsWrite",
|
|
84
|
+
[Permission.USERS_READ]: "permissions.usersRead",
|
|
85
|
+
[Permission.USERS_WRITE]: "permissions.usersWrite",
|
|
86
|
+
[Permission.USERS_DELETE]: "permissions.usersDelete",
|
|
87
|
+
[Permission.CAREERS_READ]: "permissions.careersRead",
|
|
88
|
+
[Permission.CAREERS_WRITE]: "permissions.careersWrite",
|
|
89
|
+
[Permission.CAREERS_DELETE]: "permissions.careersDelete",
|
|
90
|
+
[Permission.CONTACT_FORM_READ]: "permissions.contactFormRead",
|
|
91
|
+
[Permission.CONTACT_FORM_DELETE]: "permissions.contactFormDelete",
|
|
92
|
+
[Permission.JOB_APPLICATIONS_READ]: "permissions.jobApplicationsRead",
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export const ROLE_PERMISSIONS: Record<CanonicalRole, Permission[]> = {
|
|
96
|
+
"super-admin": [
|
|
97
|
+
Permission.ALLOWED_EMAILS_READ,
|
|
98
|
+
Permission.ALLOWED_EMAILS_WRITE,
|
|
99
|
+
Permission.USERS_READ,
|
|
100
|
+
Permission.USERS_WRITE,
|
|
101
|
+
Permission.USERS_DELETE,
|
|
102
|
+
Permission.CAREERS_READ,
|
|
103
|
+
Permission.CAREERS_WRITE,
|
|
104
|
+
Permission.CAREERS_DELETE,
|
|
105
|
+
Permission.CONTACT_FORM_READ,
|
|
106
|
+
Permission.CONTACT_FORM_DELETE,
|
|
107
|
+
Permission.JOB_APPLICATIONS_READ,
|
|
108
|
+
],
|
|
109
|
+
admin: [
|
|
110
|
+
Permission.ALLOWED_EMAILS_READ,
|
|
111
|
+
Permission.ALLOWED_EMAILS_WRITE,
|
|
112
|
+
Permission.USERS_READ,
|
|
113
|
+
Permission.CAREERS_READ,
|
|
114
|
+
Permission.CAREERS_WRITE,
|
|
115
|
+
Permission.CAREERS_DELETE,
|
|
116
|
+
Permission.CONTACT_FORM_READ,
|
|
117
|
+
Permission.CONTACT_FORM_DELETE,
|
|
118
|
+
Permission.JOB_APPLICATIONS_READ,
|
|
119
|
+
],
|
|
120
|
+
hr: [
|
|
121
|
+
Permission.CAREERS_READ,
|
|
122
|
+
Permission.CAREERS_WRITE,
|
|
123
|
+
Permission.CAREERS_DELETE,
|
|
124
|
+
Permission.JOB_APPLICATIONS_READ,
|
|
125
|
+
],
|
|
126
|
+
staff: [Permission.CAREERS_READ],
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
export function hasPermission(
|
|
130
|
+
role: RoleInput | undefined,
|
|
131
|
+
permission: Permission,
|
|
132
|
+
): boolean {
|
|
133
|
+
const normalizedRole = normalizeRole(role);
|
|
134
|
+
|
|
135
|
+
if (!normalizedRole) {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return ROLE_PERMISSIONS[normalizedRole].includes(permission);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function permissionLabelKey(permission: Permission): string {
|
|
143
|
+
return PERMISSION_I18N_KEY[permission];
|
|
144
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { MappingOption } from "./types";
|
|
2
|
+
|
|
3
|
+
export function getOptionByKey<T extends MappingOption>(
|
|
4
|
+
options: readonly T[],
|
|
5
|
+
key?: number | null,
|
|
6
|
+
): T | undefined {
|
|
7
|
+
if (key === null || key === undefined) {
|
|
8
|
+
return undefined;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
return options.find((option) => option.key === key);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function getLabelByKey<T extends MappingOption>(
|
|
15
|
+
options: readonly T[],
|
|
16
|
+
key?: number | null,
|
|
17
|
+
fallback = "Unknown",
|
|
18
|
+
): string {
|
|
19
|
+
return getOptionByKey(options, key)?.value ?? fallback;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function getI18nKeyByKey<T extends MappingOption>(
|
|
23
|
+
options: readonly T[],
|
|
24
|
+
key?: number | null,
|
|
25
|
+
fallback = "unknown",
|
|
26
|
+
): string {
|
|
27
|
+
return getOptionByKey(options, key)?.i18nKey ?? fallback;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function toRecord<T extends MappingOption>(
|
|
31
|
+
options: readonly T[],
|
|
32
|
+
): Record<number, string> {
|
|
33
|
+
return Object.fromEntries(options.map((option) => [option.key, option.value]));
|
|
34
|
+
}
|