Skip to content

chore(api): add memberIdOrLfid param support and user validation endpoints #3009

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
13 changes: 13 additions & 0 deletions backend/src/api/member/identity/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { safeWrap } from '@/middlewares/errorMiddleware'
import { memberIdOrLfidMiddleware } from '@/middlewares/memberIdOrLfidMiddleware'

export default (app) => {
// Member Identity List
Expand All @@ -15,4 +16,16 @@ export default (app) => {

// Member Identity Delete
app.delete(`/member/:memberId/identity/:id`, safeWrap(require('./memberIdentityDelete').default))

app.get(
`/member/:memberIdOrLfid/detected-identity`,
memberIdOrLfidMiddleware,
safeWrap(require('./memberIdentityDetectedList').default),
)

app.post(
`/member/:memberIdOrLfid/user-validation`,
memberIdOrLfidMiddleware,
safeWrap(require('./memberIdentityUserValidation').default),
)
}
2 changes: 1 addition & 1 deletion backend/src/api/member/identity/memberIdentityCreate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import PermissionChecker from '../../../services/user/permissionChecker'
* @response 429 - Too many requests
*/
export default async (req, res) => {
new PermissionChecker(req).validateHas(Permissions.values.memberEdit)
new PermissionChecker(req).validateHas(Permissions.values.memberIdentityCreate)

const memberIdentityService = new MemberIdentityService(req)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import PermissionChecker from '../../../services/user/permissionChecker'
* @response 429 - Too many requests
*/
export default async (req, res) => {
new PermissionChecker(req).validateHas(Permissions.values.memberEdit)
new PermissionChecker(req).validateHas(Permissions.values.memberIdentityCreate)

const memberIdentityService = new MemberIdentityService(req)

Expand Down
2 changes: 1 addition & 1 deletion backend/src/api/member/identity/memberIdentityDelete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import PermissionChecker from '../../../services/user/permissionChecker'
* @response 429 - Too many requests
*/
export default async (req, res) => {
new PermissionChecker(req).validateHas(Permissions.values.memberEdit)
new PermissionChecker(req).validateHas(Permissions.values.memberIdentityDestroy)

const memberIdentityService = new MemberIdentityService(req)

Expand Down
28 changes: 28 additions & 0 deletions backend/src/api/member/identity/memberIdentityDetectedList.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import MemberIdentityService from '@/services/member/memberIdentityService'

import Permissions from '../../../security/permissions'
import PermissionChecker from '../../../services/user/permissionChecker'

/**
* GET /member/:memberIdOrLfid/detected-identity
* @summary Query detected identities for a given LFID or memberID
* @tag Members
* @security Bearer
* @description Query detected identities for a given LFID or memberID
* @pathParam {string} memberIdOrLfid - member ID or LFID
* @response 200 - Ok
* @responseContent {MemberList} 200.application/json
* @responseExample {MemberList} 200.application/json.Member
* @response 401 - Unauthorized
* @response 404 - Not found
* @response 429 - Too many requests
*/
export default async (req, res) => {
new PermissionChecker(req).validateHas(Permissions.values.memberIdentityRead)

const memberIdentityService = new MemberIdentityService(req)

const payload = await memberIdentityService.detectedList(req.memberId)

await req.responseHandler.success(req, res, payload)
}
2 changes: 1 addition & 1 deletion backend/src/api/member/identity/memberIdentityList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import PermissionChecker from '../../../services/user/permissionChecker'
* @response 429 - Too many requests
*/
export default async (req, res) => {
new PermissionChecker(req).validateHas(Permissions.values.memberRead)
new PermissionChecker(req).validateHas(Permissions.values.memberIdentityRead)

const memberIdentityService = new MemberIdentityService(req)

Expand Down
2 changes: 1 addition & 1 deletion backend/src/api/member/identity/memberIdentityUpdate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import PermissionChecker from '../../../services/user/permissionChecker'
* @response 429 - Too many requests
*/
export default async (req, res) => {
new PermissionChecker(req).validateHas(Permissions.values.memberEdit)
new PermissionChecker(req).validateHas(Permissions.values.memberIdentityEdit)

const memberIdentityService = new MemberIdentityService(req)

Expand Down
27 changes: 27 additions & 0 deletions backend/src/api/member/identity/memberIdentityUserValidation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import MemberIdentityService from '@/services/member/memberIdentityService'

import Permissions from '../../../security/permissions'
import PermissionChecker from '../../../services/user/permissionChecker'

/**
* POST /member/:memberIdOrLfid/user-validation
* @summary Create user validation for member identity
* @tag Members
* @security Bearer
* @description Creates user validation when identity is accepted or rejected.
* @pathParam {string} memberIdOrLfid - member ID or LFID
* @response 200 - Ok
* @responseContent {MemberList} 200.application/json
* @responseExample {MemberList} 200.application/json.MemberIdentity
* @response 401 - Unauthorized
* @response 429 - Too many requests
*/
export default async (req, res) => {
new PermissionChecker(req).validateHas(Permissions.values.memberUserValidationCreate)

const memberIdentityService = new MemberIdentityService(req)

const payload = await memberIdentityService.userValidation(req.memberId, req.body)

await req.responseHandler.success(req, res, payload)
}
19 changes: 18 additions & 1 deletion backend/src/api/member/organization/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import { safeWrap } from '@/middlewares/errorMiddleware'
import { memberIdOrLfidMiddleware } from '@/middlewares/memberIdOrLfidMiddleware'

export default (app) => {
// Member Organiaztion List
app.get(`/member/:memberId/organization`, safeWrap(require('./memberOrganizationList').default))
app.get(
`/member/:memberIdOrLfid/organization`,
memberIdOrLfidMiddleware,
safeWrap(require('./memberOrganizationList').default),
)

// Member Organiaztion Create
app.post(
Expand All @@ -21,4 +26,16 @@ export default (app) => {
`/member/:memberId/organization/:id`,
safeWrap(require('./memberOrganizationDelete').default),
)

app.get(
`/member/:memberIdOrLfid/organization/status`,
memberIdOrLfidMiddleware,
safeWrap(require('./memberOrganizationStatus').default),
)

app.post(
`/member/:memberIdOrLfid/organization/user-validation`,
memberIdOrLfidMiddleware,
safeWrap(require('./memberOrganizationUserValidation').default),
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import PermissionChecker from '../../../services/user/permissionChecker'
* @response 429 - Too many requests
*/
export default async (req, res) => {
new PermissionChecker(req).validateHas(Permissions.values.memberEdit)
new PermissionChecker(req).validateHas(Permissions.values.memberOrganizationCreate)

const memberOrganizationsService = new MemberOrganizationsService(req)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import PermissionChecker from '../../../services/user/permissionChecker'
* @response 429 - Too many requests
*/
export default async (req, res) => {
new PermissionChecker(req).validateHas(Permissions.values.memberEdit)
new PermissionChecker(req).validateHas(Permissions.values.memberOrganizationDestroy)

const memberOrganizationsService = new MemberOrganizationsService(req)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import Permissions from '../../../security/permissions'
import PermissionChecker from '../../../services/user/permissionChecker'

/**
* GET /member/:memberId/organization
* GET /member/:memberIdOrLfid/organization
* @summary Query member organizations
* @tag Members
* @security Bearer
Expand All @@ -16,11 +16,11 @@ import PermissionChecker from '../../../services/user/permissionChecker'
* @response 429 - Too many requests
*/
export default async (req, res) => {
new PermissionChecker(req).validateHas(Permissions.values.memberRead)
new PermissionChecker(req).validateHas(Permissions.values.memberOrganizationRead)

const memberOrganizationsService = new MemberOrganizationsService(req)

const payload = await memberOrganizationsService.list(req.params.memberId)
const payload = await memberOrganizationsService.list(req.memberId)

await req.responseHandler.success(req, res, payload)
}
26 changes: 26 additions & 0 deletions backend/src/api/member/organization/memberOrganizationStatus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import MemberOrganizationsService from '@/services/member/memberOrganizationsService'

import Permissions from '../../../security/permissions'
import PermissionChecker from '../../../services/user/permissionChecker'

/**
* GET /member/:memberIdOrLfid/organization/status
* @summary Check if work history records exist
* @tag Members
* @security Bearer
* @description Returns { status: true } if work history exists, otherwise { status: false }
* @response 200 - Ok
* @responseContent {MemberList} 200.application/json
* @responseExample {MemberList} 200.application/json.Organization
* @response 401 - Unauthorized
* @response 429 - Too many requests
*/
export default async (req, res) => {
new PermissionChecker(req).validateHas(Permissions.values.memberOrganizationRead)

const memberOrganizationsService = new MemberOrganizationsService(req)

const status = await memberOrganizationsService.getStatus(req.memberId)

await req.responseHandler.success(req, res, { status })
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import PermissionChecker from '../../../services/user/permissionChecker'
* @response 429 - Too many requests
*/
export default async (req, res) => {
new PermissionChecker(req).validateHas(Permissions.values.memberEdit)
new PermissionChecker(req).validateHas(Permissions.values.memberOrganizationEdit)

const memberOrganizationsService = new MemberOrganizationsService(req)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import MemberOrganizationsService from '@/services/member/memberOrganizationsService'

import Permissions from '../../../security/permissions'
import PermissionChecker from '../../../services/user/permissionChecker'

/**
* POST /member/:memberIdOrLfid/organization/user-validation
* @summary Create user validation for member organization
* @tag Members
* @security Bearer
* @description Creates user validation when organization is created, updated or deleted.
* @pathParam {string} memberIdOrLfid - member ID or LFID
* @response 200 - Ok
* @responseContent {MemberList} 200.application/json
* @responseExample {MemberList} 200.application/json.Organization
* @response 401 - Unauthorized
* @response 429 - Too many requests
*/
export default async (req, res) => {
new PermissionChecker(req).validateHas(Permissions.values.memberUserValidationCreate)

const memberOrganizationsService = new MemberOrganizationsService(req)

const payload = await memberOrganizationsService.userValidation(req.memberId, req.body)

await req.responseHandler.success(req, res, payload)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
drop table if exists "memberUserValidations";
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
create table "memberUserValidations"(
id uuid primary key default uuid_generate_v4(),
"memberId" uuid not null references members(id),
action varchar(255) not null,
type varchar(255) not null,
details jsonb not null,
"createdAt" timestamp with time zone default now(),
"updatedAt" timestamp with time zone default now()
);
29 changes: 29 additions & 0 deletions backend/src/middlewares/memberIdOrLfidMiddleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { validateUUID } from '@crowd/common'
import { lfidToMemberId } from '@crowd/data-access-layer'

import SequelizeRepository from '@/database/repositories/sequelizeRepository'

/**
* Middleware to resolve `memberIdOrLfid` route param to a member UUID.
*
* - If a memberId (uuid), sets `req.memberId` and continues.
* - If an lfid (used by external services), resolves to memberId via db lookup.
* Used in routes that accept either type for member identification.
*/
export async function memberIdOrLfidMiddleware(req, res, next) {
const identifier = req.params.memberIdOrLfid
const qx = SequelizeRepository.getQueryExecutor({ database: req.database, ...req })

if (validateUUID(identifier)) {
req.memberId = identifier
return next()
}

const memberId = await lfidToMemberId(qx, identifier)
if (!memberId) {
return res.status(404).json({ error: 'Member not found for given lfid!' })
}

req.memberId = memberId
return next()
}
36 changes: 36 additions & 0 deletions backend/src/security/permissions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,42 @@ class Permissions {
id: 'memberAutocomplete',
allowedRoles: [roles.admin, roles.projectAdmin, roles.readonly],
},
memberIdentityCreate: {
id: 'memberIdentityCreate',
allowedRoles: [roles.admin, roles.projectAdmin],
},
memberIdentityEdit: {
id: 'memberIdentityEdit',
allowedRoles: [roles.admin, roles.projectAdmin],
},
memberIdentityDestroy: {
id: 'memberIdentityDestroy',
allowedRoles: [roles.admin, roles.projectAdmin],
},
memberIdentityRead: {
id: 'memberIdentityRead',
allowedRoles: [roles.admin, roles.projectAdmin, roles.readonly, roles.externalService],
},
memberOrganizationCreate: {
id: 'memberOrganizationCreate',
allowedRoles: [roles.admin, roles.projectAdmin],
},
memberOrganizationEdit: {
id: 'memberOrganizationEdit',
allowedRoles: [roles.admin, roles.projectAdmin],
},
memberOrganizationDestroy: {
id: 'memberOrganizationDestroy',
allowedRoles: [roles.admin, roles.projectAdmin],
},
memberOrganizationRead: {
id: 'memberOrganizationRead',
allowedRoles: [roles.admin, roles.projectAdmin, roles.readonly, roles.externalService],
},
memberUserValidationCreate: {
id: 'memberUserValidationCreate',
allowedRoles: [roles.externalService],
},
activityImport: {
id: 'activityImport',
allowedRoles: [roles.admin, roles.projectAdmin],
Expand Down
1 change: 1 addition & 0 deletions backend/src/security/roles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ class Roles {
admin: 'admin',
readonly: 'readonly',
projectAdmin: 'projectAdmin',
externalService: 'externalService',
}
}
}
Expand Down
Loading
Loading