diff --git a/src/endpoints/Context.ts b/src/endpoints/Context.ts index 9a66424..c1ed04c 100644 --- a/src/endpoints/Context.ts +++ b/src/endpoints/Context.ts @@ -10,9 +10,11 @@ import { } from "@rocket.chat/apps-engine/definition/api"; import { EmptyRequestError } from "../errors/EmptyRequestError"; import { RcSdk } from "../rc-sdk/RcSdk"; +import { Store } from "../store/Store"; export class Context { public readonly rc: RcSdk; + public readonly store: Store; public readonly request: IApiRequest; public readonly endpoint: IApiEndpointInfo; public readonly read: IRead; @@ -28,6 +30,7 @@ export class Context { persis: IPersistence, ) { this.rc = new RcSdk(http, read); + this.store = new Store(read, persis); this.request = request; this.endpoint = endpoint; this.read = read; diff --git a/src/endpoints/GroupEndpoint.ts b/src/endpoints/GroupEndpoint.ts index 9536b14..931f6ec 100644 --- a/src/endpoints/GroupEndpoint.ts +++ b/src/endpoints/GroupEndpoint.ts @@ -22,6 +22,10 @@ export class GroupEndpoint extends ScimEndpoint implements IScimEndpoint { const teamArgs = await Promise.all([getTeamInfo(), getTeamMembers()]); const group = SCIMGroup.fromRC(...teamArgs); + const meta = await ctx.store.getGroup(group.id); + if (meta) { + group.externalId = meta.externalId; + } return this.success(group); } @@ -73,6 +77,7 @@ export class GroupEndpoint extends ScimEndpoint implements IScimEndpoint { public async _delete(ctx: Context): Promise { const o = await ctx.rc.team.delete({ teamId: ctx.id() }); this.handleError(o); + await ctx.store.deleteGroup(ctx.id()); return this.response({ status: HttpStatusCode.NO_CONTENT, }); diff --git a/src/endpoints/GroupsEndpoint.ts b/src/endpoints/GroupsEndpoint.ts index ab44d31..8df8c78 100644 --- a/src/endpoints/GroupsEndpoint.ts +++ b/src/endpoints/GroupsEndpoint.ts @@ -14,7 +14,12 @@ export class GroupsEndpoint extends ScimEndpoint implements IScimEndpoint { const groups = teamsRaw.teams.map(async (team) => { const membersRaw = await ctx.rc.team.members(team._id); this.handleError(membersRaw); - return SCIMGroup.fromRC(team, membersRaw.members); + const group = SCIMGroup.fromRC(team, membersRaw.members); + const meta = await ctx.store.getGroup(group.id); + if (meta) { + group.externalId = meta.externalId; + } + return group; }); const list = new SCIMListResponse(); list.Resources = await Promise.all(groups); @@ -32,6 +37,8 @@ export class GroupsEndpoint extends ScimEndpoint implements IScimEndpoint { this.handleError(o); const m = await ctx.rc.team.members(o.team._id); const group = SCIMGroup.fromRC(o.team, m.members); + group.externalId = u.externalId; + await ctx.store.saveGroup(group.id, { externalId: group.externalId }); return this.response({ status: HttpStatusCode.CREATED, content: group, diff --git a/src/endpoints/UserEndpoint.ts b/src/endpoints/UserEndpoint.ts index d61b3fe..b21e50f 100644 --- a/src/endpoints/UserEndpoint.ts +++ b/src/endpoints/UserEndpoint.ts @@ -1,5 +1,9 @@ import { HttpStatusCode } from "@rocket.chat/apps-engine/definition/accessors"; import { IApiResponse } from "@rocket.chat/apps-engine/definition/api"; +import { + RocketChatAssociationModel, + RocketChatAssociationRecord, +} from "@rocket.chat/apps-engine/definition/metadata"; import { SCIMUser } from "../scim/User"; import { Context } from "./Context"; import { IScimEndpoint, ScimEndpoint } from "./ScimEndpoint"; @@ -11,6 +15,10 @@ export class UserEndpoint extends ScimEndpoint implements IScimEndpoint { const o = await ctx.rc.user.info(ctx.id()); this.handleError(o); const user = SCIMUser.fromRC(o.user); + const m = await ctx.store.getUser(ctx.id()); + if (m) { + user.externalId = m.externalId; + } return this.success(user); } @@ -29,9 +37,10 @@ export class UserEndpoint extends ScimEndpoint implements IScimEndpoint { }, }, }); - this.handleError(o); + await ctx.store.saveUser(ctx.id(), { externalId: u.externalId }); const user = SCIMUser.fromRC(o.user); + user.externalId = u.externalId; return this.success(user); } @@ -41,6 +50,7 @@ export class UserEndpoint extends ScimEndpoint implements IScimEndpoint { confirmRelinquish: true, }); this.handleError(o); + await ctx.store.deleteUser(ctx.id()); return this.response({ status: HttpStatusCode.NO_CONTENT, }); diff --git a/src/endpoints/UsersEndpoint.ts b/src/endpoints/UsersEndpoint.ts index 0d69850..e2b1c79 100644 --- a/src/endpoints/UsersEndpoint.ts +++ b/src/endpoints/UsersEndpoint.ts @@ -1,5 +1,9 @@ import { HttpStatusCode } from "@rocket.chat/apps-engine/definition/accessors"; import { IApiResponse } from "@rocket.chat/apps-engine/definition/api"; +import { + RocketChatAssociationModel, + RocketChatAssociationRecord, +} from "@rocket.chat/apps-engine/definition/metadata"; import crypto = require("crypto"); import { SCIMListResponse } from "../scim/ListResponse"; import { SCIMUser } from "../scim/User"; @@ -13,7 +17,15 @@ export class UsersEndpoint extends ScimEndpoint implements IScimEndpoint { const o = await ctx.rc.user.list(); this.handleError(o); const list = new SCIMListResponse(); - list.Resources = o.users.map(SCIMUser.fromRC); + list.Resources = await Promise.all( + o.users.map(SCIMUser.fromRC).map(async (x) => { + const meta = await ctx.store.getUser(x.id); + if (meta) { + x.externalId = meta.externalId; + } + return x; + }), + ); list.totalResults = o.total; return this.success(list); } @@ -35,6 +47,8 @@ export class UsersEndpoint extends ScimEndpoint implements IScimEndpoint { }); this.handleError(o); const user = SCIMUser.fromRC(o.user); + user.externalId = u.externalId; + await ctx.store.saveUser(user.id, { externalId: user.externalId }); return this.response({ status: HttpStatusCode.CREATED, content: user, diff --git a/src/rc-sdk/RcSdkUser.ts b/src/rc-sdk/RcSdkUser.ts index 0d85330..e66a0e7 100644 --- a/src/rc-sdk/RcSdkUser.ts +++ b/src/rc-sdk/RcSdkUser.ts @@ -90,7 +90,7 @@ export class RcSdkUser { } public async delete(body: IUserDelete): Promise { - const response = await this.sdk.post(`users.delete`); + const response = await this.sdk.post(`users.delete`, body); return this.sdk.parseResponse(response); } diff --git a/src/store/Store.ts b/src/store/Store.ts new file mode 100644 index 0000000..a454118 --- /dev/null +++ b/src/store/Store.ts @@ -0,0 +1,95 @@ +import { + IPersistence, + IRead, +} from "@rocket.chat/apps-engine/definition/accessors"; +import { + RocketChatAssociationModel, + RocketChatAssociationRecord, +} from "@rocket.chat/apps-engine/definition/metadata"; + +interface IUserMeta { + externalId: string; +} + +interface IGroupMeta { + externalId: string; +} + +export class Store { + private read: IRead; + private persis: IPersistence; + constructor(read: IRead, persis: IPersistence) { + this.read = read; + this.persis = persis; + } + + public deleteUser(id: string) { + return this.persis.removeByAssociation( + new RocketChatAssociationRecord( + RocketChatAssociationModel.USER, + id, + ), + ); + } + + public saveUser(id: string, meta: IUserMeta) { + return this.persis.updateByAssociation( + new RocketChatAssociationRecord( + RocketChatAssociationModel.USER, + id, + ), + meta, + true, + ); + } + + public async getUser(id: string): Promise { + const list = await this.read + .getPersistenceReader() + .readByAssociation( + new RocketChatAssociationRecord( + RocketChatAssociationModel.USER, + id, + ), + ); + if (list.length > 0) { + return list[0] as IUserMeta; + } + return; + } + + public deleteGroup(id: string) { + return this.persis.removeByAssociation( + new RocketChatAssociationRecord( + RocketChatAssociationModel.ROOM, + id, + ), + ); + } + + public saveGroup(id: string, meta: IGroupMeta) { + return this.persis.updateByAssociation( + new RocketChatAssociationRecord( + RocketChatAssociationModel.ROOM, + id, + ), + meta, + true, + ); + } + + public async getGroup(id: string): Promise { + const list = await this.read + .getPersistenceReader() + .readByAssociation( + new RocketChatAssociationRecord( + RocketChatAssociationModel.ROOM, + id, + ), + ); + if (list.length > 0) { + return list[0] as IGroupMeta; + } + return; + } +}