diff --git a/integration-tests/plugins/__tests__/invites/create-invite.spec.ts b/integration-tests/plugins/__tests__/invites/create-invite.spec.ts new file mode 100644 index 0000000000000..fac5a92d1c33b --- /dev/null +++ b/integration-tests/plugins/__tests__/invites/create-invite.spec.ts @@ -0,0 +1,55 @@ +import { initDb, useDb } from "../../../environment-helpers/use-db" + +import path from "path" +import { startBootstrapApp } from "../../../environment-helpers/bootstrap-app" +import { useApi } from "../../../environment-helpers/use-api" +import adminSeeder from "../../../helpers/admin-seeder" +import { AxiosInstance } from "axios" + +jest.setTimeout(50000) + +const env = { MEDUSA_FF_MEDUSA_V2: true } +const adminHeaders = { + headers: { "x-medusa-access-token": "test_token" }, +} + +describe("POST /admin/invites", () => { + let dbConnection + let shutdownServer + + beforeAll(async () => { + const cwd = path.resolve(path.join(__dirname, "..", "..")) + dbConnection = await initDb({ cwd, env } as any) + shutdownServer = await startBootstrapApp({ cwd, env }) + }) + + beforeEach(async () => { + await adminSeeder(dbConnection) + }) + + afterAll(async () => { + const db = useDb() + await db.shutdown() + await shutdownServer() + }) + + afterEach(async () => { + const db = useDb() + await db.teardown() + }) + + it("create an invite", async () => { + const api = useApi()! as AxiosInstance + + const body = { + email: "test_member@test.com", + } + + const response = await api.post(`/admin/invites`, body, adminHeaders) + + expect(response.status).toEqual(200) + expect(response.data).toEqual({ + invite: expect.objectContaining(body), + }) + }) +}) diff --git a/integration-tests/plugins/__tests__/invites/delete-invite.spec.ts b/integration-tests/plugins/__tests__/invites/delete-invite.spec.ts new file mode 100644 index 0000000000000..207619a61f82f --- /dev/null +++ b/integration-tests/plugins/__tests__/invites/delete-invite.spec.ts @@ -0,0 +1,76 @@ +import { initDb, useDb } from "../../../environment-helpers/use-db" + +import { IUserModuleService } from "@medusajs/types" +import { ModuleRegistrationName } from "@medusajs/modules-sdk" +import { getContainer } from "../../../environment-helpers/use-container" +import path from "path" +import { startBootstrapApp } from "../../../environment-helpers/bootstrap-app" +import { useApi } from "../../../environment-helpers/use-api" +import adminSeeder from "../../../helpers/admin-seeder" +import { AxiosInstance } from "axios" + +jest.setTimeout(50000) + +const env = { MEDUSA_FF_MEDUSA_V2: true } +const adminHeaders = { + headers: { "x-medusa-access-token": "test_token" }, +} + +describe("DELETE /admin/invites/:id", () => { + let dbConnection + let appContainer + let shutdownServer + let userModuleService: IUserModuleService + + beforeAll(async () => { + const cwd = path.resolve(path.join(__dirname, "..", "..")) + dbConnection = await initDb({ cwd, env } as any) + shutdownServer = await startBootstrapApp({ cwd, env }) + appContainer = getContainer() + userModuleService = appContainer.resolve(ModuleRegistrationName.USER) + }) + + beforeEach(async () => { + await adminSeeder(dbConnection) + }) + + afterAll(async () => { + const db = useDb() + await db.shutdown() + await shutdownServer() + }) + + afterEach(async () => { + const db = useDb() + await db.teardown() + }) + + it("should delete a single invite", async () => { + const invite = await userModuleService.createInvites({ + email: "potential_member@test.com", + token: "test", + expires_at: new Date(), + }) + + const api = useApi()! as AxiosInstance + + const response = await api.delete( + `/admin/invites/${invite.id}`, + adminHeaders + ) + + expect(response.status).toEqual(200) + expect(response.data).toEqual({ + id: invite.id, + object: "invite", + deleted: true, + }) + + const { response: deletedResponse } = await api + .get(`/admin/invites/${invite.id}`, adminHeaders) + .catch((e) => e) + + expect(deletedResponse.status).toEqual(404) + expect(deletedResponse.data.type).toEqual("not_found") + }) +}) diff --git a/integration-tests/plugins/__tests__/invites/list-invites.spec.ts b/integration-tests/plugins/__tests__/invites/list-invites.spec.ts new file mode 100644 index 0000000000000..ab1165fadd562 --- /dev/null +++ b/integration-tests/plugins/__tests__/invites/list-invites.spec.ts @@ -0,0 +1,69 @@ +import { initDb, useDb } from "../../../environment-helpers/use-db" + +import { IUserModuleService } from "@medusajs/types" +import { ModuleRegistrationName } from "@medusajs/modules-sdk" +import { getContainer } from "../../../environment-helpers/use-container" +import path from "path" +import { startBootstrapApp } from "../../../environment-helpers/bootstrap-app" +import { useApi } from "../../../environment-helpers/use-api" +import adminSeeder from "../../../helpers/admin-seeder" +import { AxiosInstance } from "axios" + +jest.setTimeout(50000) + +const env = { MEDUSA_FF_MEDUSA_V2: true } +const adminHeaders = { + headers: { "x-medusa-access-token": "test_token" }, +} + +describe("GET /admin/invites", () => { + let dbConnection + let appContainer + let shutdownServer + let userModuleService: IUserModuleService + + beforeAll(async () => { + const cwd = path.resolve(path.join(__dirname, "..", "..")) + dbConnection = await initDb({ cwd, env } as any) + shutdownServer = await startBootstrapApp({ cwd, env }) + appContainer = getContainer() + userModuleService = appContainer.resolve(ModuleRegistrationName.USER) + }) + + beforeEach(async () => { + await adminSeeder(dbConnection) + }) + + afterAll(async () => { + const db = useDb() + await db.shutdown() + await shutdownServer() + }) + + afterEach(async () => { + const db = useDb() + await db.teardown() + }) + + it("should list invites", async () => { + await userModuleService.createInvites({ + email: "potential_member@test.com", + token: "test", + expires_at: new Date(), + }) + + const api = useApi()! as AxiosInstance + + const response = await api.get(`/admin/invites`, adminHeaders) + + expect(response.status).toEqual(200) + expect(response.data).toEqual({ + invites: [ + expect.objectContaining({ email: "potential_member@test.com" }), + ], + count: 1, + offset: 0, + limit: 50, + }) + }) +}) diff --git a/integration-tests/plugins/__tests__/invites/retrieve-invite.spec.ts b/integration-tests/plugins/__tests__/invites/retrieve-invite.spec.ts new file mode 100644 index 0000000000000..2f283c8b76a63 --- /dev/null +++ b/integration-tests/plugins/__tests__/invites/retrieve-invite.spec.ts @@ -0,0 +1,64 @@ +import { initDb, useDb } from "../../../environment-helpers/use-db" + +import { IUserModuleService } from "@medusajs/types" +import { ModuleRegistrationName } from "@medusajs/modules-sdk" +import { getContainer } from "../../../environment-helpers/use-container" +import path from "path" +import { startBootstrapApp } from "../../../environment-helpers/bootstrap-app" +import { useApi } from "../../../environment-helpers/use-api" +import adminSeeder from "../../../helpers/admin-seeder" +import { AxiosInstance } from "axios" + +jest.setTimeout(50000) + +const env = { MEDUSA_FF_MEDUSA_V2: true } +const adminHeaders = { + headers: { "x-medusa-access-token": "test_token" }, +} + +describe("GET /admin/invites/:id", () => { + let dbConnection + let appContainer + let shutdownServer + let userModuleService: IUserModuleService + + beforeAll(async () => { + const cwd = path.resolve(path.join(__dirname, "..", "..")) + dbConnection = await initDb({ cwd, env } as any) + shutdownServer = await startBootstrapApp({ cwd, env }) + appContainer = getContainer() + userModuleService = appContainer.resolve(ModuleRegistrationName.USER) + }) + + beforeEach(async () => { + await adminSeeder(dbConnection) + }) + + afterAll(async () => { + const db = useDb() + await db.shutdown() + await shutdownServer() + }) + + afterEach(async () => { + const db = useDb() + await db.teardown() + }) + + it("should retrieve a single invite", async () => { + const invite = await userModuleService.createInvites({ + email: "potential_member@test.com", + token: "test", + expires_at: new Date(), + }) + + const api = useApi()! as AxiosInstance + + const response = await api.get(`/admin/invites/${invite.id}`, adminHeaders) + + expect(response.status).toEqual(200) + expect(response.data.invite).toEqual( + expect.objectContaining({ email: "potential_member@test.com" }) + ) + }) +}) diff --git a/packages/core-flows/src/index.ts b/packages/core-flows/src/index.ts index 56e6906010c81..c1841bb2d2c8d 100644 --- a/packages/core-flows/src/index.ts +++ b/packages/core-flows/src/index.ts @@ -5,3 +5,4 @@ export * from "./promotion" export * from "./customer" export * from "./customer-group" export * from "./user" +export * from "./invite" diff --git a/packages/core-flows/src/invite/index.ts b/packages/core-flows/src/invite/index.ts new file mode 100644 index 0000000000000..68de82c9f92da --- /dev/null +++ b/packages/core-flows/src/invite/index.ts @@ -0,0 +1,2 @@ +export * from "./steps" +export * from "./workflows" diff --git a/packages/core-flows/src/invite/steps/create-invites.ts b/packages/core-flows/src/invite/steps/create-invites.ts new file mode 100644 index 0000000000000..f6a4ac3a68b91 --- /dev/null +++ b/packages/core-flows/src/invite/steps/create-invites.ts @@ -0,0 +1,31 @@ +import { ModuleRegistrationName } from "@medusajs/modules-sdk" +import { CreateInviteDTO, IUserModuleService, InviteDTO } from "@medusajs/types" +import { StepResponse, createStep } from "@medusajs/workflows-sdk" + +export const createInviteStepId = "create-invite-step" +export const createInviteStep = createStep( + createInviteStepId, + async (input: CreateInviteDTO[], { container }) => { + const service: IUserModuleService = container.resolve( + ModuleRegistrationName.USER + ) + + const createdInvites = await service.createInvites(input) + + return new StepResponse( + createdInvites, + createdInvites.map((inv) => inv.id) + ) + }, + async (createdInvitesIds, { container }) => { + if (!createdInvitesIds?.length) { + return + } + + const service: IUserModuleService = container.resolve( + ModuleRegistrationName.USER + ) + + await service.deleteInvites(createdInvitesIds) + } +) diff --git a/packages/core-flows/src/invite/steps/delete-invites.ts b/packages/core-flows/src/invite/steps/delete-invites.ts new file mode 100644 index 0000000000000..036d71c2be35c --- /dev/null +++ b/packages/core-flows/src/invite/steps/delete-invites.ts @@ -0,0 +1,28 @@ +import { ModuleRegistrationName } from "@medusajs/modules-sdk" +import { IUserModuleService } from "@medusajs/types" +import { StepResponse, createStep } from "@medusajs/workflows-sdk" + +export const deleteInvitesStepId = "delete-invites-step" +export const deleteInvitesStep = createStep( + deleteInvitesStepId, + async (input: string[], { container }) => { + const service: IUserModuleService = container.resolve( + ModuleRegistrationName.USER + ) + + await service.softDeleteInvites(input) + + return new StepResponse(void 0, input) + }, + async (deletedInviteIds, { container }) => { + if (!deletedInviteIds?.length) { + return + } + + const service: IUserModuleService = container.resolve( + ModuleRegistrationName.USER + ) + + await service.restoreInvites(deletedInviteIds) + } +) diff --git a/packages/core-flows/src/invite/steps/index.ts b/packages/core-flows/src/invite/steps/index.ts new file mode 100644 index 0000000000000..1f17f0e332779 --- /dev/null +++ b/packages/core-flows/src/invite/steps/index.ts @@ -0,0 +1,2 @@ +export * from "./create-invites" +export * from "./delete-invites" diff --git a/packages/core-flows/src/invite/workflows/create-invites.ts b/packages/core-flows/src/invite/workflows/create-invites.ts new file mode 100644 index 0000000000000..8c82ea468c643 --- /dev/null +++ b/packages/core-flows/src/invite/workflows/create-invites.ts @@ -0,0 +1,13 @@ +import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk" +import { createInviteStep } from "../steps" +import { InviteDTO, InviteWorkflow } from "@medusajs/types" + +export const createInvitesWorkflowId = "create-invite-step" +export const createInvitesWorkflow = createWorkflow( + createInvitesWorkflowId, + ( + input: WorkflowData + ): WorkflowData => { + return createInviteStep(input.invites) + } +) diff --git a/packages/core-flows/src/invite/workflows/delete-invites.ts b/packages/core-flows/src/invite/workflows/delete-invites.ts new file mode 100644 index 0000000000000..0b82f68f93c5b --- /dev/null +++ b/packages/core-flows/src/invite/workflows/delete-invites.ts @@ -0,0 +1,13 @@ +import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk" +import { deleteInvitesStep } from "../steps" +import { InviteWorkflow, UserWorkflow } from "@medusajs/types" + +export const deleteInvitesWorkflowId = "delete-invites-workflow" +export const deleteInvitesWorkflow = createWorkflow( + deleteInvitesWorkflowId, + ( + input: WorkflowData + ): WorkflowData => { + return deleteInvitesStep(input.ids) + } +) diff --git a/packages/core-flows/src/invite/workflows/index.ts b/packages/core-flows/src/invite/workflows/index.ts new file mode 100644 index 0000000000000..1f17f0e332779 --- /dev/null +++ b/packages/core-flows/src/invite/workflows/index.ts @@ -0,0 +1,2 @@ +export * from "./create-invites" +export * from "./delete-invites" diff --git a/packages/medusa/src/api-v2/admin/invites/[id]/route.ts b/packages/medusa/src/api-v2/admin/invites/[id]/route.ts new file mode 100644 index 0000000000000..ba862c3bff93a --- /dev/null +++ b/packages/medusa/src/api-v2/admin/invites/[id]/route.ts @@ -0,0 +1,55 @@ +import { + ContainerRegistrationKeys, + MedusaError, + remoteQueryObjectFromString, +} from "@medusajs/utils" +import { MedusaRequest, MedusaResponse } from "../../../../types/routing" +import { deleteInvitesWorkflow } from "@medusajs/core-flows" +import { IUserModuleService, UpdateUserDTO } from "@medusajs/types" +import { ModuleRegistrationName } from "../../../../../../modules-sdk/dist" + +// Get invite +export const GET = async (req: MedusaRequest, res: MedusaResponse) => { + const { id } = req.params + const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY) + + const query = remoteQueryObjectFromString({ + entryPoint: "invite", + variables: { + id, + }, + fields: req.retrieveConfig.select as string[], + }) + + const [invite] = await remoteQuery(query) + + if (!invite) { + throw new MedusaError( + MedusaError.Types.NOT_FOUND, + `Invite with id: ${id} was not found` + ) + } + + res.status(200).json({ invite }) +} + +// delete invite +export const DELETE = async (req: MedusaRequest, res: MedusaResponse) => { + const { id } = req.params + const workflow = deleteInvitesWorkflow(req.scope) + + const { errors } = await workflow.run({ + input: { ids: [id] }, + throwOnError: false, + }) + + if (Array.isArray(errors) && errors[0]) { + throw errors[0].error + } + + res.status(200).json({ + id, + object: "invite", + deleted: true, + }) +} diff --git a/packages/medusa/src/api-v2/admin/invites/middlewares.ts b/packages/medusa/src/api-v2/admin/invites/middlewares.ts new file mode 100644 index 0000000000000..5381ce553a931 --- /dev/null +++ b/packages/medusa/src/api-v2/admin/invites/middlewares.ts @@ -0,0 +1,36 @@ +import { transformBody, transformQuery } from "../../../api/middlewares" +import { + AdminCreateInviteRequest, + AdminGetInvitesParams, + AdminGetInvitesInviteParams, +} from "./validators" +import * as QueryConfig from "./query-config" +import { MiddlewareRoute } from "../../../types/middlewares" + +export const adminInviteRoutesMiddlewares: MiddlewareRoute[] = [ + { + method: ["GET"], + matcher: "/admin/invites", + middlewares: [ + transformQuery( + AdminGetInvitesParams, + QueryConfig.listTransformQueryConfig + ), + ], + }, + { + method: ["POST"], + matcher: "/admin/invites", + middlewares: [transformBody(AdminCreateInviteRequest)], + }, + { + method: ["GET"], + matcher: "/admin/invites/:id", + middlewares: [ + transformQuery( + AdminGetInvitesInviteParams, + QueryConfig.retrieveTransformQueryConfig + ), + ], + }, +] diff --git a/packages/medusa/src/api-v2/admin/invites/query-config.ts b/packages/medusa/src/api-v2/admin/invites/query-config.ts new file mode 100644 index 0000000000000..10a0b8d5482e1 --- /dev/null +++ b/packages/medusa/src/api-v2/admin/invites/query-config.ts @@ -0,0 +1,25 @@ +export const defaultAdminInviteRelations = [] +export const allowedAdminInviteRelations = [] +export const defaultAdminInviteFields = [ + "id", + "email", + "accepted", + "token", + "expires_at", + "metadata", + "created_at", + "updated_at", + "deleted_at", +] + +export const retrieveTransformQueryConfig = { + defaultFields: defaultAdminInviteFields, + defaultRelations: defaultAdminInviteRelations, + allowedRelations: allowedAdminInviteRelations, + isList: false, +} + +export const listTransformQueryConfig = { + ...retrieveTransformQueryConfig, + isList: true, +} diff --git a/packages/medusa/src/api-v2/admin/invites/route.ts b/packages/medusa/src/api-v2/admin/invites/route.ts new file mode 100644 index 0000000000000..e52ca97a63d47 --- /dev/null +++ b/packages/medusa/src/api-v2/admin/invites/route.ts @@ -0,0 +1,50 @@ +import { + ContainerRegistrationKeys, + remoteQueryObjectFromString, +} from "@medusajs/utils" +import { MedusaRequest, MedusaResponse } from "../../../types/routing" +import { createInvitesWorkflow } from "@medusajs/core-flows" +import { CreateInviteDTO, CreateUserDTO } from "@medusajs/types" + +// List invites +export const GET = async (req: MedusaRequest, res: MedusaResponse) => { + const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY) + + const query = remoteQueryObjectFromString({ + entryPoint: "invite", + variables: { + filters: req.filterableFields, + order: req.listConfig.order, + skip: req.listConfig.skip, + take: req.listConfig.take, + }, + fields: req.listConfig.select as string[], + }) + + const { rows: invites, metadata } = await remoteQuery({ + ...query, + }) + + res.status(200).json({ + invites, + count: metadata.count, + offset: metadata.skip, + limit: metadata.take, + }) +} + +// Create invite +export const POST = async (req: MedusaRequest, res: MedusaResponse) => { + const workflow = createInvitesWorkflow(req.scope) + + const input = { + input: { + invites: [req.validatedBody as CreateInviteDTO], + }, + } + + const { result } = await workflow.run(input) + + const [invite] = result + res.status(200).json({ invite }) +} diff --git a/packages/medusa/src/api-v2/admin/invites/validators.ts b/packages/medusa/src/api-v2/admin/invites/validators.ts new file mode 100644 index 0000000000000..3b43bf72740f2 --- /dev/null +++ b/packages/medusa/src/api-v2/admin/invites/validators.ts @@ -0,0 +1,72 @@ +import { Type } from "class-transformer" +import { IsEmail, IsOptional, IsString, ValidateNested } from "class-validator" +import { + DateComparisonOperator, + FindParams, + extendedFindParamsMixin, +} from "../../../types/common" +import { IsType } from "../../../utils" + +export class AdminGetInvitesInviteParams extends FindParams {} + +export class AdminGetInvitesParams extends extendedFindParamsMixin({ + limit: 50, + offset: 0, +}) { + /** + * IDs to filter invites by. + */ + @IsOptional() + @IsType([String, [String]]) + id?: string | string[] + + /** + * The field to sort the data by. By default, the sort order is ascending. To change the order to descending, prefix the field name with `-`. + */ + @IsString() + @IsOptional() + order?: string + + /** + * Date filters to apply on the invites' `update_at` date. + */ + @IsOptional() + @ValidateNested() + @Type(() => DateComparisonOperator) + updated_at?: DateComparisonOperator + + /** + * Date filters to apply on the customer invites' `created_at` date. + */ + @IsOptional() + @ValidateNested() + @Type(() => DateComparisonOperator) + created_at?: DateComparisonOperator + + /** + * Date filters to apply on the invites' `deleted_at` date. + */ + @IsOptional() + @ValidateNested() + @Type(() => DateComparisonOperator) + deleted_at?: DateComparisonOperator + + /** + * Filter to apply on the invites' `email` field. + */ + @IsOptional() + @IsString() + email?: string + + /** + * Comma-separated fields that should be included in the returned invites. + */ + @IsOptional() + @IsString() + fields?: string +} + +export class AdminCreateInviteRequest { + @IsEmail() + email: string +} diff --git a/packages/medusa/src/api-v2/middlewares.ts b/packages/medusa/src/api-v2/middlewares.ts index 0add798844a52..18d4287cc6cbe 100644 --- a/packages/medusa/src/api-v2/middlewares.ts +++ b/packages/medusa/src/api-v2/middlewares.ts @@ -10,6 +10,7 @@ import { storeCartRoutesMiddlewares } from "./store/carts/middlewares" import { storeCustomerRoutesMiddlewares } from "./store/customers/middlewares" import { adminUserRoutesMiddlewares } from "./admin/users/middlewares" import { storeRegionRoutesMiddlewares } from "./store/regions/middlewares" +import { adminInviteRoutesMiddlewares } from "./admin/invites/middlewares" export const config: MiddlewaresConfig = { routes: [ @@ -25,5 +26,6 @@ export const config: MiddlewaresConfig = { ...storeRegionRoutesMiddlewares, ...adminRegionRoutesMiddlewares, ...adminUserRoutesMiddlewares, + ...adminInviteRoutesMiddlewares, ], } diff --git a/packages/types/src/user/mutations.ts b/packages/types/src/user/mutations.ts index ad9790f51b554..c27d2241963d6 100644 --- a/packages/types/src/user/mutations.ts +++ b/packages/types/src/user/mutations.ts @@ -12,8 +12,6 @@ export interface UpdateUserDTO extends Partial> { export interface CreateInviteDTO { email: string accepted?: boolean - token: string - expires_at: Date metadata?: Record | null } diff --git a/packages/types/src/workflow/index.ts b/packages/types/src/workflow/index.ts index 5a6447c5c6cca..ae6f28603ac87 100644 --- a/packages/types/src/workflow/index.ts +++ b/packages/types/src/workflow/index.ts @@ -4,3 +4,4 @@ export * as ProductWorkflow from "./product" export * as InventoryWorkflow from "./inventory" export * as PriceListWorkflow from "./price-list" export * as UserWorkflow from "./user" +export * as InviteWorkflow from "./invite" diff --git a/packages/types/src/workflow/invite/create-invite.ts b/packages/types/src/workflow/invite/create-invite.ts new file mode 100644 index 0000000000000..4322b4c583359 --- /dev/null +++ b/packages/types/src/workflow/invite/create-invite.ts @@ -0,0 +1,5 @@ +import { CreateInviteDTO } from "../../user" + +export interface CreateInvitesWorkflowInputDTO { + invites: CreateInviteDTO[] +} diff --git a/packages/types/src/workflow/invite/delete-invite.ts b/packages/types/src/workflow/invite/delete-invite.ts new file mode 100644 index 0000000000000..fd69391208527 --- /dev/null +++ b/packages/types/src/workflow/invite/delete-invite.ts @@ -0,0 +1,3 @@ +export interface DeleteInvitesWorkflowInput { + ids: string[] +} diff --git a/packages/types/src/workflow/invite/index.ts b/packages/types/src/workflow/invite/index.ts new file mode 100644 index 0000000000000..119d7e6ccd4b2 --- /dev/null +++ b/packages/types/src/workflow/invite/index.ts @@ -0,0 +1,3 @@ +export * from "./create-invite" +export * from "./update-invite" +export * from "./delete-invite" diff --git a/packages/types/src/workflow/invite/update-invite.ts b/packages/types/src/workflow/invite/update-invite.ts new file mode 100644 index 0000000000000..1af1a8e5ca090 --- /dev/null +++ b/packages/types/src/workflow/invite/update-invite.ts @@ -0,0 +1,5 @@ +import { UpdateInviteDTO } from "../../user" + +export interface UpdateInvitesWorkflowInputDTO { + updates: UpdateInviteDTO[] +} diff --git a/packages/user/src/joiner-config.ts b/packages/user/src/joiner-config.ts index 056a4c35b30b4..9dda3f6263fd8 100644 --- a/packages/user/src/joiner-config.ts +++ b/packages/user/src/joiner-config.ts @@ -1,10 +1,11 @@ -import { User } from "@models" +import { Invite, User } from "@models" import { MapToConfig } from "@medusajs/utils" import { ModuleJoinerConfig } from "@medusajs/types" import { Modules } from "@medusajs/modules-sdk" export const LinkableKeys = { user_id: User.name, + invite_id: Invite.name, } const entityLinkableKeysMap: MapToConfig = {} @@ -22,10 +23,19 @@ export const joinerConfig: ModuleJoinerConfig = { serviceName: Modules.USER, primaryKeys: ["id"], linkableKeys: LinkableKeys, - alias: { - name: ["user", "users"], - args: { - entity: User.name, + alias: [ + { + name: ["user", "users"], + args: { + entity: User.name, + }, }, - }, + { + name: ["invite", "invites"], + args: { + entity: Invite.name, + methodSuffix: "Invites", + }, + }, + ], } diff --git a/packages/user/src/services/user-module.ts b/packages/user/src/services/user-module.ts index 1da5005e254d1..d01cac59a4795 100644 --- a/packages/user/src/services/user-module.ts +++ b/packages/user/src/services/user-module.ts @@ -130,7 +130,7 @@ export default class UserModuleService< ): Promise { const input = Array.isArray(data) ? data : [data] - const invites = await this.inviteService_.create(input, sharedContext) + const invites = await this.createInvites_(input, sharedContext) const serializedInvites = await this.baseRepository_.serialize< UserTypes.InviteDTO[] | UserTypes.InviteDTO @@ -141,6 +141,25 @@ export default class UserModuleService< return Array.isArray(data) ? serializedInvites : serializedInvites[0] } + @InjectTransactionManager("baseRepository_") + private async createInvites_( + data: UserTypes.CreateInviteDTO[], + @MedusaContext() sharedContext: Context = {} + ): Promise { + // expiration date in 10 days + const expirationDate = new Date().setDate(new Date().getDate() + 10) + + const toCreate = data.map((invite) => { + return { + ...invite, + expires_at: new Date(expirationDate), + token: "placeholder", // TODO: generate token + } + }) + + return await this.inviteService_.create(toCreate) + } + updateInvites( data: UserTypes.UpdateInviteDTO[], sharedContext?: Context