Chore/rm main entity concept (#7709)
**What** Update the `MedusaService` class, factory and types to remove the concept of main modules. The idea being that all method will be explicitly named and suffixes to represent the object you are trying to manipulate. This pr also includes various fixes in different modules Co-authored-by: Stevche Radevski <4820812+sradevski@users.noreply.github.com> Co-authored-by: Oli Juhl <59018053+olivermrbl@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
2895ccfba8
commit
48963f55ef
@@ -1,17 +0,0 @@
|
||||
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
||||
import { Invite } from "@models"
|
||||
import { CreateInviteDTO } from "../../../types/dist"
|
||||
|
||||
export const createInvites = async (
|
||||
manager: SqlEntityManager,
|
||||
inviteData: (CreateInviteDTO & { id?: string })[]
|
||||
) => {
|
||||
const invites: Invite[] = []
|
||||
|
||||
for (const invite of inviteData) {
|
||||
const inv = manager.create(Invite, invite)
|
||||
invites.push(inv)
|
||||
}
|
||||
|
||||
await manager.persistAndFlush(invites)
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
||||
import { User } from "@models"
|
||||
import { CreateUserDTO } from "../../../types/dist"
|
||||
|
||||
export const createUsers = async (
|
||||
manager: SqlEntityManager,
|
||||
userData: (CreateUserDTO & { id?: string })[]
|
||||
) => {
|
||||
const users: User[] = []
|
||||
|
||||
for (const user of userData) {
|
||||
const usr = manager.create(User, user)
|
||||
users.push(usr)
|
||||
}
|
||||
|
||||
await manager.persistAndFlush(users)
|
||||
}
|
||||
@@ -4,9 +4,7 @@ import { UserEvents } from "@medusajs/utils"
|
||||
import {
|
||||
MockEventBusService,
|
||||
moduleIntegrationTestRunner,
|
||||
SuiteOptions,
|
||||
} from "medusa-test-utils"
|
||||
import { createInvites } from "../../../__fixtures__/invite"
|
||||
|
||||
jest.setTimeout(30000)
|
||||
|
||||
@@ -28,7 +26,7 @@ const defaultInviteData = [
|
||||
},
|
||||
]
|
||||
|
||||
moduleIntegrationTestRunner({
|
||||
moduleIntegrationTestRunner<IUserModuleService>({
|
||||
moduleName: Modules.USER,
|
||||
moduleOptions: {
|
||||
jwt_secret: "test",
|
||||
@@ -36,10 +34,7 @@ moduleIntegrationTestRunner({
|
||||
injectedDependencies: {
|
||||
eventBusModuleService: new MockEventBusService(),
|
||||
},
|
||||
testSuite: ({
|
||||
MikroOrmWrapper,
|
||||
service,
|
||||
}: SuiteOptions<IUserModuleService>) => {
|
||||
testSuite: ({ service }) => {
|
||||
describe("UserModuleService - Invite", () => {
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
@@ -47,8 +42,7 @@ moduleIntegrationTestRunner({
|
||||
|
||||
describe("listInvites", () => {
|
||||
it("should list invites", async () => {
|
||||
await createInvites(MikroOrmWrapper.forkManager(), defaultInviteData)
|
||||
|
||||
await service.createInvites(defaultInviteData)
|
||||
const invites = await service.listInvites()
|
||||
|
||||
expect(invites).toEqual([
|
||||
@@ -62,7 +56,7 @@ moduleIntegrationTestRunner({
|
||||
})
|
||||
|
||||
it("should list invites by id", async () => {
|
||||
await createInvites(MikroOrmWrapper.forkManager(), defaultInviteData)
|
||||
await service.createInvites(defaultInviteData)
|
||||
const invites = await service.listInvites({
|
||||
id: ["1"],
|
||||
})
|
||||
@@ -77,7 +71,7 @@ moduleIntegrationTestRunner({
|
||||
|
||||
describe("listAndCountInvites", () => {
|
||||
it("should list and count invites", async () => {
|
||||
await createInvites(MikroOrmWrapper.forkManager(), defaultInviteData)
|
||||
await service.createInvites(defaultInviteData)
|
||||
const [invites, count] = await service.listAndCountInvites()
|
||||
|
||||
expect(count).toEqual(2)
|
||||
@@ -92,7 +86,7 @@ moduleIntegrationTestRunner({
|
||||
})
|
||||
|
||||
it("should listAndCount invites by id", async () => {
|
||||
await createInvites(MikroOrmWrapper.forkManager(), defaultInviteData)
|
||||
await service.createInvites(defaultInviteData)
|
||||
const [invites, count] = await service.listAndCountInvites({
|
||||
id: "1",
|
||||
})
|
||||
@@ -110,7 +104,7 @@ moduleIntegrationTestRunner({
|
||||
const id = "1"
|
||||
|
||||
it("should return an invite for the given id", async () => {
|
||||
await createInvites(MikroOrmWrapper.forkManager(), defaultInviteData)
|
||||
await service.createInvites(defaultInviteData)
|
||||
const invite = await service.retrieveInvite(id)
|
||||
|
||||
expect(invite).toEqual(
|
||||
@@ -139,7 +133,7 @@ moduleIntegrationTestRunner({
|
||||
})
|
||||
|
||||
it("should return invite based on config select param", async () => {
|
||||
await createInvites(MikroOrmWrapper.forkManager(), defaultInviteData)
|
||||
await service.createInvites(defaultInviteData)
|
||||
const invite = await service.retrieveInvite(id, {
|
||||
select: ["id"],
|
||||
})
|
||||
@@ -166,7 +160,7 @@ moduleIntegrationTestRunner({
|
||||
})
|
||||
|
||||
it("should emit invite updated events", async () => {
|
||||
await createInvites(MikroOrmWrapper.forkManager(), defaultInviteData)
|
||||
await service.createInvites(defaultInviteData)
|
||||
|
||||
jest.clearAllMocks()
|
||||
|
||||
@@ -190,12 +184,12 @@ moduleIntegrationTestRunner({
|
||||
|
||||
describe("resendInvite", () => {
|
||||
it("should emit token generated event for invites", async () => {
|
||||
await createInvites(MikroOrmWrapper.forkManager(), defaultInviteData)
|
||||
await service.createInvites(defaultInviteData)
|
||||
const eventBusSpy = jest.spyOn(MockEventBusService.prototype, "emit")
|
||||
|
||||
await service.refreshInviteTokens(["1"])
|
||||
|
||||
expect(eventBusSpy).toHaveBeenCalledTimes(1)
|
||||
expect(eventBusSpy).toHaveBeenCalledTimes(2)
|
||||
expect(eventBusSpy).toHaveBeenCalledWith([
|
||||
expect.objectContaining({
|
||||
data: { id: "1" },
|
||||
@@ -4,9 +4,7 @@ import { UserEvents } from "@medusajs/utils"
|
||||
import {
|
||||
MockEventBusService,
|
||||
moduleIntegrationTestRunner,
|
||||
SuiteOptions,
|
||||
} from "medusa-test-utils"
|
||||
import { createUsers } from "../../../__fixtures__/user"
|
||||
|
||||
jest.setTimeout(30000)
|
||||
|
||||
@@ -21,7 +19,7 @@ const defaultUserData = [
|
||||
},
|
||||
]
|
||||
|
||||
moduleIntegrationTestRunner({
|
||||
moduleIntegrationTestRunner<IUserModuleService>({
|
||||
moduleName: Modules.USER,
|
||||
moduleOptions: {
|
||||
jwt_secret: "test",
|
||||
@@ -29,11 +27,7 @@ moduleIntegrationTestRunner({
|
||||
injectedDependencies: {
|
||||
eventBusModuleService: new MockEventBusService(),
|
||||
},
|
||||
testSuite: ({
|
||||
MikroOrmWrapper,
|
||||
service,
|
||||
medusaApp,
|
||||
}: SuiteOptions<IUserModuleService>) => {
|
||||
testSuite: ({ service }) => {
|
||||
describe("UserModuleService - User", () => {
|
||||
afterEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
@@ -41,9 +35,8 @@ moduleIntegrationTestRunner({
|
||||
|
||||
describe("list", () => {
|
||||
it("should list users", async () => {
|
||||
await createUsers(MikroOrmWrapper.forkManager(), defaultUserData)
|
||||
|
||||
const users = await service.list()
|
||||
await service.createUsers(defaultUserData)
|
||||
const users = await service.listUsers()
|
||||
|
||||
expect(users).toEqual([
|
||||
expect.objectContaining({
|
||||
@@ -56,8 +49,8 @@ moduleIntegrationTestRunner({
|
||||
})
|
||||
|
||||
it("should list users by id", async () => {
|
||||
await createUsers(MikroOrmWrapper.forkManager(), defaultUserData)
|
||||
const users = await service.list({
|
||||
await service.createUsers(defaultUserData)
|
||||
const users = await service.listUsers({
|
||||
id: ["1"],
|
||||
})
|
||||
|
||||
@@ -71,8 +64,8 @@ moduleIntegrationTestRunner({
|
||||
|
||||
describe("listAndCount", () => {
|
||||
it("should list and count users", async () => {
|
||||
await createUsers(MikroOrmWrapper.forkManager(), defaultUserData)
|
||||
const [users, count] = await service.listAndCount()
|
||||
await service.createUsers(defaultUserData)
|
||||
const [users, count] = await service.listAndCountUsers()
|
||||
|
||||
expect(count).toEqual(2)
|
||||
expect(users).toEqual([
|
||||
@@ -86,8 +79,8 @@ moduleIntegrationTestRunner({
|
||||
})
|
||||
|
||||
it("should list and count users by id", async () => {
|
||||
await createUsers(MikroOrmWrapper.forkManager(), defaultUserData)
|
||||
const [Users, count] = await service.listAndCount({
|
||||
await service.createUsers(defaultUserData)
|
||||
const [Users, count] = await service.listAndCountUsers({
|
||||
id: "1",
|
||||
})
|
||||
|
||||
@@ -104,9 +97,9 @@ moduleIntegrationTestRunner({
|
||||
const id = "1"
|
||||
|
||||
it("should return an user for the given id", async () => {
|
||||
await createUsers(MikroOrmWrapper.forkManager(), defaultUserData)
|
||||
await service.createUsers(defaultUserData)
|
||||
|
||||
const user = await service.retrieve(id)
|
||||
const user = await service.retrieveUser(id)
|
||||
|
||||
expect(user).toEqual(
|
||||
expect.objectContaining({
|
||||
@@ -116,7 +109,9 @@ moduleIntegrationTestRunner({
|
||||
})
|
||||
|
||||
it("should throw an error when an user with the given id does not exist", async () => {
|
||||
const error = await service.retrieve("does-not-exist").catch((e) => e)
|
||||
const error = await service
|
||||
.retrieveUser("does-not-exist")
|
||||
.catch((e) => e)
|
||||
|
||||
expect(error.message).toEqual(
|
||||
"User with id: does-not-exist was not found"
|
||||
@@ -125,16 +120,16 @@ moduleIntegrationTestRunner({
|
||||
|
||||
it("should throw an error when a userId is not provided", async () => {
|
||||
const error = await service
|
||||
.retrieve(undefined as unknown as string)
|
||||
.retrieveUser(undefined as unknown as string)
|
||||
.catch((e) => e)
|
||||
|
||||
expect(error.message).toEqual("user - id must be defined")
|
||||
})
|
||||
|
||||
it("should return user based on config select param", async () => {
|
||||
await createUsers(MikroOrmWrapper.forkManager(), defaultUserData)
|
||||
await service.createUsers(defaultUserData)
|
||||
|
||||
const User = await service.retrieve(id, {
|
||||
const User = await service.retrieveUser(id, {
|
||||
select: ["id"],
|
||||
})
|
||||
|
||||
@@ -150,11 +145,11 @@ moduleIntegrationTestRunner({
|
||||
const id = "1"
|
||||
|
||||
it("should delete the users given an id successfully", async () => {
|
||||
await createUsers(MikroOrmWrapper.forkManager(), defaultUserData)
|
||||
await service.createUsers(defaultUserData)
|
||||
|
||||
await service.delete([id])
|
||||
await service.deleteUsers([id])
|
||||
|
||||
const users = await service.list({
|
||||
const users = await service.listUsers({
|
||||
id: [id],
|
||||
})
|
||||
|
||||
@@ -165,7 +160,7 @@ moduleIntegrationTestRunner({
|
||||
describe("update", () => {
|
||||
it("should throw an error when a id does not exist", async () => {
|
||||
const error = await service
|
||||
.update([
|
||||
.updateUsers([
|
||||
{
|
||||
id: "does-not-exist",
|
||||
},
|
||||
@@ -179,11 +174,11 @@ moduleIntegrationTestRunner({
|
||||
|
||||
it("should emit user created events", async () => {
|
||||
const eventBusSpy = jest.spyOn(MockEventBusService.prototype, "emit")
|
||||
await service.create(defaultUserData)
|
||||
await service.createUsers(defaultUserData)
|
||||
|
||||
jest.clearAllMocks()
|
||||
|
||||
await service.update([
|
||||
await service.updateUsers([
|
||||
{
|
||||
id: "1",
|
||||
first_name: "John",
|
||||
@@ -202,9 +197,9 @@ moduleIntegrationTestRunner({
|
||||
|
||||
describe("create", () => {
|
||||
it("should create a user successfully", async () => {
|
||||
await service.create(defaultUserData)
|
||||
await service.createUsers(defaultUserData)
|
||||
|
||||
const [User, count] = await service.listAndCount({
|
||||
const [User, count] = await service.listAndCountUsers({
|
||||
id: ["1"],
|
||||
})
|
||||
|
||||
@@ -218,7 +213,7 @@ moduleIntegrationTestRunner({
|
||||
|
||||
it("should emit user created events", async () => {
|
||||
const eventBusSpy = jest.spyOn(MockEventBusService.prototype, "emit")
|
||||
await service.create(defaultUserData)
|
||||
await service.createUsers(defaultUserData)
|
||||
|
||||
expect(eventBusSpy).toHaveBeenCalledTimes(1)
|
||||
expect(eventBusSpy).toHaveBeenCalledWith([
|
||||
@@ -8,10 +8,7 @@
|
||||
"dist"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
},
|
||||
"bin": {
|
||||
"medusa-user-seed": "dist/scripts/bin/run-seed.js"
|
||||
"node": ">=20"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
import { moduleDefinition } from "./module-definition"
|
||||
import { UserModuleService } from "@services"
|
||||
import { ModuleExports } from "@medusajs/types"
|
||||
|
||||
const moduleDefinition: ModuleExports = {
|
||||
service: UserModuleService,
|
||||
}
|
||||
export default moduleDefinition
|
||||
|
||||
@@ -1,41 +1,11 @@
|
||||
import { Invite, User } from "@models"
|
||||
import { MapToConfig } from "@medusajs/utils"
|
||||
import { ModuleJoinerConfig } from "@medusajs/types"
|
||||
import {
|
||||
buildEntitiesNameToLinkableKeysMap,
|
||||
defineJoinerConfig,
|
||||
MapToConfig,
|
||||
} from "@medusajs/utils"
|
||||
import { Modules } from "@medusajs/modules-sdk"
|
||||
|
||||
export const LinkableKeys = {
|
||||
user_id: User.name,
|
||||
invite_id: Invite.name,
|
||||
}
|
||||
export const joinerConfig = defineJoinerConfig(Modules.USER)
|
||||
|
||||
const entityLinkableKeysMap: MapToConfig = {}
|
||||
Object.entries(LinkableKeys).forEach(([key, value]) => {
|
||||
entityLinkableKeysMap[value] ??= []
|
||||
entityLinkableKeysMap[value].push({
|
||||
mapTo: key,
|
||||
valueFrom: key.split("_").pop()!,
|
||||
})
|
||||
})
|
||||
|
||||
export const entityNameToLinkableKeysMap: MapToConfig = entityLinkableKeysMap
|
||||
|
||||
export const joinerConfig: ModuleJoinerConfig = {
|
||||
serviceName: Modules.USER,
|
||||
primaryKeys: ["id"],
|
||||
linkableKeys: LinkableKeys,
|
||||
alias: [
|
||||
{
|
||||
name: ["user", "users"],
|
||||
args: {
|
||||
entity: User.name,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: ["invite", "invites"],
|
||||
args: {
|
||||
entity: Invite.name,
|
||||
methodSuffix: "Invites",
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
export const entityNameToLinkableKeysMap: MapToConfig =
|
||||
buildEntitiesNameToLinkableKeysMap(joinerConfig.linkableKeys)
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
import { UserModuleService } from "@services"
|
||||
import { ModuleExports } from "@medusajs/types"
|
||||
|
||||
const service = UserModuleService
|
||||
|
||||
export const moduleDefinition: ModuleExports = {
|
||||
service,
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export { MikroOrmBaseRepository as BaseRepository } from "@medusajs/utils"
|
||||
@@ -1,19 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { EOL } from "os"
|
||||
import { run } from "../seed"
|
||||
|
||||
const args = process.argv
|
||||
const path = args.pop() as string
|
||||
|
||||
export default (async () => {
|
||||
const { config } = await import("dotenv")
|
||||
config()
|
||||
if (!path) {
|
||||
throw new Error(
|
||||
`filePath is required.${EOL}Example: medusa-user-seed <filePath>`
|
||||
)
|
||||
}
|
||||
|
||||
await run({ path })
|
||||
})()
|
||||
@@ -1,58 +0,0 @@
|
||||
import * as UserModels from "@models"
|
||||
|
||||
import { DALUtils, ModulesSdkUtils } from "@medusajs/utils"
|
||||
import { LoaderOptions, Logger, ModulesSdkTypes } from "@medusajs/types"
|
||||
|
||||
import { EOL } from "os"
|
||||
import { EntitySchema } from "@mikro-orm/core"
|
||||
import { Modules } from "@medusajs/modules-sdk"
|
||||
import { resolve } from "path"
|
||||
|
||||
export async function run({
|
||||
options,
|
||||
logger,
|
||||
path,
|
||||
}: Partial<
|
||||
Pick<
|
||||
LoaderOptions<ModulesSdkTypes.ModuleServiceInitializeOptions>,
|
||||
"options" | "logger"
|
||||
>
|
||||
> & {
|
||||
path: string
|
||||
}) {
|
||||
logger ??= console as unknown as Logger
|
||||
|
||||
logger.info(`Loading seed data from ${path}...`)
|
||||
|
||||
const { userData } = await import(resolve(process.cwd(), path)).catch((e) => {
|
||||
logger?.error(
|
||||
`Failed to load seed data from ${path}. Please, provide a relative path and check that you export the following: userData.${EOL}${e}`
|
||||
)
|
||||
throw e
|
||||
})
|
||||
|
||||
const dbData = ModulesSdkUtils.loadDatabaseConfig(Modules.USER, options)!
|
||||
const entities = Object.values(UserModels) as unknown as EntitySchema[]
|
||||
const pathToMigrations = __dirname + "/../migrations"
|
||||
|
||||
const orm = await DALUtils.mikroOrmCreateConnection(
|
||||
dbData,
|
||||
entities,
|
||||
pathToMigrations
|
||||
)
|
||||
|
||||
const manager = orm.em.fork()
|
||||
|
||||
try {
|
||||
logger.info("Seeding user data..")
|
||||
|
||||
// TODO: implement user seed data
|
||||
// await createUsers(manager, usersData)
|
||||
} catch (e) {
|
||||
logger.error(
|
||||
`Failed to insert the seed data in the PostgreSQL database ${dbData.clientUrl}.${EOL}${e}`
|
||||
)
|
||||
}
|
||||
|
||||
await orm.close(true)
|
||||
}
|
||||
@@ -1,2 +1 @@
|
||||
export { default as UserModuleService } from "./user-module"
|
||||
export { default as InviteService } from "./invite"
|
||||
|
||||
@@ -1,184 +0,0 @@
|
||||
import * as crypto from "crypto"
|
||||
|
||||
import { Context, DAL } from "@medusajs/types"
|
||||
import {
|
||||
arrayDifference,
|
||||
InjectTransactionManager,
|
||||
MedusaError,
|
||||
ModulesSdkUtils,
|
||||
} from "@medusajs/utils"
|
||||
import jwt, { JwtPayload } from "jsonwebtoken"
|
||||
|
||||
import { Invite } from "@models"
|
||||
import { InviteServiceTypes } from "@types"
|
||||
|
||||
type InjectedDependencies = {
|
||||
inviteRepository: DAL.RepositoryService
|
||||
}
|
||||
|
||||
// 1 day
|
||||
const DEFAULT_VALID_INVITE_DURATION = 60 * 60 * 24 * 1000
|
||||
|
||||
export default class InviteService<
|
||||
TEntity extends Invite = Invite
|
||||
> extends ModulesSdkUtils.MedusaInternalService<InjectedDependencies>(
|
||||
Invite
|
||||
)<TEntity> {
|
||||
// eslint-disable-next-line max-len
|
||||
protected readonly inviteRepository_: DAL.RepositoryService<TEntity>
|
||||
protected options_: { jwt_secret: string; valid_duration: number } | undefined
|
||||
|
||||
constructor(container: InjectedDependencies) {
|
||||
super(container)
|
||||
this.inviteRepository_ = container.inviteRepository
|
||||
}
|
||||
|
||||
public withModuleOptions(options: any) {
|
||||
const service = new InviteService<TEntity>(this.__container__)
|
||||
|
||||
service.options_ = options
|
||||
|
||||
return service
|
||||
}
|
||||
|
||||
private getOption(key: string) {
|
||||
if (!this.options_) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.UNEXPECTED_STATE,
|
||||
`Options are not configured for InviteService, call "withModuleOptions" and provide options`
|
||||
)
|
||||
}
|
||||
return this.options_[key]
|
||||
}
|
||||
|
||||
create(
|
||||
data: InviteServiceTypes.CreateInviteDTO,
|
||||
context?: Context
|
||||
): Promise<TEntity>
|
||||
create(
|
||||
data: InviteServiceTypes.CreateInviteDTO[],
|
||||
context?: Context
|
||||
): Promise<TEntity[]>
|
||||
|
||||
@InjectTransactionManager("inviteRepository_")
|
||||
async create(
|
||||
data:
|
||||
| InviteServiceTypes.CreateInviteDTO
|
||||
| InviteServiceTypes.CreateInviteDTO[],
|
||||
context: Context = {}
|
||||
): Promise<TEntity | TEntity[]> {
|
||||
const data_ = Array.isArray(data) ? data : [data]
|
||||
|
||||
const invites = await super.create(data_, context)
|
||||
|
||||
const expiresIn: number = this.getValidDuration()
|
||||
|
||||
const updates = invites.map((invite) => {
|
||||
return {
|
||||
id: invite.id,
|
||||
expires_at: new Date().setMilliseconds(
|
||||
new Date().getMilliseconds() + expiresIn
|
||||
),
|
||||
token: this.generateToken({ id: invite.id }),
|
||||
}
|
||||
})
|
||||
|
||||
return await super.update(updates, context)
|
||||
}
|
||||
|
||||
@InjectTransactionManager("inviteRepository_")
|
||||
async refreshInviteTokens(
|
||||
inviteIds: string[],
|
||||
context: Context = {}
|
||||
): Promise<TEntity[]> {
|
||||
const [invites, count] = await super.listAndCount(
|
||||
{ id: inviteIds },
|
||||
{},
|
||||
context
|
||||
)
|
||||
|
||||
if (count !== inviteIds.length) {
|
||||
const missing = arrayDifference(
|
||||
inviteIds,
|
||||
invites.map((invite) => invite.id)
|
||||
)
|
||||
|
||||
if (missing.length > 0) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`The following invites do not exist: ${missing.join(", ")}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const expiresIn: number = this.getValidDuration()
|
||||
|
||||
const updates = invites.map((invite) => {
|
||||
return {
|
||||
id: invite.id,
|
||||
expires_at: new Date().setMilliseconds(
|
||||
new Date().getMilliseconds() + expiresIn
|
||||
),
|
||||
token: this.generateToken({ id: invite.id }),
|
||||
}
|
||||
})
|
||||
|
||||
return await super.update(updates, context)
|
||||
}
|
||||
|
||||
@InjectTransactionManager("inviteRepository_")
|
||||
async validateInviteToken(
|
||||
token: string,
|
||||
context?: Context
|
||||
): Promise<TEntity> {
|
||||
const decoded = this.validateToken(token)
|
||||
|
||||
const invite = await super.retrieve(decoded.payload.id, {}, context)
|
||||
|
||||
if (invite.expires_at < new Date()) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
"The invite has expired"
|
||||
)
|
||||
}
|
||||
|
||||
return invite
|
||||
}
|
||||
|
||||
private generateToken(data: any): string {
|
||||
const jwtSecret: string = this.getOption("jwt_secret")
|
||||
const expiresIn: number = this.getValidDuration() / 1000
|
||||
|
||||
if (!jwtSecret) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
"No jwt_secret was provided in the UserModule's options. Please add one."
|
||||
)
|
||||
}
|
||||
|
||||
return jwt.sign(data, jwtSecret, {
|
||||
jwtid: crypto.randomUUID(),
|
||||
expiresIn,
|
||||
})
|
||||
}
|
||||
|
||||
private getValidDuration(): number {
|
||||
return (
|
||||
parseInt(this.getOption("valid_duration")) ||
|
||||
DEFAULT_VALID_INVITE_DURATION
|
||||
)
|
||||
}
|
||||
|
||||
private validateToken(data: any): JwtPayload {
|
||||
const jwtSecret = this.getOption("jwt_secret")
|
||||
|
||||
if (!jwtSecret) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
"No jwt_secret was provided in the UserModule's options. Please add one."
|
||||
)
|
||||
}
|
||||
|
||||
return jwt.verify(data, jwtSecret, { complete: true })
|
||||
}
|
||||
}
|
||||
@@ -8,40 +8,40 @@ import {
|
||||
UserTypes,
|
||||
} from "@medusajs/types"
|
||||
import {
|
||||
arrayDifference,
|
||||
CommonEvents,
|
||||
EmitEvents,
|
||||
InjectManager,
|
||||
InjectTransactionManager,
|
||||
MedusaContext,
|
||||
ModulesSdkUtils,
|
||||
MedusaError,
|
||||
MedusaService,
|
||||
UserEvents,
|
||||
} from "@medusajs/utils"
|
||||
import jwt, { JwtPayload } from "jsonwebtoken"
|
||||
import { entityNameToLinkableKeysMap, joinerConfig } from "../joiner-config"
|
||||
import crypto from "node:crypto"
|
||||
|
||||
import { Invite, User } from "@models"
|
||||
import InviteService from "./invite"
|
||||
|
||||
type InjectedDependencies = {
|
||||
baseRepository: DAL.RepositoryService
|
||||
userService: ModulesSdkTypes.IMedusaInternalService<any>
|
||||
inviteService: InviteService<any>
|
||||
inviteService: ModulesSdkTypes.IMedusaInternalService<any>
|
||||
eventBusModuleService: IEventBusModuleService
|
||||
}
|
||||
|
||||
const generateMethodForModels = { Invite }
|
||||
|
||||
export default class UserModuleService<
|
||||
TUser extends User = User,
|
||||
TInvite extends Invite = Invite
|
||||
>
|
||||
extends ModulesSdkUtils.MedusaService<
|
||||
UserTypes.UserDTO,
|
||||
{
|
||||
Invite: {
|
||||
dto: UserTypes.InviteDTO
|
||||
}
|
||||
// 1 day
|
||||
const DEFAULT_VALID_INVITE_DURATION = 60 * 60 * 24 * 1000
|
||||
export default class UserModuleService
|
||||
extends MedusaService<{
|
||||
User: {
|
||||
dto: UserTypes.UserDTO
|
||||
}
|
||||
>(User, generateMethodForModels, entityNameToLinkableKeysMap)
|
||||
Invite: {
|
||||
dto: UserTypes.InviteDTO
|
||||
}
|
||||
}>({ User, Invite }, entityNameToLinkableKeysMap)
|
||||
implements UserTypes.IUserModuleService
|
||||
{
|
||||
__joinerConfig(): ModuleJoinerConfig {
|
||||
@@ -50,8 +50,9 @@ export default class UserModuleService<
|
||||
|
||||
protected baseRepository_: DAL.RepositoryService
|
||||
|
||||
protected readonly userService_: ModulesSdkTypes.IMedusaInternalService<TUser>
|
||||
protected readonly inviteService_: InviteService<TInvite>
|
||||
protected readonly userService_: ModulesSdkTypes.IMedusaInternalService<User>
|
||||
protected readonly inviteService_: ModulesSdkTypes.IMedusaInternalService<Invite>
|
||||
protected readonly config: { jwtSecret: string; expiresIn: number }
|
||||
|
||||
constructor(
|
||||
{ userService, inviteService, baseRepository }: InjectedDependencies,
|
||||
@@ -62,9 +63,20 @@ export default class UserModuleService<
|
||||
|
||||
this.baseRepository_ = baseRepository
|
||||
this.userService_ = userService
|
||||
this.inviteService_ = inviteService.withModuleOptions(
|
||||
this.moduleDeclaration
|
||||
)
|
||||
this.inviteService_ = inviteService
|
||||
this.config = {
|
||||
jwtSecret: moduleDeclaration["jwt_secret"],
|
||||
expiresIn:
|
||||
parseInt(moduleDeclaration["valid_duration"]) ||
|
||||
DEFAULT_VALID_INVITE_DURATION,
|
||||
}
|
||||
|
||||
if (!this.config.jwtSecret) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
"No jwt_secret was provided in the UserModule's options. Please add one."
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@InjectTransactionManager("baseRepository_")
|
||||
@@ -72,11 +84,22 @@ export default class UserModuleService<
|
||||
token: string,
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<UserTypes.InviteDTO> {
|
||||
const invite = await this.inviteService_.validateInviteToken(
|
||||
token,
|
||||
const jwtSecret = this.moduleDeclaration["jwt_secret"]
|
||||
const decoded: JwtPayload = jwt.verify(token, jwtSecret, { complete: true })
|
||||
|
||||
const invite = await this.inviteService_.retrieve(
|
||||
decoded.payload.id,
|
||||
{},
|
||||
sharedContext
|
||||
)
|
||||
|
||||
if (invite.expires_at < new Date()) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
"The invite has expired"
|
||||
)
|
||||
}
|
||||
|
||||
return await this.baseRepository_.serialize<UserTypes.InviteDTO>(invite, {
|
||||
populate: true,
|
||||
})
|
||||
@@ -114,24 +137,52 @@ export default class UserModuleService<
|
||||
inviteIds: string[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
) {
|
||||
return await this.inviteService_.refreshInviteTokens(
|
||||
inviteIds,
|
||||
const [invites, count] = await this.inviteService_.listAndCount(
|
||||
{ id: inviteIds },
|
||||
{},
|
||||
sharedContext
|
||||
)
|
||||
|
||||
if (count !== inviteIds.length) {
|
||||
const missing = arrayDifference(
|
||||
inviteIds,
|
||||
invites.map((invite) => invite.id)
|
||||
)
|
||||
|
||||
if (missing.length > 0) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`The following invites do not exist: ${missing.join(", ")}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const updates = invites.map((invite) => {
|
||||
return {
|
||||
id: invite.id,
|
||||
expires_at: new Date().setMilliseconds(
|
||||
new Date().getMilliseconds() + this.config.expiresIn
|
||||
),
|
||||
token: this.generateToken({ id: invite.id }),
|
||||
}
|
||||
})
|
||||
|
||||
return await this.inviteService_.update(updates, sharedContext)
|
||||
}
|
||||
|
||||
create(
|
||||
// @ts-expect-error
|
||||
createUsers(
|
||||
data: UserTypes.CreateUserDTO[],
|
||||
sharedContext?: Context
|
||||
): Promise<UserTypes.UserDTO[]>
|
||||
create(
|
||||
createUsers(
|
||||
data: UserTypes.CreateUserDTO,
|
||||
sharedContext?: Context
|
||||
): Promise<UserTypes.UserDTO>
|
||||
|
||||
@InjectManager("baseRepository_")
|
||||
@EmitEvents()
|
||||
async create(
|
||||
async createUsers(
|
||||
data: UserTypes.CreateUserDTO[] | UserTypes.CreateUserDTO,
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<UserTypes.UserDTO | UserTypes.UserDTO[]> {
|
||||
@@ -159,18 +210,19 @@ export default class UserModuleService<
|
||||
return Array.isArray(data) ? serializedUsers : serializedUsers[0]
|
||||
}
|
||||
|
||||
update(
|
||||
// @ts-expect-error
|
||||
updateUsers(
|
||||
data: UserTypes.UpdateUserDTO[],
|
||||
sharedContext?: Context
|
||||
): Promise<UserTypes.UserDTO[]>
|
||||
update(
|
||||
updateUsers(
|
||||
data: UserTypes.UpdateUserDTO,
|
||||
sharedContext?: Context
|
||||
): Promise<UserTypes.UserDTO>
|
||||
|
||||
@InjectManager("baseRepository_")
|
||||
@EmitEvents()
|
||||
async update(
|
||||
async updateUsers(
|
||||
data: UserTypes.UpdateUserDTO | UserTypes.UpdateUserDTO[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<UserTypes.UserDTO | UserTypes.UserDTO[]> {
|
||||
@@ -253,7 +305,7 @@ export default class UserModuleService<
|
||||
private async createInvites_(
|
||||
data: UserTypes.CreateInviteDTO[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<TInvite[]> {
|
||||
): Promise<Invite[]> {
|
||||
const toCreate = data.map((invite) => {
|
||||
return {
|
||||
...invite,
|
||||
@@ -262,7 +314,19 @@ export default class UserModuleService<
|
||||
}
|
||||
})
|
||||
|
||||
return await this.inviteService_.create(toCreate, sharedContext)
|
||||
const created = await this.inviteService_.create(toCreate, sharedContext)
|
||||
|
||||
const updates = created.map((invite) => {
|
||||
return {
|
||||
id: invite.id,
|
||||
expires_at: new Date().setMilliseconds(
|
||||
new Date().getMilliseconds() + this.config.expiresIn
|
||||
),
|
||||
token: this.generateToken({ id: invite.id }),
|
||||
}
|
||||
})
|
||||
|
||||
return await this.inviteService_.update(updates, sharedContext)
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
@@ -307,4 +371,12 @@ export default class UserModuleService<
|
||||
|
||||
return Array.isArray(data) ? serializedInvites : serializedInvites[0]
|
||||
}
|
||||
|
||||
private generateToken(data: any): string {
|
||||
const jwtSecret: string = this.moduleDeclaration["jwt_secret"]
|
||||
return jwt.sign(data, jwtSecret, {
|
||||
jwtid: crypto.randomUUID(),
|
||||
expiresIn: this.config.expiresIn,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user