𪪠ããªããŒã¿ãŒ
ããªããŒã¿ãŒã¯ withRoute({ validators }) ãéããŠã«ãŒãã«æ¥ç¶ãã颿°ã§ããHTTP ããªããŒã¿ãŒã¯éåæã§ããWebSocket ããªããŒã¿ãŒã¯åæãŸãã¯éåæã«ã§ããŸããã¹ããŒãæ€èšŒãè¶
ããããžãã¹ããžãã¯ã®æ€æ»ãããªãã¡èªèšŒãèªå¯ããªãœãŒã¹ã®ååšãæææš©ãå®è¡ããŸãã
ããªããŒã¿ãŒç»é² API ã¯ãããŸãããvalidators åå空éããã€ã³ããŒãããsrc/routes/ ã®ã«ãŒãã³ã³ããŒã¶ãŒã«æ¥ç¶ããŸãã
import { validators } from '@nodeblocks/backend-sdk';
type Validator = (payload: RouteHandlerPayload) => Promise<void>;
type WsValidator = (payload: WsRouteHandlerPayload) => void | Promise<void>;
ãã¡ã¯ããªãŒããªããŒã¿ãŒã¯ Validator ãçæããããã«åŒã³åºãå¿
èŠããããŸãïŒäŸ: isAuthenticated()ãisAuthenticated ã®ãŸãŸã§ã¯ãããŸããïŒãdoesCategoryExist ã®ãããªçŽæ¥ããªããŒã¿ãŒã¯ãã®ãŸãŸäœ¿çšããŸãã() ã§åŒã³åºããªãã§ãã ããã
ð ããªããŒã¿ãŒãšã¯ïŒâ
ããªããŒã¿ãŒã¯ããªã¯ãšã¹ããã€ãã©ã€ã³å
ã§ãã³ãã©ãŒã®ããžãã¹ããžãã¯ããåã«å®è¡ãããŸããã«ãŒããã³ãã©ãŒãšåããã€ããŒããªããžã§ã¯ããåãåããŸãïŒHTTP 㯠RouteHandlerPayloadãWebSocket 㯠WsRouteHandlerPayloadïŒã
ããªããŒã¿ãŒã䜿ãçç±:
- DB è² è·ã®é«ããã³ãã©ãŒãã§ãŒã³ã®åã«ãèªèšŒãšã¢ã¯ã»ã¹å¶åŸ¡ã匷å¶ãã
- SDK ã®è¿°èªãã«ãŒãéã§åå©çšããïŒ
hasOrgRoleãownsProfileãªã©ïŒ - æè»ãªã¢ã¯ã»ã¹èŠåã®ããã«
all()ïŒANDïŒãŸãã¯some()ïŒORïŒã§åæãã - äžè²«ãã HTTP ãšã©ãŒã¬ã¹ãã³ã¹ã®ããã«
NodeblocksErrorãã¹ããŒããïŒWebSocket ã®å€±ææã¯æ¥ç¶ãéããŸãïŒ
ð ã¹ããŒããšããªããŒã¿ãŒâ
2 ã€ã®æ€èšŒã¬ã€ã€ãŒã¯ç°ãªãæç¹ã§å®è¡ãããŸããwithSchema ã«ããåœ¢ç¶æ€èšŒã«ã€ããŠã¯ã¹ããŒã »ãåç
§ããŠãã ããã
HTTP ãªã¯ãšã¹ã
â ã«ãŒãããªããŒã¿ãŒïŒdefServiceïŒ
â ãã³ãã©ãŒéå§
â ã¹ããŒãæ€èšŒïŒwithNextRoute ã©ãããŒïŒ
â ãã³ãã©ãŒãã§ãŒã³ïŒblocks / handlersïŒ
| ã¬ã€ã€ãŒ | ç®ç | å®çŸ©å Žæ |
|---|---|---|
| ã«ãŒãããªããŒã¿ãŒ | ããžãã¹ããžã㯠â èªèšŒãæš©éãååš | withRoute ã® validators |
| ã¹ããŒãæ€èšŒ | ãªã¯ãšã¹ãåœ¢ç¶ â ãã¹ãã¯ãšãªãæ¬æãã£ãŒã«ã | æ©èœã® withSchemaïŒcompose(schema, route)ïŒ |
å®è¡é åº: defService ã¯ãŸãã«ãŒãããªããŒã¿ãŒãå®è¡ãããã®åŸãã³ãã©ãŒãåŒã³åºããŸããã¹ããŒãæ€èšŒã¯ withNextRoute çµç±ã§ã¹ããŒãã«ã©ããããããã³ãã©ãŒå
éšã§å®è¡ãããŸããæ£ããé åºã¯ ããªããŒã¿ãŒ â ã¹ããŒã â ãã³ãã©ãŒãã§ãŒã³ ã§ãã
âïž ã³ã³ããã¹ããšãã¹â
ããªããŒã¿ãŒã¯ RouteHandlerPayload ãåãåããŸãã
{
context: {
db, // service dataStores ãããããã³ã°
configuration, // ãµãŒãã¹æ§æïŒIdentity ã¿ã€ã IDãçµç¹ããŒã«ïŒ
authenticate, // ããã©ã«ã㯠getBearerTokenInfoãCookie ãµãŒãã¹ã¯ getCookieTokenInfo ãæ³šå
¥
// ...ä»»æã®ãã©ã€ããŒïŒmailServiceãfileStorageDriver ãªã©ïŒ
},
params: {
requestParams, // ãã¹ãã©ã¡ãŒã¿ãŒ
requestQuery, // ã¯ãšãªæåå
requestBody, // JSON æ¬æïŒPOST/PUT/PATCH/DELETEïŒ
},
}
ãã¹åŒæ°ã«ã¯ããã€ããŒããèµ·ç¹ãšãã Ramda ã® path ã¿ãã«ã䜿çšããŸããSDK ã«ãŒãã§ã¯äžè²«ããŠæ¬¡ã䜿çšããŸãã
['params', 'requestParams', 'profileId']
['params', 'requestBody', 'identityId']
['params', 'requestQuery', 'channelId']
Cookie èªèšŒã䜿çšããå ŽåããµãŒãã¹ã¯ context.authenticate ãšã㊠getCookieTokenInfo ãæ³šå
¥ããŸãããµãŒãã¹ã«ãŒã¿ãŒãããŠã³ãããåã« cookie-parser ããã«ãŠã§ã¢ãç»é²ããŠãã ããã
æ§æã®äŸåé¢ä¿â
äžéšã®ããªããŒã¿ãŒã¯ãµãŒãã¹æ§æãå¿ èŠãšããŸãã
| ããªããŒã¿ãŒ | å¿ é æ§æ |
|---|---|
checkIdentityType | configuration.identity.typeIdsãdb.identities |
hasOrgRoleãhasOrgRoleSameOrAboveãhasOrgRoleAssignmentPermission | configuration.organization.rolesãdb.organizations |
hasOrgOwnerRemainingAfterMembersUpsertãhasOrgOwnerRemainingAfterMemberRemoval | configuration.organization.rolesãdb.organizations |
ð åæâ
| ãšã¯ã¹ããŒã | åäœ |
|---|---|
all(...validators) | 鿬¡ AND â ããªããŒã¿ãŒã 1 ã€ãã€å®è¡ããæåã®ã¹ããŒã§å³åº§ã«å€±æ |
some(...validators) | äžŠè¡ OR â Promise.allSettled ã§ãã¹ãŠã®ããªããŒã¿ãŒãå®è¡ããããããæåããã°ééãããã¹ãŠå€±æããå Žåã¯å
¥åé ã§æåã«åéããããšã©ãŒãã¹ã㌠|
å€ãã® SDK ã«ãŒãã¯ã管çè
ãŸãã¯çµç¹ã¡ã³ããŒã®ã¢ã¯ã»ã¹ã« some(checkIdentityType(['admin']), hasOrgRole(...)) ã䜿çšããŸãã
import { validators } from '@nodeblocks/backend-sdk';
const { isAuthenticated, some, checkIdentityType, hasOrgRole } = validators;
validators: [
isAuthenticated(),
some(
checkIdentityType(['admin']),
hasOrgRole(['owner', 'admin'], ['params', 'requestParams', 'organizationId'])
),
],
ðŠ SDK ããªããŒã¿ãŒã«ã¿ãã°â
ãã¹ãŠã®ããªããŒã¿ãŒã¯ src/validators/index.ts ã«å¯Ÿå¿ãã validators åå空éãããšã¯ã¹ããŒããããŸãã
åæâ
| ãšã¯ã¹ããŒã | ã·ã°ãã㣠|
|---|---|
all | (...validators: Validator[]) => Validator |
some | (...validators: Validator[]) => Validator |
èªèšŒãš Identityâ
| ãšã¯ã¹ããŒã | ã·ã°ãã㣠| æ³šèš |
|---|---|---|
isAuthenticated | () => Validator | context.authenticate ãåŒã³åºããŸããããã©ã«ã㯠getBearerTokenInfo |
checkIdentityType | (allowedTypes) => Validator | èªèšŒãå®è¡ãdb.identities ãš configuration.identity.typeIds ãå¿
èŠ |
Organizationâ
| ãšã¯ã¹ããŒã | ã·ã°ãã㣠| æ³šèš |
|---|---|---|
hasOrgRole | (allowedRoles, organizationIdPath) => Validator | configuration.organization.roles ã«ããããŒã«ããŒããããã³ã° |
hasOrgRoleSameOrAbove | (orgIdPath, identityIdPath, rolesByRankAsc?) => Validator | ããã©ã«ãã®ã©ã³ã¯é : ['member', 'admin', 'owner'] |
hasOrgRoleAssignmentPermission | (orgIdPath, membersPath, rolesByRankAsc?) => Validator | ã¡ã³ããŒã® upsert æã®ããŒã«å²ãåœãŠãæ€èšŒ |
hasOrgOwnerRemainingAfterMembersUpsert | (orgIdPath, membersPath) => Validator | çµç¹äžå€æ¡ä»¶ã®ã¿ â èªèšŒãªã |
hasOrgOwnerRemainingAfterMemberRemoval | (orgIdPath, identityIdPath) => Validator | çµç¹äžå€æ¡ä»¶ã®ã¿ â èªèšŒãªã |
ãªãœãŒã¹æææš©â
æ±çšçãªæææš©ãã§ãã¯:
ownsResource(resource, ownerIdPathInResource, resourceIdPathInPayload)
䜿çšã§ãã resource ã³ã¬ã¯ã·ã§ã³: chatChannelsãchatMessagesãordersãprofilesãsubscriptionsãnotificationsã
ãã¡ã€ã³ã©ãããŒã¯ partial(ownsResource, [collection, [ownerField]]) ã§ãããªãœãŒã¹ ID ã®ãã¹ã ããæž¡ããŸãã
| ãšã¯ã¹ããŒã | ã³ã¬ã¯ã·ã§ã³ | ææè ãã£ãŒã«ã |
|---|---|---|
ownsProfile | profiles | identityId |
ownsOrder | orders | identityId |
ownsChannel | chatChannels | ownerId |
ownsMessage | chatMessages | senderId |
ownsSubscription | subscriptions | subscribedId |
ownsNotification | notifications | receiverId |
ã»ãšãã©ã®æææš©ããªããŒã¿ãŒã¯å
éšã§èªèšŒããŸããæç¢ºã«ãããããSDK ã«ãŒãã§ã¯ isAuthenticated() ãå«ããããšããããããŸãã
Chatâ
| ãšã¯ã¹ããŒã | ã·ã°ãã㣠| æ³šèš |
|---|---|---|
hasSubscription | (channelIdPath, subscribedIdPath?) => Validator | ãã¹ãçç¥ãããšãsubscribedId ã¯ããŒã¯ã³ã® identityId ãããã©ã«ã |
hasOrganizationAccessToMessageTemplate | (allowedRoles, messageTemplateIdPath) => Validator | hasOrgRole ãšç°ãªããDB ã®çã® member.role æååãæ¯èŒ |
channelExists | (channelIdPath) => Validator | ååšç¢ºèªã®ã¿ â èªèšŒãªã |
ãã®ä»â
| ãšã¯ã¹ããŒã | ã·ã°ãã㣠| æ³šèš |
|---|---|---|
isSelf | (identityIdPath) => Validator | 察象 Identity ãèªèšŒæžã¿ãŠãŒã¶ãŒãšäžèŽããããšãç¢ºèª |
doesCategoryExist | çŽæ¥ã® Validator | params.requestParams.categoryId ãèªã¿åã â ãã¡ã¯ããªãŒã§ã¯ãªã |
ãŠãŒãã£ãªãã£é¢æ°â
ãããã¯ããªããŒã¿ãŒãšäžç·ã«ãšã¯ã¹ããŒããããŸãããã«ãŒãããªããŒã¿ãŒãã®ãã®ã§ã¯ãããŸããã
| ãšã¯ã¹ããŒã | ã¢ãžã¥ãŒã« | ç®ç |
|---|---|---|
getResourceById | ownsResource | ã³ã¬ã¯ã·ã§ã³ãã id ã«ãã£ãŠããã¥ã¡ã³ããååŸ |
getSubscriptionByChannelAndSubscriber | hasSubscription | channelId + subscribedId ã§ãµãã¹ã¯ãªãã·ã§ã³ãååŸ |
ð ã«ãŒãã§ã®ããªããŒã¿ãŒäœ¿çšâ
src/routes/ ã® withRoute ã«ããªããŒã¿ãŒãæ¥ç¶ããŸããããã㯠compose(withSchema, withRoute) ã«ããæ©èœãžåæãããŸãã
管çè ãŸãã¯ãªãœãŒã¹ææè â
src/routes/profile.ts ãã:
import { handlers, primitives, validators } from '@nodeblocks/backend-sdk';
const { getProfileById } = handlers;
const { withRoute } = primitives;
const { isAuthenticated, some, checkIdentityType, ownsProfile } = validators;
export const getProfileRoute = withRoute({
method: 'GET',
path: '/profiles/:profileId',
validators: [
isAuthenticated(),
some(
checkIdentityType(['admin']),
ownsProfile(['params', 'requestParams', 'profileId'])
),
],
// ãã³ãã©ãŒãã§ãŒã³ã¯çç¥ãããªããŒã¿ãŒãå
ã«å®è¡ãããã
handler: getProfileById,
});
äžå€æ¡ä»¶ã䌎ãçµç¹ã¡ã³ããŒã® upsertâ
src/routes/organization.ts ãã:
import { validators } from '@nodeblocks/backend-sdk';
const {
isAuthenticated,
all,
some,
checkIdentityType,
hasOrgRole,
hasOrgRoleAssignmentPermission,
hasOrgOwnerRemainingAfterMembersUpsert,
} = validators;
validators: [
isAuthenticated(),
some(
checkIdentityType(['admin']),
all(
hasOrgRole(['owner', 'admin'], ['params', 'requestParams', 'organizationId']),
hasOrgRoleAssignmentPermission(
['params', 'requestParams', 'organizationId'],
['params', 'requestBody']
)
)
),
hasOrgOwnerRemainingAfterMembersUpsert(
['params', 'requestParams', 'organizationId'],
['params', 'requestBody']
),
],
ã«ããŽãªãŒã®ååšâ
import { validators } from '@nodeblocks/backend-sdk';
const { isAuthenticated, doesCategoryExist } = validators;
validators: [isAuthenticated(), doesCategoryExist],
ð ïž ã«ã¹ã¿ã ããªããŒã¿ãŒã®äœæâ
import { primitives } from '@nodeblocks/backend-sdk';
const validateIdentityExists: primitives.Validator = async ({ context, params }) => {
const identityId = params.requestParams?.identityId;
const identity = await context.db.identities.findOne({ id: String(identityId) });
if (!identity) {
throw new primitives.NodeblocksError(
404,
'Identity not found',
'validateIdentityExists'
);
}
};
ãã¡ã¯ããªãŒãã¿ãŒã³â
const requireResourceExists = (collectionName: string, paramName: string) => {
return async (payload: primitives.RouteHandlerPayload) => {
const { context, params } = payload;
const resourceId = params.requestParams?.[paramName];
const resource = await context.db[collectionName].findOne({
id: String(resourceId),
});
if (!resource) {
throw new primitives.NodeblocksError(
404,
`${collectionName} not found`,
'requireResourceExists'
);
}
};
};
NodeblocksError(status, message, source) ãã¹ããŒããŠãªã¯ãšã¹ããæåŠããŸããããªããŒã¿ãŒã¯æåæã« void ãžè§£æ±ºããå¿
èŠããããŸãã
ð WebSocket ããªããŒã¿ãŒâ
WebSocket ã«ãŒãã§ã¯ withRoute({ protocol: 'ws', validators }) ã« WsValidator ã䜿çšããŸããdefService ã¯ã¯ã©ã€ã¢ã³ãæ¥ç¶æã«ãæ¥ç¶ URL ã®ã¯ãšãªæååããå°åºããã params ãšãšãã«ããªããŒã¿ãŒãå®è¡ããŸãã
{ context, params: { requestQuery: { /* URL ããååŸ */ } } }
該åœããå Žåã¯åãããªããŒã¿ãŒãã¡ã¯ããªãŒãæ¥ç¶ããããWS åºæã®æ€æ»ã«ã¯ã«ã¹ã¿ã WsValidator 颿°ãæžããŠãã ããã
WebSocket ã«ãŒãæ§æã¯ã«ãŒã »ããã£ããã¹ããªãŒãã³ã°ã®ã»ããã¢ããã¯WebSocket ãµãŒãã¹ã¬ã€ã »ãåç §ããŠãã ããã
ðš ãšã©ãŒåŠçâ
ããªããŒã¿ãŒã¯ NodeblocksError ãã¹ããŒããŸããdefService ã¯ã¹ããŒããããšã©ãŒãæ£èŠåã㊠Express ãžæž¡ããŸãããšã©ãŒã¬ã¹ãã³ã¹ãéãã«ã¯ã¢ããªã±ãŒã·ã§ã³ã¬ãã«ã§ nodeBlocksErrorMiddleware() ãããŠã³ãããŠãã ããã
import { middlewares } from '@nodeblocks/backend-sdk';
const { nodeBlocksErrorMiddleware } = middlewares;
app.use(nodeBlocksErrorMiddleware());
ã¯ã©ã€ã¢ã³ãã¯æ¬¡ãåãåããŸãã
{
"error": {
"message": "Category does not exist"
}
}
stack 㯠NODE_ENV === 'development' ã®å Žåã«ã®ã¿å«ãŸããŸããçç¥å¯èœãª data ã¯ãšã©ãŒã«èšå®ãããŠããå Žåã«å«ãŸããŸãã
å®å šãªãšã©ãŒã¢ãã«ã¯ãšã©ãŒãã³ããªã³ã° »ãåç §ããŠãã ããã
ð æšå¥šäºé â
- ãã³ãã©ãŒããå ã«ã«ãŒãããªããŒã¿ãŒ â ã¹ããŒãã¯ãã³ãã©ãŒã©ãããŒå éšã§åœ¢ç¶ãæ€èšŒããããªããŒã¿ãŒã¯å ã«ããžãã¹ããžãã¯ãééãããŸãã
- SDK ããªããŒã¿ãŒãåªå
ãã â å Žåœããç㪠DB æ€æ»ã®ä»£ããã«
hasOrgRoleãownsProfileãªã©ã䜿çšããŸãã - ãã¡ã¯ããªãŒãåŒã³åºã â
isAuthenticated()ãcheckIdentityType([...])ãhasOrgRole([...], path)ã䜿ããdoesCategoryExistã¯()ãªãã§çŽæ¥äœ¿çšããŸãã - éžæè¢ã«ã¯
some()ã䜿çšãã â 管çè OR çµç¹ææè OR ãªãœãŒã¹ææè ã®ã¢ã¯ã»ã¹ãã¿ãŒã³ã«äœ¿ããŸãã - ã¿ã€ã ID ãšããŒã« ID ãæ§æãã â ã¿ã€ãïŒããŒã«ããªããŒã¿ãŒã䜿çšããå Žåã
configuration.identity.typeIdsãšconfiguration.organization.rolesãèšå®ããŸãã - è«ççã«åæãã â èªèšŒ â èªå¯ â ãªãœãŒã¹ã®ååšïŒäžå€æ¡ä»¶ã®é ã«ããŸãã
- ã¯ãšãªã«ã€ã³ããã¯ã¹ãä»ãã â ããªããŒã¿ãŒã¯ãã°ãã°
idã§ã¯ãšãªããããããããã®ãã£ãŒã«ãã« MongoDB ã€ã³ããã¯ã¹ã確ä¿ããŸãã
â¡ïž 次ãžâ
- ã¹ããŒã » â ãªã¯ãšã¹ã圢ç¶ã®æ€èšŒãšã¹ããŒããšããªããŒã¿ãŒã®éã
- ã«ãŒã » â
withRouteãšãã³ãã©ãŒãã§ãŒã³ãžã®ããªããŒã¿ãŒæ¥ç¶ - æ©èœ » â æ©èœã§ã®ã¹ããŒããšã«ãŒãã®çµã¿åãã
- ãµãŒã㹠» â
defServiceã«ããåææžã¿æ©èœã®ããŠã³ã - ãšã©ãŒãã³ããªã³ã° » â
NodeblocksErrorãšããã«ãŠã§ã¢