diff --git a/packages/authentication/integration-tests/__fixtures__/auth-provider/index.ts b/packages/authentication/integration-tests/__fixtures__/auth-provider/index.ts new file mode 100644 index 0000000000000..c21f9be7b10cc --- /dev/null +++ b/packages/authentication/integration-tests/__fixtures__/auth-provider/index.ts @@ -0,0 +1,41 @@ +import { SqlEntityManager } from "@mikro-orm/postgresql" +import { AuthProvider } from "@models" + +export async function createAuthProviders( + manager: SqlEntityManager, + providerData: any[] = [ + { + provider: "manual", + name: "manual", + is_active: true, + }, + { + provider: "disabled", + name: "disabled", + }, + { + provider: "store", + name: "store", + domain: "store", + is_active: true, + }, + { + provider: "admin", + name: "admin", + domain: "admin", + is_active: true, + }, + ] +): Promise { + const authProviders: AuthProvider[] = [] + + for (const provider of providerData) { + const authProvider = manager.create(AuthProvider, provider) + + authProviders.push(authProvider) + } + + await manager.persistAndFlush(authProviders) + + return authProviders +} diff --git a/packages/authentication/integration-tests/__fixtures__/auth-user/index.ts b/packages/authentication/integration-tests/__fixtures__/auth-user/index.ts new file mode 100644 index 0000000000000..7921cbdf906f3 --- /dev/null +++ b/packages/authentication/integration-tests/__fixtures__/auth-user/index.ts @@ -0,0 +1,31 @@ +import { SqlEntityManager } from "@mikro-orm/postgresql" +import { AuthUser } from "@models" + +export async function createAuthUsers( + manager: SqlEntityManager, + userData: any[] = [ + { + id: "test-id", + provider: "manual", + }, + { + id: "test-id-1", + provider: "manual", + }, + { + provider: "store", + }, + ] +): Promise { + const authUsers: AuthUser[] = [] + + for (const user of userData) { + const authUser = manager.create(AuthUser, user) + + authUsers.push(authUser) + } + + await manager.persistAndFlush(authUsers) + + return authUsers +} diff --git a/packages/authentication/integration-tests/__tests__/index.spec.ts b/packages/authentication/integration-tests/__tests__/index.spec.ts deleted file mode 100644 index 728f6245c6bfd..0000000000000 --- a/packages/authentication/integration-tests/__tests__/index.spec.ts +++ /dev/null @@ -1,5 +0,0 @@ -describe("Noop test", () => { - it("noop check", async () => { - expect(true).toBe(true) - }) -}) diff --git a/packages/authentication/integration-tests/__tests__/services/auth-provider/index.spec.ts b/packages/authentication/integration-tests/__tests__/services/auth-provider/index.spec.ts new file mode 100644 index 0000000000000..645045ecd2f9d --- /dev/null +++ b/packages/authentication/integration-tests/__tests__/services/auth-provider/index.spec.ts @@ -0,0 +1,270 @@ +import { SqlEntityManager } from "@mikro-orm/postgresql" +import { AuthProviderRepository } from "@repositories" +import { AuthProviderService } from "@services" + +import { MikroOrmWrapper } from "../../../utils" +import { createAuthProviders } from "../../../__fixtures__/auth-provider" + +jest.setTimeout(30000) + +describe("AuthProvider Service", () => { + let service: AuthProviderService + let testManager: SqlEntityManager + let repositoryManager: SqlEntityManager + + beforeEach(async () => { + await MikroOrmWrapper.setupDatabase() + repositoryManager = await MikroOrmWrapper.forkManager() + testManager = await MikroOrmWrapper.forkManager() + + const authProviderRepository = new AuthProviderRepository({ + manager: repositoryManager, + }) + + service = new AuthProviderService({ + authProviderRepository, + }) + + await createAuthProviders(testManager) + }) + + afterEach(async () => { + await MikroOrmWrapper.clearDatabase() + }) + + describe("list", () => { + it("should list AuthProviders", async () => { + const authProviders = await service.list() + const serialized = JSON.parse(JSON.stringify(authProviders)) + + expect(serialized).toEqual([ + expect.objectContaining({ + provider: "manual", + }), + expect.objectContaining({ + provider: "disabled", + }), + expect.objectContaining({ + provider: "store", + }), + expect.objectContaining({ + provider: "admin", + }), + ]) + }) + + it("should list authProviders by provider id", async () => { + const authProviders = await service.list({ + provider: ["manual"], + }) + + expect(authProviders).toEqual([ + expect.objectContaining({ + provider: "manual", + }), + ]) + }) + + it("should list active authProviders", async () => { + const authProviders = await service.list({ + is_active: true, + }) + + const serialized = JSON.parse(JSON.stringify(authProviders)) + + expect(serialized).toEqual([ + expect.objectContaining({ + provider: "manual", + }), + expect.objectContaining({ + provider: "store", + }), + expect.objectContaining({ + provider: "admin", + }), + ]) + }) + }) + + describe("listAndCount", () => { + it("should list AuthProviders", async () => { + const [authProviders, count] = await service.listAndCount() + const serialized = JSON.parse(JSON.stringify(authProviders)) + + expect(count).toEqual(4) + expect(serialized).toEqual([ + expect.objectContaining({ + provider: "manual", + }), + expect.objectContaining({ + provider: "disabled", + }), + expect.objectContaining({ + provider: "store", + }), + expect.objectContaining({ + provider: "admin", + }), + ]) + }) + + it("should listAndCount authProviders by provider", async () => { + const [authProviders, count] = await service.listAndCount({ + provider: ["manual"], + }) + + expect(count).toEqual(1) + expect(authProviders).toEqual([ + expect.objectContaining({ + provider: "manual", + }), + ]) + }) + + it("should listAndCount active authProviders", async () => { + const [authProviders, count] = await service.listAndCount({ + is_active: true, + }) + + const serialized = JSON.parse(JSON.stringify(authProviders)) + + expect(count).toEqual(3) + expect(serialized).toEqual([ + expect.objectContaining({ + provider: "manual", + }), + expect.objectContaining({ + provider: "store", + }), + expect.objectContaining({ + provider: "admin", + }), + ]) + }) + }) + + describe("retrieve", () => { + const provider = "manual" + + it("should return an authProvider for the given provider", async () => { + const authProvider = await service.retrieve(provider) + + expect(authProvider).toEqual( + expect.objectContaining({ + provider, + }) + ) + }) + + it("should throw an error when an authProvider with the given provider does not exist", async () => { + let error + + try { + await service.retrieve("does-not-exist") + } catch (e) { + error = e + } + + expect(error.message).toEqual( + "AuthProvider with provider: does-not-exist was not found" + ) + }) + + it("should throw an error when a provider is not provided", async () => { + let error + + try { + await service.retrieve(undefined as unknown as string) + } catch (e) { + error = e + } + + expect(error.message).toEqual('"authProviderProvider" must be defined') + }) + + it("should return authProvider based on config select param", async () => { + const authProvider = await service.retrieve(provider, { + select: ["provider"], + }) + + const serialized = JSON.parse(JSON.stringify(authProvider)) + + expect(serialized).toEqual({ + provider, + }) + }) + }) + + describe("delete", () => { + const provider = "manual" + + it("should delete the authProviders given a provider successfully", async () => { + await service.delete([provider]) + + const authProviders = await service.list({ + provider: [provider], + }) + + expect(authProviders).toHaveLength(0) + }) + }) + + describe("update", () => { + const provider = "manual" + + it("should throw an error when a id does not exist", async () => { + let error + + try { + await service.update([ + { + provider: "does-not-exist", + }, + ]) + } catch (e) { + error = e + } + + expect(error.message).toEqual( + 'AuthProvider with provider "does-not-exist" not found' + ) + }) + + it("should update authProvider", async () => { + await service.update([ + { + provider: "manual", + name: "test", + }, + ]) + + const [provider] = await service.list({ provider: ["manual"] }) + expect(provider).toEqual( + expect.objectContaining({ + name: "test", + }) + ) + }) + }) + + describe("create", () => { + it("should create a authProvider successfully", async () => { + await service.create([ + { + provider: "test", + name: "test provider", + }, + ]) + + const [authProvider] = await service.list({ + provider: ["test"], + }) + + expect(authProvider).toEqual( + expect.objectContaining({ + provider: "test", + }) + ) + }) + }) +}) diff --git a/packages/authentication/integration-tests/__tests__/services/auth-user/index.spec.ts b/packages/authentication/integration-tests/__tests__/services/auth-user/index.spec.ts new file mode 100644 index 0000000000000..1aaa7d2f999da --- /dev/null +++ b/packages/authentication/integration-tests/__tests__/services/auth-user/index.spec.ts @@ -0,0 +1,245 @@ +import { SqlEntityManager } from "@mikro-orm/postgresql" +import { AuthUserRepository } from "@repositories" +import { AuthUserService } from "@services" + +import { MikroOrmWrapper } from "../../../utils" +import { createAuthProviders } from "../../../__fixtures__/auth-provider" +import { createAuthUsers } from "../../../__fixtures__/auth-user" + +jest.setTimeout(30000) + +describe("AuthUser Service", () => { + let service: AuthUserService + let testManager: SqlEntityManager + let repositoryManager: SqlEntityManager + + beforeEach(async () => { + await MikroOrmWrapper.setupDatabase() + repositoryManager = await MikroOrmWrapper.forkManager() + testManager = await MikroOrmWrapper.forkManager() + + const authUserRepository = new AuthUserRepository({ + manager: repositoryManager, + }) + + service = new AuthUserService({ + authUserRepository, + }) + + await createAuthProviders(testManager) + await createAuthUsers(testManager) + }) + + afterEach(async () => { + await MikroOrmWrapper.clearDatabase() + }) + + describe("list", () => { + it("should list authUsers", async () => { + const authUsers = await service.list() + const serialized = JSON.parse(JSON.stringify(authUsers)) + + expect(serialized).toEqual([ + expect.objectContaining({ + provider: "manual", + }), + expect.objectContaining({ + provider: "manual", + }), + expect.objectContaining({ + provider: "store", + }), + ]) + }) + + it("should list authUsers by id", async () => { + const authUsers = await service.list({ + id: ["test-id"], + }) + + expect(authUsers).toEqual([ + expect.objectContaining({ + id: "test-id", + }), + ]) + }) + + it("should list authUsers by provider_id", async () => { + const authUsers = await service.list({ + provider_id: "manual", + }) + + const serialized = JSON.parse(JSON.stringify(authUsers)) + + expect(serialized).toEqual([ + expect.objectContaining({ + id: "test-id", + }), + expect.objectContaining({ + id: "test-id-1", + }), + ]) + }) + }) + + describe("listAndCount", () => { + it("should list authUsers", async () => { + const [authUsers, count] = await service.listAndCount() + const serialized = JSON.parse(JSON.stringify(authUsers)) + + expect(count).toEqual(3) + expect(serialized).toEqual([ + expect.objectContaining({ + provider: "manual", + }), + expect.objectContaining({ + provider: "manual", + }), + expect.objectContaining({ + provider: "store", + }), + ]) + }) + + it("should listAndCount authUsers by provider_id", async () => { + const [authUsers, count] = await service.listAndCount({ + provider_id: "manual", + }) + + expect(count).toEqual(2) + expect(authUsers).toEqual([ + expect.objectContaining({ + id: "test-id", + }), + expect.objectContaining({ + id: "test-id-1", + }), + ]) + }) + }) + + describe("retrieve", () => { + const id = "test-id" + + it("should return an authUser for the given id", async () => { + const authUser = await service.retrieve(id) + + expect(authUser).toEqual( + expect.objectContaining({ + id, + }) + ) + }) + + it("should return authUser based on config select param", async () => { + const authUser = await service.retrieve(id, { + select: ["id"], + }) + + const serialized = JSON.parse(JSON.stringify(authUser)) + + expect(serialized).toEqual({ + id, + }) + }) + + it("should throw an error when an authUser with the given id does not exist", async () => { + let error + + try { + await service.retrieve("does-not-exist") + } catch (e) { + error = e + } + + expect(error.message).toEqual( + "AuthUser with id: does-not-exist was not found" + ) + }) + + it("should throw an error when a authUserId is not provided", async () => { + let error + + try { + await service.retrieve(undefined as unknown as string) + } catch (e) { + error = e + } + + expect(error.message).toEqual('"authUserId" must be defined') + }) + }) + + describe("delete", () => { + it("should delete the authUsers given an id successfully", async () => { + const id = "test-id" + + await service.delete([id]) + + const authUsers = await service.list({ + id: [id], + }) + + expect(authUsers).toHaveLength(0) + }) + }) + + describe("update", () => { + it("should throw an error when a id does not exist", async () => { + let error + + try { + await service.update([ + { + id: "does-not-exist", + }, + ]) + } catch (e) { + error = e + } + + expect(error.message).toEqual( + 'AuthUser with id "does-not-exist" not found' + ) + }) + + it("should update authUser", async () => { + const id = "test-id" + + await service.update([ + { + id, + provider_metadata: { email: "test@email.com" }, + }, + ]) + + const [authUser] = await service.list({ id: [id] }) + expect(authUser).toEqual( + expect.objectContaining({ + provider_metadata: { email: "test@email.com" }, + }) + ) + }) + }) + + describe("create", () => { + it("should create a authUser successfully", async () => { + await service.create([ + { + id: "test", + provider_id: "manual", + }, + ]) + + const [authUser] = await service.list({ + id: ["test"], + }) + + expect(authUser).toEqual( + expect.objectContaining({ + id: "test", + }) + ) + }) + }) +}) diff --git a/packages/authentication/integration-tests/__tests__/services/module/auth-provider.spec.ts b/packages/authentication/integration-tests/__tests__/services/module/auth-provider.spec.ts new file mode 100644 index 0000000000000..90fe7aeebea18 --- /dev/null +++ b/packages/authentication/integration-tests/__tests__/services/module/auth-provider.spec.ts @@ -0,0 +1,272 @@ +import { SqlEntityManager } from "@mikro-orm/postgresql" + +import { MikroOrmWrapper } from "../../../utils" +import { createAuthProviders } from "../../../__fixtures__/auth-provider" +import { IAuthenticationModuleService } from "@medusajs/types" +import { initialize } from "../../../../src" +import { DB_URL } from "@medusajs/pricing/integration-tests/utils" +import { createAuthUsers } from "../../../__fixtures__/auth-user" + +jest.setTimeout(30000) + +describe("AuthenticationModuleService - AuthProvider", () => { + let service: IAuthenticationModuleService + let testManager: SqlEntityManager + + beforeEach(async () => { + await MikroOrmWrapper.setupDatabase() + testManager = MikroOrmWrapper.forkManager() + + service = await initialize({ + database: { + clientUrl: DB_URL, + schema: process.env.MEDUSA_PRICING_DB_SCHEMA, + }, + }) + + await createAuthProviders(testManager) + await createAuthUsers(testManager) + }) + + afterEach(async () => { + await MikroOrmWrapper.clearDatabase() + }) + + describe("listAuthProviders", () => { + it("should list AuthProviders", async () => { + const authProviders = await service.listAuthProviders() + const serialized = JSON.parse(JSON.stringify(authProviders)) + + expect(serialized).toEqual([ + expect.objectContaining({ + provider: "manual", + }), + expect.objectContaining({ + provider: "disabled", + }), + expect.objectContaining({ + provider: "store", + }), + expect.objectContaining({ + provider: "admin", + }), + ]) + }) + + it("should list authProviders by id", async () => { + const authProviders = await service.listAuthProviders({ + provider: ["manual"], + }) + + expect(authProviders).toEqual([ + expect.objectContaining({ + provider: "manual", + }), + ]) + }) + + it("should list active authProviders", async () => { + const authProviders = await service.listAuthProviders({ + is_active: true, + }) + + const serialized = JSON.parse(JSON.stringify(authProviders)) + + expect(serialized).toEqual([ + expect.objectContaining({ + provider: "manual", + }), + expect.objectContaining({ + provider: "store", + }), + expect.objectContaining({ + provider: "admin", + }), + ]) + }) + }) + + describe("listAndCountAuthProviders", () => { + it("should list and count AuthProviders", async () => { + const [authProviders, count] = await service.listAndCountAuthProviders() + const serialized = JSON.parse(JSON.stringify(authProviders)) + + expect(count).toEqual(4) + expect(serialized).toEqual([ + expect.objectContaining({ + provider: "manual", + }), + expect.objectContaining({ + provider: "disabled", + }), + expect.objectContaining({ + provider: "store", + }), + expect.objectContaining({ + provider: "admin", + }), + ]) + }) + + it("should list and count authProviders by provider", async () => { + const [authProviders, count] = await service.listAndCountAuthProviders({ + provider: ["manual"], + }) + + expect(count).toEqual(1) + expect(authProviders).toEqual([ + expect.objectContaining({ + provider: "manual", + }), + ]) + }) + + it("should list and count active authProviders", async () => { + const [authProviders, count] = await service.listAndCountAuthProviders({ + is_active: true, + }) + + const serialized = JSON.parse(JSON.stringify(authProviders)) + + expect(count).toEqual(3) + expect(serialized).toEqual([ + expect.objectContaining({ + provider: "manual", + }), + expect.objectContaining({ + provider: "store", + }), + expect.objectContaining({ + provider: "admin", + }), + ]) + }) + }) + + describe("retrieveAuthProvider", () => { + const provider = "manual" + + it("should return an authProvider for the given provider", async () => { + const authProvider = await service.retrieveAuthProvider(provider) + + expect(authProvider).toEqual( + expect.objectContaining({ + provider, + }) + ) + }) + + it("should return authProvider based on config select param", async () => { + const authProvider = await service.retrieveAuthProvider(provider, { + select: ["provider"], + }) + + const serialized = JSON.parse(JSON.stringify(authProvider)) + + expect(serialized).toEqual({ + provider, + }) + }) + + it("should throw an error when an authProvider with the given provider does not exist", async () => { + let error + + try { + await service.retrieveAuthProvider("does-not-exist") + } catch (e) { + error = e + } + + expect(error.message).toEqual( + "AuthProvider with provider: does-not-exist was not found" + ) + }) + + it("should throw an error when a provider is not provided", async () => { + let error + + try { + await service.retrieveAuthProvider(undefined as unknown as string) + } catch (e) { + error = e + } + + expect(error.message).toEqual('"authProviderProvider" must be defined') + }) + }) + + describe("deleteAuthProvider", () => { + const provider = "manual" + + it("should delete the authProviders given a provider successfully", async () => { + await service.deleteAuthProvider([provider]) + + const authProviders = await service.listAuthProviders({ + provider: [provider], + }) + + expect(authProviders).toHaveLength(0) + }) + }) + + describe("updateAuthProvider", () => { + const provider = "manual" + + it("should throw an error when a id does not exist", async () => { + let error + + try { + await service.updateAuthProvider([ + { + provider: "does-not-exist", + }, + ]) + } catch (e) { + error = e + } + + expect(error.message).toEqual( + 'AuthProvider with provider "does-not-exist" not found' + ) + }) + + it("should update authProvider", async () => { + await service.updateAuthProvider([ + { + provider: "manual", + name: "test", + }, + ]) + + const [provider] = await service.listAuthProviders({ + provider: ["manual"], + }) + expect(provider).toEqual( + expect.objectContaining({ + name: "test", + }) + ) + }) + }) + + describe("createAuthProvider", () => { + it("should create a authProvider successfully", async () => { + await service.createAuthProvider([ + { + provider: "test", + name: "test provider", + }, + ]) + + const [authProvider] = await service.listAuthProviders({ + provider: ["test"], + }) + + expect(authProvider).toEqual( + expect.objectContaining({ + provider: "test", + }) + ) + }) + }) +}) diff --git a/packages/authentication/integration-tests/__tests__/services/module/auth-user.spec.ts b/packages/authentication/integration-tests/__tests__/services/module/auth-user.spec.ts new file mode 100644 index 0000000000000..14dc31ef6690b --- /dev/null +++ b/packages/authentication/integration-tests/__tests__/services/module/auth-user.spec.ts @@ -0,0 +1,255 @@ +import { SqlEntityManager } from "@mikro-orm/postgresql" + +import { MikroOrmWrapper } from "../../../utils" +import { createAuthProviders } from "../../../__fixtures__/auth-provider" +import { createAuthUsers } from "../../../__fixtures__/auth-user" +import { DB_URL } from "@medusajs/pricing/integration-tests/utils" +import { IAuthenticationModuleService } from "@medusajs/types" +import { initialize } from "../../../../src" + +jest.setTimeout(30000) + +describe("AuthenticationModuleService - AuthUser", () => { + let service: IAuthenticationModuleService + let testManager: SqlEntityManager + + beforeEach(async () => { + await MikroOrmWrapper.setupDatabase() + testManager = MikroOrmWrapper.forkManager() + + service = await initialize({ + database: { + clientUrl: DB_URL, + schema: process.env.MEDUSA_PRICING_DB_SCHEMA, + }, + }) + + await createAuthProviders(testManager) + await createAuthUsers(testManager) + }) + + afterEach(async () => { + await MikroOrmWrapper.clearDatabase() + }) + + describe("listAuthUsers", () => { + it("should list authUsers", async () => { + const authUsers = await service.listAuthUsers() + const serialized = JSON.parse(JSON.stringify(authUsers)) + + expect(serialized).toEqual([ + expect.objectContaining({ + provider: "manual", + }), + expect.objectContaining({ + provider: "manual", + }), + expect.objectContaining({ + provider: "store", + }), + ]) + }) + + it("should list authUsers by id", async () => { + const authUsers = await service.listAuthUsers({ + id: ["test-id"], + }) + + expect(authUsers).toEqual([ + expect.objectContaining({ + id: "test-id", + }), + ]) + }) + + it("should list authUsers by provider_id", async () => { + const authUsers = await service.listAuthUsers({ + provider_id: "manual", + }) + + const serialized = JSON.parse(JSON.stringify(authUsers)) + + expect(serialized).toEqual([ + expect.objectContaining({ + id: "test-id", + }), + expect.objectContaining({ + id: "test-id-1", + }), + ]) + }) + }) + + describe("listAndCountAuthUsers", () => { + it("should list and count authUsers", async () => { + const [authUsers, count] = await service.listAndCountAuthUsers() + const serialized = JSON.parse(JSON.stringify(authUsers)) + + expect(count).toEqual(3) + expect(serialized).toEqual([ + expect.objectContaining({ + provider: "manual", + }), + expect.objectContaining({ + provider: "manual", + }), + expect.objectContaining({ + provider: "store", + }), + ]) + }) + + it("should listAndCount authUsers by provider_id", async () => { + const [authUsers, count] = await service.listAndCountAuthUsers({ + provider_id: "manual", + }) + + expect(count).toEqual(2) + expect(authUsers).toEqual([ + expect.objectContaining({ + id: "test-id", + }), + expect.objectContaining({ + id: "test-id-1", + }), + ]) + }) + }) + + describe("retrieveAuthUser", () => { + const id = "test-id" + + it("should return an authUser for the given id", async () => { + const authUser = await service.retrieveAuthUser(id) + + expect(authUser).toEqual( + expect.objectContaining({ + id, + }) + ) + }) + + it("should throw an error when an authUser with the given id does not exist", async () => { + let error + + try { + await service.retrieveAuthUser("does-not-exist") + } catch (e) { + error = e + } + + expect(error.message).toEqual( + "AuthUser with id: does-not-exist was not found" + ) + }) + + it("should not return an authUser with password hash", async () => { + const authUser = await service.retrieveAuthUser("test-id-1") + + expect(authUser).toEqual( + expect.objectContaining({ + id: "test-id-1", + }) + ) + expect(authUser["password_hash"]).toEqual(undefined) + }) + + it("should throw an error when a authUserId is not provided", async () => { + let error + + try { + await service.retrieveAuthUser(undefined as unknown as string) + } catch (e) { + error = e + } + + expect(error.message).toEqual('"authUserId" must be defined') + }) + + it("should return authUser based on config select param", async () => { + const authUser = await service.retrieveAuthUser(id, { + select: ["id"], + }) + + const serialized = JSON.parse(JSON.stringify(authUser)) + + expect(serialized).toEqual({ + id, + }) + }) + }) + + describe("deleteAuthUser", () => { + const id = "test-id" + + it("should delete the authUsers given an id successfully", async () => { + await service.deleteAuthUser([id]) + + const authUsers = await service.listAuthUsers({ + id: [id], + }) + + expect(authUsers).toHaveLength(0) + }) + }) + + describe("updateAuthUser", () => { + const id = "test-id" + + it("should throw an error when a id does not exist", async () => { + let error + + try { + await service.updateAuthUser([ + { + id: "does-not-exist", + }, + ]) + } catch (e) { + error = e + } + + expect(error.message).toEqual( + 'AuthUser with id "does-not-exist" not found' + ) + }) + + it("should update authUser", async () => { + await service.updateAuthUser([ + { + id, + provider_metadata: { email: "test@email.com" }, + }, + ]) + + const [authUser] = await service.listAuthUsers({ id: [id] }) + expect(authUser).toEqual( + expect.objectContaining({ + provider_metadata: { email: "test@email.com" }, + }) + ) + }) + }) + + describe("createAuthUser", () => { + it("should create a authUser successfully", async () => { + await service.createAuthUser([ + { + id: "test", + provider_id: "manual", + }, + ]) + + const [authUser, count] = await service.listAndCountAuthUsers({ + id: ["test"], + }) + + expect(count).toEqual(1) + expect(authUser[0]).toEqual( + expect.objectContaining({ + id: "test", + }) + ) + }) + }) +}) diff --git a/packages/authentication/jest.config.js b/packages/authentication/jest.config.js index 860ba90a49c5e..456054fe8ae27 100644 --- a/packages/authentication/jest.config.js +++ b/packages/authentication/jest.config.js @@ -3,6 +3,7 @@ module.exports = { "^@models": "/src/models", "^@services": "/src/services", "^@repositories": "/src/repositories", + "^@types": "/src/types", }, transform: { "^.+\\.[jt]s?$": [ diff --git a/packages/authentication/src/initialize/index.ts b/packages/authentication/src/initialize/index.ts index 2e651756d8bce..cfd284f1e965d 100644 --- a/packages/authentication/src/initialize/index.ts +++ b/packages/authentication/src/initialize/index.ts @@ -10,7 +10,10 @@ import { moduleDefinition } from "../module-definition" import { InitializeModuleInjectableDependencies } from "../types" export const initialize = async ( - options?: ModulesSdkTypes.ModuleBootstrapDeclaration, + options?: + | ModulesSdkTypes.ModuleBootstrapDeclaration + | ModulesSdkTypes.ModuleServiceInitializeOptions + | ModulesSdkTypes.ModuleServiceInitializeCustomDataLayerOptions, injectedDependencies?: InitializeModuleInjectableDependencies ): Promise => { const loaded = await MedusaModule.bootstrap({ diff --git a/packages/authentication/src/loaders/container.ts b/packages/authentication/src/loaders/container.ts index 22b26b451c161..d9d36e6daf828 100644 --- a/packages/authentication/src/loaders/container.ts +++ b/packages/authentication/src/loaders/container.ts @@ -1,4 +1,5 @@ import * as defaultRepositories from "@repositories" +import * as defaultServices from "@services" import { LoaderOptions } from "@medusajs/modules-sdk" import { ModulesSdkTypes } from "@medusajs/types" @@ -17,7 +18,10 @@ export default async ({ )?.repositories container.register({ - // authenticationService: asClass(defaultServices.AuthenticationService).singleton(), + authUserService: asClass(defaultServices.AuthUserService).singleton(), + authProviderService: asClass( + defaultServices.AuthProviderService + ).singleton(), }) if (customRepositories) { @@ -34,5 +38,11 @@ export default async ({ function loadDefaultRepositories({ container }) { container.register({ baseRepository: asClass(defaultRepositories.BaseRepository).singleton(), + authUserRepository: asClass( + defaultRepositories.AuthUserRepository + ).singleton(), + authProviderRepository: asClass( + defaultRepositories.AuthProviderRepository + ).singleton(), }) } diff --git a/packages/authentication/src/migrations/.snapshot-medusa-authentication.json b/packages/authentication/src/migrations/.snapshot-medusa-authentication.json index c7d860b7427ac..0d3f1eaba19f5 100644 --- a/packages/authentication/src/migrations/.snapshot-medusa-authentication.json +++ b/packages/authentication/src/migrations/.snapshot-medusa-authentication.json @@ -4,6 +4,68 @@ ], "name": "public", "tables": [ + { + "columns": { + "provider": { + "name": "provider", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "name": { + "name": "name", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "domain": { + "name": "domain", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "default": "'all'", + "enumItems": [ + "all", + "store", + "admin" + ], + "mappedType": "enum" + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "default": "false", + "mappedType": "boolean" + } + }, + "name": "auth_provider", + "schema": "public", + "indexes": [ + { + "keyName": "auth_provider_pkey", + "columnNames": [ + "provider" + ], + "composite": false, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": {} + }, { "columns": { "id": { @@ -14,6 +76,42 @@ "primary": false, "nullable": false, "mappedType": "text" + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "user_metadata": { + "name": "user_metadata", + "type": "jsonb", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "json" + }, + "app_metadata": { + "name": "app_metadata", + "type": "jsonb", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "json" + }, + "provider_metadata": { + "name": "provider_metadata", + "type": "jsonb", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "json" } }, "name": "auth_user", @@ -30,7 +128,20 @@ } ], "checks": [], - "foreignKeys": {} + "foreignKeys": { + "auth_user_provider_id_foreign": { + "constraintName": "auth_user_provider_id_foreign", + "columnNames": [ + "provider_id" + ], + "localTableName": "public.auth_user", + "referencedColumnNames": [ + "provider" + ], + "referencedTableName": "public.auth_provider", + "deleteRule": "cascade" + } + } } ] } diff --git a/packages/authentication/src/migrations/Migration20231220132440.ts b/packages/authentication/src/migrations/Migration20231220132440.ts deleted file mode 100644 index 0ff613b274556..0000000000000 --- a/packages/authentication/src/migrations/Migration20231220132440.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Migration } from '@mikro-orm/migrations'; - -export class Migration20231220132440 extends Migration { - - async up(): Promise { - this.addSql('create table "auth_user" ("id" text not null, constraint "auth_user_pkey" primary key ("id"));'); - } - - async down(): Promise { - this.addSql('drop table if exists "auth_user" cascade;'); - } - -} diff --git a/packages/authentication/src/migrations/Migration20240104154451.ts b/packages/authentication/src/migrations/Migration20240104154451.ts new file mode 100644 index 0000000000000..2ffaf00de1668 --- /dev/null +++ b/packages/authentication/src/migrations/Migration20240104154451.ts @@ -0,0 +1,21 @@ +import { Migration } from '@mikro-orm/migrations'; + +export class Migration20240104154451 extends Migration { + + async up(): Promise { + this.addSql('create table "auth_provider" ("provider" text not null, "name" text not null, "domain" text check ("domain" in (\'all\', \'store\', \'admin\')) not null default \'all\', "is_active" boolean not null default false, constraint "auth_provider_pkey" primary key ("provider"));'); + + this.addSql('create table "auth_user" ("id" text not null, "provider_id" text null, "user_metadata" jsonb null, "app_metadata" jsonb null, "provider_metadata" jsonb null, constraint "auth_user_pkey" primary key ("id"));'); + + this.addSql('alter table "auth_user" add constraint "auth_user_provider_id_foreign" foreign key ("provider_id") references "auth_provider" ("provider") on delete cascade;'); + } + + async down(): Promise { + this.addSql('alter table "auth_user" drop constraint "auth_user_provider_id_foreign";'); + + this.addSql('drop table if exists "auth_provider" cascade;'); + + this.addSql('drop table if exists "auth_user" cascade;'); + } + +} diff --git a/packages/authentication/src/models/auth-provider.ts b/packages/authentication/src/models/auth-provider.ts new file mode 100644 index 0000000000000..8febe9ff6f62f --- /dev/null +++ b/packages/authentication/src/models/auth-provider.ts @@ -0,0 +1,27 @@ +import { + Entity, + Enum, + OptionalProps, + PrimaryKey, + Property, +} from "@mikro-orm/core" +import { ProviderDomain } from "../types/repositories/auth-provider" + +type OptionalFields = "domain" | "is_active" + +@Entity() +export default class AuthProvider { + [OptionalProps]: OptionalFields + + @PrimaryKey({ columnType: "text" }) + provider!: string + + @Property({ columnType: "text" }) + name: string + + @Enum({ items: () => ProviderDomain, default: ProviderDomain.ALL }) + domain: ProviderDomain = ProviderDomain.ALL + + @Property({ columnType: "boolean", default: false }) + is_active = false +} diff --git a/packages/authentication/src/models/auth-user.ts b/packages/authentication/src/models/auth-user.ts index 72f165a84a73a..e90cb10130c03 100644 --- a/packages/authentication/src/models/auth-user.ts +++ b/packages/authentication/src/models/auth-user.ts @@ -1,11 +1,41 @@ import { generateEntityId } from "@medusajs/utils" -import { BeforeCreate, Entity, OnInit, PrimaryKey } from "@mikro-orm/core" +import { + BeforeCreate, + Cascade, + Entity, + ManyToOne, + OnInit, + OptionalProps, + PrimaryKey, + Property, +} from "@mikro-orm/core" +import AuthProvider from "./auth-provider" + +type OptionalFields = "provider_metadata" | "app_metadata" | "user_metadata" @Entity() export default class AuthUser { + [OptionalProps]: OptionalFields + @PrimaryKey({ columnType: "text" }) id!: string + @ManyToOne(() => AuthProvider, { + joinColumn: "provider", + fieldName: "provider_id", + cascade: [Cascade.REMOVE], + }) + provider: AuthProvider + + @Property({ columnType: "jsonb", nullable: true }) + user_metadata: Record | null + + @Property({ columnType: "jsonb", nullable: true }) + app_metadata: Record | null + + @Property({ columnType: "jsonb", nullable: true }) + provider_metadata: Record | null + @BeforeCreate() onCreate() { this.id = generateEntityId(this.id, "authusr") diff --git a/packages/authentication/src/models/index.ts b/packages/authentication/src/models/index.ts index e5b8ee0f1c6cf..56d27c3d137dc 100644 --- a/packages/authentication/src/models/index.ts +++ b/packages/authentication/src/models/index.ts @@ -1 +1,2 @@ export { default as AuthUser } from "./auth-user" +export { default as AuthProvider } from "./auth-provider" diff --git a/packages/authentication/src/repositories/auth-provider.ts b/packages/authentication/src/repositories/auth-provider.ts new file mode 100644 index 0000000000000..b3c16f1fb2b2b --- /dev/null +++ b/packages/authentication/src/repositories/auth-provider.ts @@ -0,0 +1,98 @@ +import { Context, DAL } from "@medusajs/types" +import { DALUtils } from "@medusajs/utils" +import { + FilterQuery as MikroFilterQuery, + FindOptions as MikroOptions, + LoadStrategy, +} from "@mikro-orm/core" + +import { AuthProvider } from "@models" +import { RepositoryTypes } from "@types" +import { SqlEntityManager } from "@mikro-orm/postgresql" + +export class AuthProviderRepository extends DALUtils.MikroOrmBaseRepository { + protected readonly manager_: SqlEntityManager + + constructor({ manager }: { manager: SqlEntityManager }) { + // @ts-ignore + // eslint-disable-next-line prefer-rest-params + super(...arguments) + this.manager_ = manager + } + + async find( + findOptions: DAL.FindOptions = { where: {} }, + context: Context = {} + ): Promise { + const manager = this.getActiveManager(context) + + const findOptions_ = { ...findOptions } + findOptions_.options ??= {} + + Object.assign(findOptions_.options, { + strategy: LoadStrategy.SELECT_IN, + }) + + return await manager.find( + AuthProvider, + findOptions_.where as MikroFilterQuery, + findOptions_.options as MikroOptions + ) + } + + async findAndCount( + findOptions: DAL.FindOptions = { where: {} }, + context: Context = {} + ): Promise<[AuthProvider[], number]> { + const manager = this.getActiveManager(context) + + const findOptions_ = { ...findOptions } + findOptions_.options ??= {} + + Object.assign(findOptions_.options, { + strategy: LoadStrategy.SELECT_IN, + }) + + return await manager.findAndCount( + AuthProvider, + findOptions_.where as MikroFilterQuery, + findOptions_.options as MikroOptions + ) + } + + async delete(ids: string[], context: Context = {}): Promise { + const manager = this.getActiveManager(context) + await manager.nativeDelete(AuthProvider, { provider: { $in: ids } }, {}) + } + + async create( + data: RepositoryTypes.CreateAuthProviderDTO[], + context: Context = {} + ): Promise { + const manager: SqlEntityManager = + this.getActiveManager(context) + + const authProviders = data.map((authProviderData) => { + return manager.create(AuthProvider, authProviderData) + }) + + manager.persist(authProviders) + + return authProviders + } + + async update( + data: RepositoryTypes.UpdateAuthProviderDTO[], + context: Context = {} + ): Promise { + const manager = this.getActiveManager(context) + + const authProviders = data.map(({ provider, update }) => { + return manager.assign(provider, update) + }) + + manager.persist(authProviders) + + return authProviders + } +} diff --git a/packages/authentication/src/repositories/auth-user.ts b/packages/authentication/src/repositories/auth-user.ts new file mode 100644 index 0000000000000..0ca1ec0091e36 --- /dev/null +++ b/packages/authentication/src/repositories/auth-user.ts @@ -0,0 +1,106 @@ +import { Context, DAL } from "@medusajs/types" +import { DALUtils } from "@medusajs/utils" +import { + FilterQuery as MikroFilterQuery, + FindOptions as MikroOptions, + LoadStrategy, +} from "@mikro-orm/core" + +import { AuthUser } from "@models" +import { RepositoryTypes } from "@types" +import { SqlEntityManager } from "@mikro-orm/postgresql" + +export class AuthUserRepository extends DALUtils.MikroOrmBaseRepository { + protected readonly manager_: SqlEntityManager + + constructor({ manager }: { manager: SqlEntityManager }) { + // @ts-ignore + // eslint-disable-next-line prefer-rest-params + super(...arguments) + this.manager_ = manager + } + + async find( + findOptions: DAL.FindOptions = { where: {} }, + context: Context = {} + ): Promise { + const manager = this.getActiveManager(context) + + const findOptions_ = { ...findOptions } + findOptions_.options ??= {} + + Object.assign(findOptions_.options, { + strategy: LoadStrategy.SELECT_IN, + }) + + return await manager.find( + AuthUser, + findOptions_.where as MikroFilterQuery, + findOptions_.options as MikroOptions + ) + } + + async findAndCount( + findOptions: DAL.FindOptions = { where: {} }, + context: Context = {} + ): Promise<[AuthUser[], number]> { + const manager = this.getActiveManager(context) + + const findOptions_ = { ...findOptions } + findOptions_.options ??= {} + + Object.assign(findOptions_.options, { + strategy: LoadStrategy.SELECT_IN, + }) + + return await manager.findAndCount( + AuthUser, + findOptions_.where as MikroFilterQuery, + findOptions_.options as MikroOptions + ) + } + + async delete(ids: string[], context: Context = {}): Promise { + const manager = this.getActiveManager(context) + await manager.nativeDelete(AuthUser, { id: { $in: ids } }, {}) + } + + async create( + data: RepositoryTypes.CreateAuthUserDTO[], + context: Context = {} + ): Promise { + const manager: SqlEntityManager = + this.getActiveManager(context) + + const toCreate = data.map((authUser) => { + const authUserClone = { ...authUser } as any + + authUserClone.provider ??= authUser.provider_id + + return authUserClone + }) + + const authUsers = toCreate.map((authUserData) => { + return manager.create(AuthUser, authUserData) + }) + + manager.persist(authUsers) + + return authUsers + } + + async update( + data: RepositoryTypes.UpdateAuthUserDTO[], + context: Context = {} + ): Promise { + const manager = this.getActiveManager(context) + + const authUsers = data.map(({ user, update }) => { + return manager.assign(user, update) + }) + + manager.persist(authUsers) + + return authUsers + } +} diff --git a/packages/authentication/src/repositories/index.ts b/packages/authentication/src/repositories/index.ts index 147c9cc259fa4..506db731aa7b4 100644 --- a/packages/authentication/src/repositories/index.ts +++ b/packages/authentication/src/repositories/index.ts @@ -1 +1,3 @@ export { MikroOrmBaseRepository as BaseRepository } from "@medusajs/utils" +export { AuthProviderRepository } from "./auth-provider" +export { AuthUserRepository } from "./auth-user" diff --git a/packages/authentication/src/services/auth-provider.ts b/packages/authentication/src/services/auth-provider.ts new file mode 100644 index 0000000000000..ee6ccd8b86e23 --- /dev/null +++ b/packages/authentication/src/services/auth-provider.ts @@ -0,0 +1,139 @@ +import { Context, DAL, FindConfig } from "@medusajs/types" +import { + InjectManager, + InjectTransactionManager, + MedusaContext, + MedusaError, + ModulesSdkUtils, + retrieveEntity, +} from "@medusajs/utils" +import { AuthProvider } from "@models" +import { AuthProviderRepository } from "@repositories" + +import { RepositoryTypes, ServiceTypes } from "@types" + +type InjectedDependencies = { + authProviderRepository: DAL.RepositoryService +} + +export default class AuthProviderService< + TEntity extends AuthProvider = AuthProvider +> { + protected readonly authProviderRepository_: DAL.RepositoryService + + constructor({ authProviderRepository }: InjectedDependencies) { + this.authProviderRepository_ = authProviderRepository + } + + @InjectManager("authProviderRepository_") + async retrieve( + provider: string, + config: FindConfig = {}, + @MedusaContext() sharedContext: Context = {} + ): Promise { + return (await retrieveEntity({ + id: provider, + identifierColumn: "provider", + entityName: AuthProvider.name, + repository: this.authProviderRepository_, + config, + sharedContext, + })) as TEntity + } + + @InjectManager("authProviderRepository_") + async list( + filters: ServiceTypes.FilterableAuthProviderProps = {}, + config: FindConfig = {}, + @MedusaContext() sharedContext: Context = {} + ): Promise { + const queryConfig = ModulesSdkUtils.buildQuery( + filters, + config + ) + + return (await this.authProviderRepository_.find( + queryConfig, + sharedContext + )) as TEntity[] + } + + @InjectManager("authProviderRepository_") + async listAndCount( + filters: ServiceTypes.FilterableAuthProviderProps = {}, + config: FindConfig = {}, + @MedusaContext() sharedContext: Context = {} + ): Promise<[TEntity[], number]> { + const queryConfig = ModulesSdkUtils.buildQuery( + filters, + config + ) + + return (await this.authProviderRepository_.findAndCount( + queryConfig, + sharedContext + )) as [TEntity[], number] + } + + @InjectTransactionManager("authProviderRepository_") + async create( + data: ServiceTypes.CreateAuthProviderDTO[], + @MedusaContext() sharedContext: Context = {} + ): Promise { + return (await ( + this.authProviderRepository_ as AuthProviderRepository + ).create(data, sharedContext)) as TEntity[] + } + + @InjectTransactionManager("authProviderRepository_") + async update( + data: ServiceTypes.UpdateAuthProviderDTO[], + @MedusaContext() sharedContext: Context = {} + ): Promise { + const authProviderIds = data.map( + (authProviderData) => authProviderData.provider + ) + + const existingAuthProviders = await this.list( + { + provider: authProviderIds, + }, + {}, + sharedContext + ) + + const updates: RepositoryTypes.UpdateAuthProviderDTO[] = [] + + const existingAuthProvidersMap = new Map( + existingAuthProviders.map<[string, AuthProvider]>((authProvider) => [ + authProvider.provider, + authProvider, + ]) + ) + + for (const update of data) { + const provider = existingAuthProvidersMap.get(update.provider) + + if (!provider) { + throw new MedusaError( + MedusaError.Types.NOT_FOUND, + `AuthProvider with provider "${update.provider}" not found` + ) + } + + updates.push({ update, provider }) + } + + return (await ( + this.authProviderRepository_ as AuthProviderRepository + ).update(updates, sharedContext)) as TEntity[] + } + + @InjectTransactionManager("authProviderRepository_") + async delete( + ids: string[], + @MedusaContext() sharedContext: Context = {} + ): Promise { + await this.authProviderRepository_.delete(ids, sharedContext) + } +} diff --git a/packages/authentication/src/services/auth-user.ts b/packages/authentication/src/services/auth-user.ts new file mode 100644 index 0000000000000..a7573852b7e95 --- /dev/null +++ b/packages/authentication/src/services/auth-user.ts @@ -0,0 +1,126 @@ +import { Context, DAL, FindConfig } from "@medusajs/types" +import { + InjectManager, + InjectTransactionManager, + MedusaContext, + MedusaError, + ModulesSdkUtils, + retrieveEntity, +} from "@medusajs/utils" +import { AuthUser } from "@models" +import { AuthUserRepository } from "@repositories" + +import { RepositoryTypes, ServiceTypes } from "@types" + +type InjectedDependencies = { + authUserRepository: DAL.RepositoryService +} + +export default class AuthUserService { + protected readonly authUserRepository_: DAL.RepositoryService + + constructor({ authUserRepository }: InjectedDependencies) { + this.authUserRepository_ = authUserRepository + } + + @InjectManager("authUserRepository_") + async retrieve( + provider: string, + config: FindConfig = {}, + @MedusaContext() sharedContext: Context = {} + ): Promise { + return (await retrieveEntity({ + id: provider, + entityName: AuthUser.name, + repository: this.authUserRepository_, + config, + sharedContext, + })) as TEntity + } + + @InjectManager("authUserRepository_") + async list( + filters: ServiceTypes.FilterableAuthProviderProps = {}, + config: FindConfig = {}, + @MedusaContext() sharedContext: Context = {} + ): Promise { + const queryConfig = ModulesSdkUtils.buildQuery(filters, config) + + return (await this.authUserRepository_.find( + queryConfig, + sharedContext + )) as TEntity[] + } + + @InjectManager("authUserRepository_") + async listAndCount( + filters: ServiceTypes.FilterableAuthUserProps = {}, + config: FindConfig = {}, + @MedusaContext() sharedContext: Context = {} + ): Promise<[TEntity[], number]> { + const queryConfig = ModulesSdkUtils.buildQuery(filters, config) + + return (await this.authUserRepository_.findAndCount( + queryConfig, + sharedContext + )) as [TEntity[], number] + } + + @InjectTransactionManager("authUserRepository_") + async create( + data: ServiceTypes.CreateAuthUserDTO[], + @MedusaContext() sharedContext: Context = {} + ): Promise { + return (await (this.authUserRepository_ as AuthUserRepository).create( + data, + sharedContext + )) as TEntity[] + } + + @InjectTransactionManager("authUserRepository_") + async update( + data: ServiceTypes.UpdateAuthUserDTO[], + @MedusaContext() sharedContext: Context = {} + ): Promise { + const existingUsers = await this.list( + { id: data.map(({ id }) => id) }, + {}, + sharedContext + ) + + const existingUsersMap = new Map( + existingUsers.map<[string, AuthUser]>((authUser) => [ + authUser.id, + authUser, + ]) + ) + + const updates: RepositoryTypes.UpdateAuthUserDTO[] = [] + + for (const update of data) { + const user = existingUsersMap.get(update.id) + + if (!user) { + throw new MedusaError( + MedusaError.Types.NOT_FOUND, + `AuthUser with id "${update.id}" not found` + ) + } + + updates.push({ update, user }) + } + + return (await (this.authUserRepository_ as AuthUserRepository).update( + updates, + sharedContext + )) as TEntity[] + } + + @InjectTransactionManager("authUserRepository_") + async delete( + ids: string[], + @MedusaContext() sharedContext: Context = {} + ): Promise { + await this.authUserRepository_.delete(ids, sharedContext) + } +} diff --git a/packages/authentication/src/services/authentication-module.ts b/packages/authentication/src/services/authentication-module.ts index 5e48f4ba12cca..d47c094b3e1b1 100644 --- a/packages/authentication/src/services/authentication-module.ts +++ b/packages/authentication/src/services/authentication-module.ts @@ -1,29 +1,339 @@ import { AuthenticationTypes, + Context, DAL, + FindConfig, InternalModuleDeclaration, ModuleJoinerConfig, } from "@medusajs/types" -import { AuthUser } from "@models" +import { AuthProvider, AuthUser } from "@models" import { joinerConfig } from "../joiner-config" +import { AuthProviderService, AuthUserService } from "@services" +import { + InjectManager, + InjectTransactionManager, + MedusaContext, +} from "@medusajs/utils" +import { + AuthProviderDTO, + AuthUserDTO, + CreateAuthProviderDTO, + CreateAuthUserDTO, + FilterableAuthProviderProps, + FilterableAuthUserProps, + UpdateAuthUserDTO, +} from "@medusajs/types/dist/authentication/common" type InjectedDependencies = { baseRepository: DAL.RepositoryService + authUserService: AuthUserService + authProviderService: AuthProviderService } export default class AuthenticationModuleService< - TAuthUser extends AuthUser = AuthUser + TAuthUser extends AuthUser = AuthUser, + TAuthProvider extends AuthProvider = AuthProvider > implements AuthenticationTypes.IAuthenticationModuleService { protected baseRepository_: DAL.RepositoryService + protected authUserService_: AuthUserService + protected authProviderService_: AuthProviderService + constructor( - { baseRepository }: InjectedDependencies, + { + authUserService, + authProviderService, + baseRepository, + }: InjectedDependencies, protected readonly moduleDeclaration: InternalModuleDeclaration ) { this.baseRepository_ = baseRepository + this.authUserService_ = authUserService + this.authProviderService_ = authProviderService + } + + async retrieveAuthProvider( + provider: string, + config: FindConfig = {}, + sharedContext: Context = {} + ): Promise { + const authProvider = await this.authProviderService_.retrieve( + provider, + config, + sharedContext + ) + + return await this.baseRepository_.serialize( + authProvider, + { populate: true } + ) + } + + async listAuthProviders( + filters: FilterableAuthProviderProps = {}, + config: FindConfig = {}, + sharedContext: Context = {} + ): Promise { + const authProviders = await this.authProviderService_.list( + filters, + config, + sharedContext + ) + + return await this.baseRepository_.serialize< + AuthenticationTypes.AuthProviderDTO[] + >(authProviders, { populate: true }) + } + + @InjectManager("baseRepository_") + async listAndCountAuthProviders( + filters: FilterableAuthProviderProps = {}, + config: FindConfig, + @MedusaContext() sharedContext: Context = {} + ): Promise<[AuthenticationTypes.AuthProviderDTO[], number]> { + const [authProviders, count] = await this.authProviderService_.listAndCount( + filters, + config, + sharedContext + ) + + return [ + await this.baseRepository_.serialize< + AuthenticationTypes.AuthProviderDTO[] + >(authProviders, { populate: true }), + count, + ] + } + + async createAuthProvider( + data: CreateAuthProviderDTO[], + sharedContext?: Context + ): Promise + + async createAuthProvider( + data: CreateAuthProviderDTO, + sharedContext?: Context + ): Promise + + @InjectManager("baseRepository_") + async createAuthProvider( + data: CreateAuthProviderDTO | CreateAuthProviderDTO[], + @MedusaContext() sharedContext: Context = {} + ): Promise< + AuthenticationTypes.AuthProviderDTO | AuthenticationTypes.AuthProviderDTO[] + > { + const input = Array.isArray(data) ? data : [data] + + const providers = await this.createAuthProviders_(input, sharedContext) + + const serializedProviders = await this.baseRepository_.serialize< + AuthenticationTypes.AuthProviderDTO[] + >(providers, { + populate: true, + }) + + return Array.isArray(data) ? serializedProviders : serializedProviders[0] + } + + @InjectTransactionManager("baseRepository_") + protected async createAuthProviders_( + data: any[], + @MedusaContext() sharedContext: Context + ): Promise { + return await this.authProviderService_.create(data, sharedContext) + } + + updateAuthProvider( + data: AuthenticationTypes.UpdateAuthProviderDTO[], + sharedContext?: Context + ): Promise + updateAuthProvider( + data: AuthenticationTypes.UpdateAuthProviderDTO, + sharedContext?: Context + ): Promise + + @InjectManager("baseRepository_") + async updateAuthProvider( + data: + | AuthenticationTypes.UpdateAuthProviderDTO[] + | AuthenticationTypes.UpdateAuthProviderDTO, + @MedusaContext() sharedContext: Context = {} + ): Promise< + AuthenticationTypes.AuthProviderDTO | AuthenticationTypes.AuthProviderDTO[] + > { + const input = Array.isArray(data) ? data : [data] + + const providers = await this.updateAuthProvider_(input, sharedContext) + + const serializedProviders = await this.baseRepository_.serialize< + AuthenticationTypes.AuthProviderDTO[] + >(providers, { + populate: true, + }) + + return Array.isArray(data) ? serializedProviders : serializedProviders[0] + } + + async updateAuthProvider_( + data: AuthenticationTypes.UpdateAuthProviderDTO[], + @MedusaContext() sharedContext: Context = {} + ): Promise { + return await this.authProviderService_.update(data, sharedContext) + } + + @InjectTransactionManager("baseRepository_") + async deleteAuthProvider( + ids: string[], + @MedusaContext() sharedContext: Context = {} + ): Promise { + await this.authProviderService_.delete(ids, sharedContext) + } + + @InjectManager("baseRepository_") + async retrieveAuthUser( + id: string, + config: FindConfig = {}, + @MedusaContext() sharedContext: Context = {} + ): Promise { + const authUser = await this.authUserService_.retrieve( + id, + config, + sharedContext + ) + + return await this.baseRepository_.serialize( + authUser, + { + exclude: ["password_hash"], + } + ) + } + + @InjectManager("baseRepository_") + async listAuthUsers( + filters: FilterableAuthUserProps = {}, + config: FindConfig = {}, + @MedusaContext() sharedContext: Context = {} + ): Promise { + const authUsers = await this.authUserService_.list( + filters, + config, + sharedContext + ) + + return await this.baseRepository_.serialize< + AuthenticationTypes.AuthUserDTO[] + >(authUsers, { + populate: true, + }) + } + + @InjectManager("baseRepository_") + async listAndCountAuthUsers( + filters: FilterableAuthUserProps = {}, + config: FindConfig = {}, + @MedusaContext() sharedContext: Context = {} + ): Promise<[AuthUserDTO[], number]> { + const [authUsers, count] = await this.authUserService_.listAndCount( + filters, + config, + sharedContext + ) + + return [ + await this.baseRepository_.serialize( + authUsers, + { + populate: true, + } + ), + count, + ] + } + + createAuthUser( + data: CreateAuthUserDTO[], + sharedContext?: Context + ): Promise + createAuthUser( + data: CreateAuthUserDTO, + sharedContext?: Context + ): Promise + + @InjectManager("baseRepository_") + async createAuthUser( + data: CreateAuthUserDTO[] | CreateAuthUserDTO, + @MedusaContext() sharedContext: Context = {} + ): Promise< + AuthenticationTypes.AuthUserDTO | AuthenticationTypes.AuthUserDTO[] + > { + const input = Array.isArray(data) ? data : [data] + + const authUsers = await this.createAuthUsers_(input, sharedContext) + + const serializedUsers = await this.baseRepository_.serialize< + AuthenticationTypes.AuthUserDTO[] + >(authUsers, { + populate: true, + }) + + return Array.isArray(data) ? serializedUsers : serializedUsers[0] + } + + @InjectTransactionManager("baseRepository_") + protected async createAuthUsers_( + data: CreateAuthUserDTO[], + @MedusaContext() sharedContext: Context + ): Promise { + return await this.authUserService_.create(data, sharedContext) + } + + updateAuthUser( + data: UpdateAuthUserDTO[], + sharedContext?: Context + ): Promise + updateAuthUser( + data: UpdateAuthUserDTO, + sharedContext?: Context + ): Promise + + @InjectManager("baseRepository_") + async updateAuthUser( + data: UpdateAuthUserDTO | UpdateAuthUserDTO[], + @MedusaContext() sharedContext: Context = {} + ): Promise< + AuthenticationTypes.AuthUserDTO | AuthenticationTypes.AuthUserDTO[] + > { + const input = Array.isArray(data) ? data : [data] + + const updatedUsers = await this.updateAuthUsers_(input, sharedContext) + + const serializedUsers = await this.baseRepository_.serialize< + AuthenticationTypes.AuthUserDTO[] + >(updatedUsers, { + populate: true, + }) + + return Array.isArray(data) ? serializedUsers : serializedUsers[0] + } + + @InjectTransactionManager("baseRepository_") + protected async updateAuthUsers_( + data: UpdateAuthUserDTO[], + @MedusaContext() sharedContext: Context + ): Promise { + return await this.authUserService_.update(data, sharedContext) + } + + @InjectTransactionManager("baseRepository_") + async deleteAuthUser( + ids: string[], + @MedusaContext() sharedContext: Context = {} + ): Promise { + await this.authUserService_.delete(ids, sharedContext) } __joinerConfig(): ModuleJoinerConfig { diff --git a/packages/authentication/src/services/index.ts b/packages/authentication/src/services/index.ts index 70e0f87be053d..bacc64fd0e427 100644 --- a/packages/authentication/src/services/index.ts +++ b/packages/authentication/src/services/index.ts @@ -1 +1,3 @@ export { default as AuthenticationModuleService } from "./authentication-module" +export { default as AuthProviderService } from "./auth-provider" +export { default as AuthUserService } from "./auth-user" diff --git a/packages/authentication/src/types/index.ts b/packages/authentication/src/types/index.ts index 0f252977b02a2..4ed2a2c559e32 100644 --- a/packages/authentication/src/types/index.ts +++ b/packages/authentication/src/types/index.ts @@ -3,3 +3,6 @@ import { Logger } from "@medusajs/types" export type InitializeModuleInjectableDependencies = { logger?: Logger } + +export * as RepositoryTypes from "./repositories" +export * as ServiceTypes from "./services" diff --git a/packages/authentication/src/types/repositories/auth-provider.ts b/packages/authentication/src/types/repositories/auth-provider.ts new file mode 100644 index 0000000000000..31f5ce087a5f9 --- /dev/null +++ b/packages/authentication/src/types/repositories/auth-provider.ts @@ -0,0 +1,24 @@ +import { AuthProvider } from "@models" + +export type CreateAuthProviderDTO = { + provider: string + name: string + domain?: ProviderDomain + is_active?: boolean +} + +export type UpdateAuthProviderDTO = { + update: { + provider: string + name?: string + domain?: ProviderDomain + is_active?: boolean + } + provider: AuthProvider +} + +export enum ProviderDomain { + ALL = "all", + STORE = "store", + ADMIN = "admin", +} diff --git a/packages/authentication/src/types/repositories/auth-user.ts b/packages/authentication/src/types/repositories/auth-user.ts new file mode 100644 index 0000000000000..33315b20e1600 --- /dev/null +++ b/packages/authentication/src/types/repositories/auth-user.ts @@ -0,0 +1,18 @@ +import { AuthUser } from "@models" + +export type CreateAuthUserDTO = { + provider_id: string + provider_metadata?: Record + user_metadata?: Record + app_metadata?: Record +} + +export type UpdateAuthUserDTO = { + update: { + id: string + provider_metadata?: Record + user_metadata?: Record + app_metadata?: Record + } + user: AuthUser +} diff --git a/packages/authentication/src/types/repositories/index.ts b/packages/authentication/src/types/repositories/index.ts new file mode 100644 index 0000000000000..b4282c985c6a3 --- /dev/null +++ b/packages/authentication/src/types/repositories/index.ts @@ -0,0 +1,2 @@ +export * from "./auth-user" +export * from "./auth-provider" diff --git a/packages/authentication/src/types/services/auth-provider.ts b/packages/authentication/src/types/services/auth-provider.ts new file mode 100644 index 0000000000000..8f1307ab07c29 --- /dev/null +++ b/packages/authentication/src/types/services/auth-provider.ts @@ -0,0 +1,28 @@ +export type AuthProviderDTO = { + provider: string + name: string + domain: ProviderDomain + is_active: boolean +} + +export type CreateAuthProviderDTO = { + provider: string + name: string + domain?: ProviderDomain + is_active?: boolean +} + +export type UpdateAuthProviderDTO = { + provider: string + name?: string + domain?: ProviderDomain + is_active?: boolean +} + +export enum ProviderDomain { + ALL = "all", + STORE = "store", + ADMIN = "admin", +} + +export type FilterableAuthProviderProps = {} diff --git a/packages/authentication/src/types/services/auth-user.ts b/packages/authentication/src/types/services/auth-user.ts new file mode 100644 index 0000000000000..18c8899303ee6 --- /dev/null +++ b/packages/authentication/src/types/services/auth-user.ts @@ -0,0 +1,26 @@ +import { AuthProviderDTO } from "./auth-provider" + +export type AuthUserDTO = { + id: string + provider_id: string + provider: AuthProviderDTO + provider_metadata?: Record + user_metadata: Record + app_metadata: Record +} + +export type CreateAuthUserDTO = { + provider_id: string + provider_metadata?: Record + user_metadata?: Record + app_metadata?: Record +} + +export type UpdateAuthUserDTO = { + id: string + provider_metadata?: Record + user_metadata?: Record + app_metadata?: Record +} + +export type FilterableAuthUserProps = {} diff --git a/packages/authentication/src/types/services/index.ts b/packages/authentication/src/types/services/index.ts new file mode 100644 index 0000000000000..b4282c985c6a3 --- /dev/null +++ b/packages/authentication/src/types/services/index.ts @@ -0,0 +1,2 @@ +export * from "./auth-user" +export * from "./auth-provider" diff --git a/packages/authentication/tsconfig.json b/packages/authentication/tsconfig.json index 213e38fc559c0..7fd528e0e3ecc 100644 --- a/packages/authentication/tsconfig.json +++ b/packages/authentication/tsconfig.json @@ -1,6 +1,8 @@ { "compilerOptions": { - "lib": ["es2020"], + "lib": [ + "es2020" + ], "target": "es2020", "outDir": "./dist", "esModuleInterop": true, @@ -16,16 +18,28 @@ "noImplicitThis": true, "allowJs": true, "skipLibCheck": true, - "downlevelIteration": true, // to use ES5 specific tooling + "downlevelIteration": true, + // to use ES5 specific tooling "baseUrl": ".", "resolveJsonModule": true, "paths": { - "@models": ["./src/models"], - "@services": ["./src/services"], - "@repositories": ["./src/repositories"] + "@models": [ + "./src/models" + ], + "@services": [ + "./src/services" + ], + "@repositories": [ + "./src/repositories" + ], + "@types": [ + "./src/types" + ] } }, - "include": ["src"], + "include": [ + "src" + ], "exclude": [ "dist", "./src/**/__tests__", diff --git a/packages/types/src/authentication/common/auth-provider.ts b/packages/types/src/authentication/common/auth-provider.ts new file mode 100644 index 0000000000000..f39d01b21755f --- /dev/null +++ b/packages/types/src/authentication/common/auth-provider.ts @@ -0,0 +1,36 @@ +import { BaseFilterable } from "../../dal" + +export type AuthProviderDTO = { + provider: string + name: string + domain: ProviderDomain + is_active: boolean +} + +export type CreateAuthProviderDTO = { + provider: string + name: string + domain?: ProviderDomain + is_active?: boolean +} + +export type UpdateAuthProviderDTO = { + provider: string + name?: string + domain?: ProviderDomain + is_active?: boolean +} + +export enum ProviderDomain { + ALL = "all", + STORE = "store", + ADMIN = "admin", +} + +export interface FilterableAuthProviderProps + extends BaseFilterable { + provider?: string[] + is_active?: boolean + domain?: ProviderDomain[] + name?: string[] +} diff --git a/packages/types/src/authentication/common/auth-user.ts b/packages/types/src/authentication/common/auth-user.ts new file mode 100644 index 0000000000000..ae5b03b0fe253 --- /dev/null +++ b/packages/types/src/authentication/common/auth-user.ts @@ -0,0 +1,31 @@ +import { BaseFilterable } from "../../dal" +import { AuthProviderDTO } from "./auth-provider" + +export type AuthUserDTO = { + id: string + provider_id: string + provider: AuthProviderDTO + provider_metadata?: Record + user_metadata: Record + app_metadata: Record +} + +export type CreateAuthUserDTO = { + provider_id: string + provider_metadata?: Record + user_metadata?: Record + app_metadata?: Record +} + +export type UpdateAuthUserDTO = { + id: string + provider_metadata?: Record + user_metadata?: Record + app_metadata?: Record +} + +export interface FilterableAuthUserProps + extends BaseFilterable { + id?: string[] + provider?: string[] | string +} diff --git a/packages/types/src/authentication/common/index.ts b/packages/types/src/authentication/common/index.ts new file mode 100644 index 0000000000000..b4282c985c6a3 --- /dev/null +++ b/packages/types/src/authentication/common/index.ts @@ -0,0 +1,2 @@ +export * from "./auth-user" +export * from "./auth-provider" diff --git a/packages/types/src/authentication/index.ts b/packages/types/src/authentication/index.ts index 9376fea807351..1aa665fd541fe 100644 --- a/packages/types/src/authentication/index.ts +++ b/packages/types/src/authentication/index.ts @@ -1 +1,2 @@ export * from "./service" +export * from "./common" diff --git a/packages/types/src/authentication/service.ts b/packages/types/src/authentication/service.ts index eb172bca5d367..78df1eabca25b 100644 --- a/packages/types/src/authentication/service.ts +++ b/packages/types/src/authentication/service.ts @@ -1,3 +1,95 @@ import { IModuleService } from "../modules-sdk" +import { + AuthProviderDTO, + AuthUserDTO, + CreateAuthProviderDTO, + CreateAuthUserDTO, + FilterableAuthProviderProps, + FilterableAuthUserProps, + UpdateAuthProviderDTO, + UpdateAuthUserDTO, +} from "./common" +import { FindConfig } from "../common" +import { Context } from "../shared-context" -export interface IAuthenticationModuleService extends IModuleService {} +export interface IAuthenticationModuleService extends IModuleService { + retrieveAuthProvider( + provider: string, + config?: FindConfig, + sharedContext?: Context + ): Promise + + listAuthProviders( + filters?: FilterableAuthProviderProps, + config?: FindConfig, + sharedContext?: Context + ): Promise + + listAndCountAuthProviders( + filters?: FilterableAuthProviderProps, + config?: FindConfig, + sharedContext?: Context + ): Promise<[AuthProviderDTO[], number]> + + createAuthProvider( + data: CreateAuthProviderDTO[], + sharedContext?: Context + ): Promise + + createAuthProvider( + data: CreateAuthProviderDTO, + sharedContext?: Context + ): Promise + + updateAuthProvider( + data: UpdateAuthProviderDTO[], + sharedContext?: Context + ): Promise + + updateAuthProvider( + data: UpdateAuthProviderDTO, + sharedContext?: Context + ): Promise + + deleteAuthProvider(ids: string[], sharedContext?: Context): Promise + + retrieveAuthUser( + id: string, + config?: FindConfig, + sharedContext?: Context + ): Promise + + listAuthUsers( + filters?: FilterableAuthProviderProps, + config?: FindConfig, + sharedContext?: Context + ): Promise + + listAndCountAuthUsers( + filters?: FilterableAuthUserProps, + config?: FindConfig, + sharedContext?: Context + ): Promise<[AuthUserDTO[], number]> + + createAuthUser( + data: CreateAuthUserDTO[], + sharedContext?: Context + ): Promise + + createAuthUser( + data: CreateAuthUserDTO, + sharedContext?: Context + ): Promise + + updateAuthUser( + data: UpdateAuthUserDTO[], + sharedContext?: Context + ): Promise + + updateAuthUser( + data: UpdateAuthUserDTO, + sharedContext?: Context + ): Promise + + deleteAuthUser(ids: string[], sharedContext?: Context): Promise +}