feat: Support invites in CLI for V2 (#6798)
**What** - Add invite support to cli for 2.0 - Allow email to be passed upon accepting an invite
This commit is contained in:
1 parent
16860cc883
commit
0b23e7efb8
8 files changed
+98
-85
No files matched your search
@@ -1,7 +1,7 @@
|
|||||||
import { IUserModuleService } from "@medusajs/types"
|
|
||||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||||
import { createAdminUser } from "../../../helpers/create-admin-user"
|
import { IUserModuleService } from "@medusajs/types"
|
||||||
import { medusaIntegrationTestRunner } from "medusa-test-utils"
|
import { medusaIntegrationTestRunner } from "medusa-test-utils"
|
||||||
|
import { createAdminUser } from "../../../helpers/create-admin-user"
|
||||||
|
|
||||||
jest.setTimeout(50000)
|
jest.setTimeout(50000)
|
||||||
|
|
||||||
@@ -86,6 +86,41 @@ medusaIntegrationTestRunner({
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("should accept an invite with email different from invite", async () => {
|
||||||
|
const invite = await userModuleService.createInvites({
|
||||||
|
email: "potential_member@test.com",
|
||||||
|
})
|
||||||
|
|
||||||
|
const authResponse = await api.post(`/auth/admin/emailpass`, {
|
||||||
|
email: "some-email@test.com",
|
||||||
|
password: "supersecret",
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(authResponse.status).toEqual(200)
|
||||||
|
const token = authResponse.data.token
|
||||||
|
|
||||||
|
const acceptResponse = await api.post(
|
||||||
|
`/admin/invites/accept?token=${invite.token}`,
|
||||||
|
{
|
||||||
|
first_name: "John",
|
||||||
|
email: "some-email@test.com",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(acceptResponse.status).toEqual(200)
|
||||||
|
expect(acceptResponse.data.user).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
email: "some-email@test.com",
|
||||||
|
first_name: "John",
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
import { createAdminUser } from "../../../helpers/create-admin-user"
|
|
||||||
import { medusaIntegrationTestRunner } from "medusa-test-utils"
|
|
||||||
|
|
||||||
jest.setTimeout(50000)
|
|
||||||
|
|
||||||
const env = { MEDUSA_FF_MEDUSA_V2: true }
|
|
||||||
const adminHeaders = {
|
|
||||||
headers: { "x-medusa-access-token": "test_token" },
|
|
||||||
}
|
|
||||||
|
|
||||||
medusaIntegrationTestRunner({
|
|
||||||
env,
|
|
||||||
testSuite: ({ dbConnection, getContainer, api }) => {
|
|
||||||
describe("POST /admin/users", () => {
|
|
||||||
beforeEach(async () => {
|
|
||||||
await createAdminUser(dbConnection, adminHeaders, getContainer())
|
|
||||||
})
|
|
||||||
|
|
||||||
it("create a user", async () => {
|
|
||||||
const body = {
|
|
||||||
email: "test_member@test.com",
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await api.post(`/admin/users`, body, adminHeaders)
|
|
||||||
|
|
||||||
expect(response.status).toEqual(200)
|
|
||||||
expect(response.data).toEqual({
|
|
||||||
user: expect.objectContaining(body),
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
})
|
|
||||||
@@ -1,14 +1,13 @@
|
|||||||
import { UserDTO } from "@medusajs/types"
|
import { InviteWorkflow, UserDTO } from "@medusajs/types"
|
||||||
import {
|
import {
|
||||||
WorkflowData,
|
WorkflowData,
|
||||||
createWorkflow,
|
createWorkflow,
|
||||||
transform,
|
transform,
|
||||||
} from "@medusajs/workflows-sdk"
|
} from "@medusajs/workflows-sdk"
|
||||||
import { createUsersStep } from "../../user"
|
|
||||||
import { validateTokenStep } from "../steps/validate-token"
|
|
||||||
import { setAuthAppMetadataStep } from "../../auth"
|
import { setAuthAppMetadataStep } from "../../auth"
|
||||||
import { InviteWorkflow } from "@medusajs/types"
|
import { createUsersStep } from "../../user"
|
||||||
import { deleteInvitesStep } from "../steps"
|
import { deleteInvitesStep } from "../steps"
|
||||||
|
import { validateTokenStep } from "../steps/validate-token"
|
||||||
|
|
||||||
export const acceptInviteWorkflowId = "accept-invite-workflow"
|
export const acceptInviteWorkflowId = "accept-invite-workflow"
|
||||||
export const acceptInviteWorkflow = createWorkflow(
|
export const acceptInviteWorkflow = createWorkflow(
|
||||||
@@ -16,7 +15,6 @@ export const acceptInviteWorkflow = createWorkflow(
|
|||||||
(
|
(
|
||||||
input: WorkflowData<InviteWorkflow.AcceptInviteWorkflowInputDTO>
|
input: WorkflowData<InviteWorkflow.AcceptInviteWorkflowInputDTO>
|
||||||
): WorkflowData<UserDTO[]> => {
|
): WorkflowData<UserDTO[]> => {
|
||||||
// validate token
|
|
||||||
const invite = validateTokenStep(input.invite_token)
|
const invite = validateTokenStep(input.invite_token)
|
||||||
|
|
||||||
const createUserInput = transform(
|
const createUserInput = transform(
|
||||||
@@ -25,7 +23,7 @@ export const acceptInviteWorkflow = createWorkflow(
|
|||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
...input.user,
|
...input.user,
|
||||||
email: invite.email,
|
email: input.user.email ?? invite.email,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -6,7 +6,7 @@ import {
|
|||||||
UpdateServiceZoneDTO,
|
UpdateServiceZoneDTO,
|
||||||
} from "@medusajs/types"
|
} from "@medusajs/types"
|
||||||
import { GeoZoneType } from "@medusajs/utils"
|
import { GeoZoneType } from "@medusajs/utils"
|
||||||
import { moduleIntegrationTestRunner, SuiteOptions } from "medusa-test-utils"
|
import { SuiteOptions, moduleIntegrationTestRunner } from "medusa-test-utils"
|
||||||
|
|
||||||
jest.setTimeout(100000)
|
jest.setTimeout(100000)
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
|
import { acceptInviteWorkflow } from "@medusajs/core-flows"
|
||||||
|
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||||
|
import { IUserModuleService, InviteWorkflow } from "@medusajs/types"
|
||||||
import {
|
import {
|
||||||
AuthenticatedMedusaRequest,
|
AuthenticatedMedusaRequest,
|
||||||
MedusaResponse,
|
MedusaResponse,
|
||||||
} from "../../../../types/routing"
|
} from "../../../../types/routing"
|
||||||
|
|
||||||
import { acceptInviteWorkflow } from "@medusajs/core-flows"
|
|
||||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
|
||||||
import { IUserModuleService, InviteWorkflow } from "@medusajs/types"
|
|
||||||
import { AdminPostInvitesInviteAcceptReq } from "../validators"
|
import { AdminPostInvitesInviteAcceptReq } from "../validators"
|
||||||
|
|
||||||
export const POST = async (
|
export const POST = async (
|
||||||
@@ -21,8 +20,6 @@ export const POST = async (
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const workflow = acceptInviteWorkflow(req.scope)
|
|
||||||
|
|
||||||
const input = {
|
const input = {
|
||||||
invite_token: req.filterableFields.token as string,
|
invite_token: req.filterableFields.token as string,
|
||||||
auth_user_id: req.auth?.auth_user_id,
|
auth_user_id: req.auth?.auth_user_id,
|
||||||
@@ -31,7 +28,7 @@ export const POST = async (
|
|||||||
|
|
||||||
let users
|
let users
|
||||||
try {
|
try {
|
||||||
const { result } = await workflow.run({ input })
|
const { result } = await acceptInviteWorkflow(req.scope).run({ input })
|
||||||
users = result
|
users = result
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
res.status(401).json({ message: "Unauthorized" })
|
res.status(401).json({ message: "Unauthorized" })
|
||||||
|
|||||||
@@ -77,11 +77,6 @@ export class AdminCreateInviteRequest {
|
|||||||
email: string
|
email: string
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Details of the use accepting the invite.
|
|
||||||
*/
|
|
||||||
export class AdminPostInvitesInviteAcceptUserReq {}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @schema AdminPostInvitesInviteAcceptReq
|
* @schema AdminPostInvitesInviteAcceptReq
|
||||||
* type: object
|
* type: object
|
||||||
@@ -113,6 +108,13 @@ export class AdminPostInvitesInviteAcceptUserReq {}
|
|||||||
* format: password
|
* format: password
|
||||||
*/
|
*/
|
||||||
export class AdminPostInvitesInviteAcceptReq {
|
export class AdminPostInvitesInviteAcceptReq {
|
||||||
|
/**
|
||||||
|
* The invite's first name.
|
||||||
|
* If email is not passed, we default to using the email of the invite.
|
||||||
|
*/
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
email: string
|
||||||
/**
|
/**
|
||||||
* The invite's first name.
|
* The invite's first name.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -11,25 +11,38 @@ import featureFlagLoader from "../loaders/feature-flags"
|
|||||||
import configModuleLoader from "../loaders/config"
|
import configModuleLoader from "../loaders/config"
|
||||||
import { MedusaV2Flag } from "@medusajs/utils"
|
import { MedusaV2Flag } from "@medusajs/utils"
|
||||||
|
|
||||||
// TEMP: Only supporting emailpass
|
const useV2Command = async (
|
||||||
const createV2User = async ({ email, password }, { container }) => {
|
{ email, password, isInvite, provider = "emailpass" },
|
||||||
const authService = container.resolve(ModuleRegistrationName.AUTH)
|
{ container }
|
||||||
|
) => {
|
||||||
const userService = container.resolve(ModuleRegistrationName.USER)
|
const userService = container.resolve(ModuleRegistrationName.USER)
|
||||||
const user = await userService.create({ email })
|
const authService = container.resolve(ModuleRegistrationName.AUTH)
|
||||||
const { authUser } = await authService.authenticate("emailpass", {
|
|
||||||
body: {
|
|
||||||
email,
|
|
||||||
password,
|
|
||||||
},
|
|
||||||
authScope: "admin",
|
|
||||||
})
|
|
||||||
|
|
||||||
await authService.update({
|
if (isInvite) {
|
||||||
id: authUser.id,
|
// The invite flow only works with the V2 version of packages/admin-next/dashboard, so enable the V2 feature flag in admin before using this command
|
||||||
app_metadata: {
|
const invite = await userService.createInvites({ email })
|
||||||
user_id: user.id,
|
|
||||||
},
|
Logger.info(`
|
||||||
})
|
Invite token: ${invite.token}
|
||||||
|
Open the invite in Medusa Admin at: [your-admin-url]/invite?token=${invite.token}`)
|
||||||
|
} else {
|
||||||
|
const user = await userService.create({ email })
|
||||||
|
|
||||||
|
const { authUser } = await authService.authenticate(provider, {
|
||||||
|
body: {
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
},
|
||||||
|
authScope: "admin",
|
||||||
|
})
|
||||||
|
|
||||||
|
await authService.update({
|
||||||
|
id: authUser.id,
|
||||||
|
app_metadata: {
|
||||||
|
user_id: user.id,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function ({
|
export default async function ({
|
||||||
@@ -48,21 +61,21 @@ export default async function ({
|
|||||||
expressApp: app,
|
expressApp: app,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (invite) {
|
const configModule = configModuleLoader(directory)
|
||||||
const inviteService = container.resolve("inviteService")
|
const featureFlagRouter = featureFlagLoader(configModule)
|
||||||
await inviteService.create(email, "admin")
|
|
||||||
const invite = await inviteService.list({
|
|
||||||
user_email: email,
|
|
||||||
})
|
|
||||||
Logger.info(`
|
|
||||||
Invite token: ${invite[0].token}
|
|
||||||
Open the invite in Medusa Admin at: [your-admin-url]/invite?token=${invite[0].token}`)
|
|
||||||
} else {
|
|
||||||
const configModule = configModuleLoader(directory)
|
|
||||||
const featureFlagRouter = featureFlagLoader(configModule)
|
|
||||||
|
|
||||||
if (featureFlagRouter.isFeatureEnabled(MedusaV2Flag.key)) {
|
if (featureFlagRouter.isFeatureEnabled(MedusaV2Flag.key)) {
|
||||||
await createV2User({ email, password }, { container })
|
await useV2Command({ email, password, isInvite: invite }, { container })
|
||||||
|
} else {
|
||||||
|
if (invite) {
|
||||||
|
const inviteService = container.resolve("inviteService")
|
||||||
|
await inviteService.create(email, "admin")
|
||||||
|
const invite = await inviteService.list({
|
||||||
|
user_email: email,
|
||||||
|
})
|
||||||
|
Logger.info(`
|
||||||
|
Invite token: ${invite[0].token}
|
||||||
|
Open the invite in Medusa Admin at: [your-admin-url]/invite?token=${invite[0].token}`)
|
||||||
} else {
|
} else {
|
||||||
const userService = container.resolve("userService")
|
const userService = container.resolve("userService")
|
||||||
await userService.create({ id, email }, password)
|
await userService.create({ id, email }, password)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ export interface AcceptInviteWorkflowInputDTO {
|
|||||||
invite_token: string
|
invite_token: string
|
||||||
auth_user_id: string
|
auth_user_id: string
|
||||||
user: {
|
user: {
|
||||||
|
email?: string
|
||||||
first_name?: string | null
|
first_name?: string | null
|
||||||
last_name?: string | null
|
last_name?: string | null
|
||||||
avatar_url?: string | null
|
avatar_url?: string | null
|
||||||
|
|||||||
Reference in new issue
Block a user