feat(medusa, types, core-flows): Add invite endpoints for api-v2 (#6395)
**What** - Add invite endpoints for simple operations on invites
This commit is contained in:
@@ -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),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -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")
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -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,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -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" })
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -5,3 +5,4 @@ export * from "./promotion"
|
|||||||
export * from "./customer"
|
export * from "./customer"
|
||||||
export * from "./customer-group"
|
export * from "./customer-group"
|
||||||
export * from "./user"
|
export * from "./user"
|
||||||
|
export * from "./invite"
|
||||||
|
|||||||
2
packages/core-flows/src/invite/index.ts
Normal file
2
packages/core-flows/src/invite/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export * from "./steps"
|
||||||
|
export * from "./workflows"
|
||||||
31
packages/core-flows/src/invite/steps/create-invites.ts
Normal file
31
packages/core-flows/src/invite/steps/create-invites.ts
Normal file
@@ -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)
|
||||||
|
}
|
||||||
|
)
|
||||||
28
packages/core-flows/src/invite/steps/delete-invites.ts
Normal file
28
packages/core-flows/src/invite/steps/delete-invites.ts
Normal file
@@ -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)
|
||||||
|
}
|
||||||
|
)
|
||||||
2
packages/core-flows/src/invite/steps/index.ts
Normal file
2
packages/core-flows/src/invite/steps/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export * from "./create-invites"
|
||||||
|
export * from "./delete-invites"
|
||||||
13
packages/core-flows/src/invite/workflows/create-invites.ts
Normal file
13
packages/core-flows/src/invite/workflows/create-invites.ts
Normal file
@@ -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<InviteWorkflow.CreateInvitesWorkflowInputDTO>
|
||||||
|
): WorkflowData<InviteDTO[]> => {
|
||||||
|
return createInviteStep(input.invites)
|
||||||
|
}
|
||||||
|
)
|
||||||
13
packages/core-flows/src/invite/workflows/delete-invites.ts
Normal file
13
packages/core-flows/src/invite/workflows/delete-invites.ts
Normal file
@@ -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<InviteWorkflow.DeleteInvitesWorkflowInput>
|
||||||
|
): WorkflowData<void> => {
|
||||||
|
return deleteInvitesStep(input.ids)
|
||||||
|
}
|
||||||
|
)
|
||||||
2
packages/core-flows/src/invite/workflows/index.ts
Normal file
2
packages/core-flows/src/invite/workflows/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export * from "./create-invites"
|
||||||
|
export * from "./delete-invites"
|
||||||
55
packages/medusa/src/api-v2/admin/invites/[id]/route.ts
Normal file
55
packages/medusa/src/api-v2/admin/invites/[id]/route.ts
Normal file
@@ -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,
|
||||||
|
})
|
||||||
|
}
|
||||||
36
packages/medusa/src/api-v2/admin/invites/middlewares.ts
Normal file
36
packages/medusa/src/api-v2/admin/invites/middlewares.ts
Normal file
@@ -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
|
||||||
|
),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
25
packages/medusa/src/api-v2/admin/invites/query-config.ts
Normal file
25
packages/medusa/src/api-v2/admin/invites/query-config.ts
Normal file
@@ -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,
|
||||||
|
}
|
||||||
50
packages/medusa/src/api-v2/admin/invites/route.ts
Normal file
50
packages/medusa/src/api-v2/admin/invites/route.ts
Normal file
@@ -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 })
|
||||||
|
}
|
||||||
72
packages/medusa/src/api-v2/admin/invites/validators.ts
Normal file
72
packages/medusa/src/api-v2/admin/invites/validators.ts
Normal file
@@ -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
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ import { storeCartRoutesMiddlewares } from "./store/carts/middlewares"
|
|||||||
import { storeCustomerRoutesMiddlewares } from "./store/customers/middlewares"
|
import { storeCustomerRoutesMiddlewares } from "./store/customers/middlewares"
|
||||||
import { adminUserRoutesMiddlewares } from "./admin/users/middlewares"
|
import { adminUserRoutesMiddlewares } from "./admin/users/middlewares"
|
||||||
import { storeRegionRoutesMiddlewares } from "./store/regions/middlewares"
|
import { storeRegionRoutesMiddlewares } from "./store/regions/middlewares"
|
||||||
|
import { adminInviteRoutesMiddlewares } from "./admin/invites/middlewares"
|
||||||
|
|
||||||
export const config: MiddlewaresConfig = {
|
export const config: MiddlewaresConfig = {
|
||||||
routes: [
|
routes: [
|
||||||
@@ -25,5 +26,6 @@ export const config: MiddlewaresConfig = {
|
|||||||
...storeRegionRoutesMiddlewares,
|
...storeRegionRoutesMiddlewares,
|
||||||
...adminRegionRoutesMiddlewares,
|
...adminRegionRoutesMiddlewares,
|
||||||
...adminUserRoutesMiddlewares,
|
...adminUserRoutesMiddlewares,
|
||||||
|
...adminInviteRoutesMiddlewares,
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,8 +12,6 @@ export interface UpdateUserDTO extends Partial<Omit<CreateUserDTO, "email">> {
|
|||||||
export interface CreateInviteDTO {
|
export interface CreateInviteDTO {
|
||||||
email: string
|
email: string
|
||||||
accepted?: boolean
|
accepted?: boolean
|
||||||
token: string
|
|
||||||
expires_at: Date
|
|
||||||
metadata?: Record<string, unknown> | null
|
metadata?: Record<string, unknown> | null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,3 +4,4 @@ export * as ProductWorkflow from "./product"
|
|||||||
export * as InventoryWorkflow from "./inventory"
|
export * as InventoryWorkflow from "./inventory"
|
||||||
export * as PriceListWorkflow from "./price-list"
|
export * as PriceListWorkflow from "./price-list"
|
||||||
export * as UserWorkflow from "./user"
|
export * as UserWorkflow from "./user"
|
||||||
|
export * as InviteWorkflow from "./invite"
|
||||||
|
|||||||
5
packages/types/src/workflow/invite/create-invite.ts
Normal file
5
packages/types/src/workflow/invite/create-invite.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { CreateInviteDTO } from "../../user"
|
||||||
|
|
||||||
|
export interface CreateInvitesWorkflowInputDTO {
|
||||||
|
invites: CreateInviteDTO[]
|
||||||
|
}
|
||||||
3
packages/types/src/workflow/invite/delete-invite.ts
Normal file
3
packages/types/src/workflow/invite/delete-invite.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export interface DeleteInvitesWorkflowInput {
|
||||||
|
ids: string[]
|
||||||
|
}
|
||||||
3
packages/types/src/workflow/invite/index.ts
Normal file
3
packages/types/src/workflow/invite/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export * from "./create-invite"
|
||||||
|
export * from "./update-invite"
|
||||||
|
export * from "./delete-invite"
|
||||||
5
packages/types/src/workflow/invite/update-invite.ts
Normal file
5
packages/types/src/workflow/invite/update-invite.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { UpdateInviteDTO } from "../../user"
|
||||||
|
|
||||||
|
export interface UpdateInvitesWorkflowInputDTO {
|
||||||
|
updates: UpdateInviteDTO[]
|
||||||
|
}
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
import { User } from "@models"
|
import { Invite, User } from "@models"
|
||||||
import { MapToConfig } from "@medusajs/utils"
|
import { MapToConfig } from "@medusajs/utils"
|
||||||
import { ModuleJoinerConfig } from "@medusajs/types"
|
import { ModuleJoinerConfig } from "@medusajs/types"
|
||||||
import { Modules } from "@medusajs/modules-sdk"
|
import { Modules } from "@medusajs/modules-sdk"
|
||||||
|
|
||||||
export const LinkableKeys = {
|
export const LinkableKeys = {
|
||||||
user_id: User.name,
|
user_id: User.name,
|
||||||
|
invite_id: Invite.name,
|
||||||
}
|
}
|
||||||
|
|
||||||
const entityLinkableKeysMap: MapToConfig = {}
|
const entityLinkableKeysMap: MapToConfig = {}
|
||||||
@@ -22,10 +23,19 @@ export const joinerConfig: ModuleJoinerConfig = {
|
|||||||
serviceName: Modules.USER,
|
serviceName: Modules.USER,
|
||||||
primaryKeys: ["id"],
|
primaryKeys: ["id"],
|
||||||
linkableKeys: LinkableKeys,
|
linkableKeys: LinkableKeys,
|
||||||
alias: {
|
alias: [
|
||||||
name: ["user", "users"],
|
{
|
||||||
args: {
|
name: ["user", "users"],
|
||||||
entity: User.name,
|
args: {
|
||||||
|
entity: User.name,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
|
name: ["invite", "invites"],
|
||||||
|
args: {
|
||||||
|
entity: Invite.name,
|
||||||
|
methodSuffix: "Invites",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ export default class UserModuleService<
|
|||||||
): Promise<UserTypes.InviteDTO | UserTypes.InviteDTO[]> {
|
): Promise<UserTypes.InviteDTO | UserTypes.InviteDTO[]> {
|
||||||
const input = Array.isArray(data) ? data : [data]
|
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<
|
const serializedInvites = await this.baseRepository_.serialize<
|
||||||
UserTypes.InviteDTO[] | UserTypes.InviteDTO
|
UserTypes.InviteDTO[] | UserTypes.InviteDTO
|
||||||
@@ -141,6 +141,25 @@ export default class UserModuleService<
|
|||||||
return Array.isArray(data) ? serializedInvites : serializedInvites[0]
|
return Array.isArray(data) ? serializedInvites : serializedInvites[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@InjectTransactionManager("baseRepository_")
|
||||||
|
private async createInvites_(
|
||||||
|
data: UserTypes.CreateInviteDTO[],
|
||||||
|
@MedusaContext() sharedContext: Context = {}
|
||||||
|
): Promise<TInvite[]> {
|
||||||
|
// 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(
|
updateInvites(
|
||||||
data: UserTypes.UpdateInviteDTO[],
|
data: UserTypes.UpdateInviteDTO[],
|
||||||
sharedContext?: Context
|
sharedContext?: Context
|
||||||
|
|||||||
Reference in New Issue
Block a user