diff --git a/src/services/control-plane/adapters/drivens/index.ts b/src/services/control-plane/adapters/drivens/index.ts new file mode 100644 index 0000000..6656bea --- /dev/null +++ b/src/services/control-plane/adapters/drivens/index.ts @@ -0,0 +1,2 @@ +export * from './repo-querier-local-adapter'; +export * from './repo-querier-stub-adapter'; \ No newline at end of file diff --git a/src/services/control-plane/adapters/drivens/repo-querier-local-adapter.ts b/src/services/control-plane/adapters/drivens/repo-querier-local-adapter.ts new file mode 100644 index 0000000..0dd9e82 --- /dev/null +++ b/src/services/control-plane/adapters/drivens/repo-querier-local-adapter.ts @@ -0,0 +1,21 @@ +import { userManagerProxy, userPermissionManagerProxy } from "../../../repository/app/composition-root"; +import { RepoUser } from "../../../repository/app/schemas"; +import { Permissions } from "../../app/schemas/auth"; +import { ForRepoQuerying } from "../../ports/drivens"; + +export class RepoQuerierLocalAdapter implements ForRepoQuerying { + async getUser(email: string): Promise { + const user = userManagerProxy.getInternalUser(email) + + return user; + } + + async getPermissions(userId: string): Promise { + const userPermissions = await userPermissionManagerProxy.getUserPermissions(userId) + + return { + admin: userPermissions.admin, + user: userPermissions.user + } + } +} diff --git a/src/services/control-plane/adapters/drivens/repo-querier-stub-adapter.ts b/src/services/control-plane/adapters/drivens/repo-querier-stub-adapter.ts new file mode 100644 index 0000000..161c72f --- /dev/null +++ b/src/services/control-plane/adapters/drivens/repo-querier-stub-adapter.ts @@ -0,0 +1,18 @@ +import { RepoUser } from "../../../repository/app/schemas"; +import { UserPermissions } from "../../app/schemas/auth"; +import { ForRepoQuerying } from "../../ports/drivens"; + +export class RepoQuerierStubAdapter implements ForRepoQuerying { + async getUser(_email: string): Promise { + return { + id: "1", + name: "John Doe", + email: "john@gmail.com", + password: "123", + }; + } + + async getPermissions(_userId: string): Promise { + return Promise.resolve({ id: "1", userId: "1", admin: true, user: true }); + } +} diff --git a/src/services/control-plane/adapters/drivers/auth-manager-proxy-adapter.ts b/src/services/control-plane/adapters/drivers/auth-manager-proxy-adapter.ts new file mode 100644 index 0000000..02eab0f --- /dev/null +++ b/src/services/control-plane/adapters/drivers/auth-manager-proxy-adapter.ts @@ -0,0 +1,15 @@ +import { ControlPlane } from "../../app/control-plane"; +import { AuthDetails, Permissions } from "../../app/schemas/auth"; +import { ForManagingAuthentication } from "../../ports/drivers"; + +export class AuthManagerProxyAdapter implements ForManagingAuthentication { + constructor(private readonly controlPlane: ControlPlane) {} + + async getAuthDetails(email: string, password: string): Promise { + return this.controlPlane.getAuthDetails(email, password); + } + + async getPermissions(userId: string): Promise { + return this.controlPlane.getPermissions(userId); + } +} diff --git a/src/services/control-plane/adapters/drivers/index.ts b/src/services/control-plane/adapters/drivers/index.ts new file mode 100644 index 0000000..60db67d --- /dev/null +++ b/src/services/control-plane/adapters/drivers/index.ts @@ -0,0 +1 @@ +export * from './auth-manager-proxy-adapter'; \ No newline at end of file diff --git a/src/services/control-plane/app/composition-root.ts b/src/services/control-plane/app/composition-root.ts new file mode 100644 index 0000000..19c9abb --- /dev/null +++ b/src/services/control-plane/app/composition-root.ts @@ -0,0 +1,14 @@ +import { RepoQuerierStubAdapter } from "../adapters/drivens"; +import { AuthManagerProxyAdapter } from "../adapters/drivers"; +import { ControlPlane } from "./control-plane"; + +const compositionMock = () => { + const repoQuerierstub = new RepoQuerierStubAdapter(); + const controlPlane = new ControlPlane(repoQuerierstub); + + const authManagerProxy = new AuthManagerProxyAdapter(controlPlane); + + return { authManagerProxy }; +}; + +export const { authManagerProxy } = compositionMock(); diff --git a/src/services/control-plane/app/control-plane.test.ts b/src/services/control-plane/app/control-plane.test.ts new file mode 100644 index 0000000..680c1c4 --- /dev/null +++ b/src/services/control-plane/app/control-plane.test.ts @@ -0,0 +1,39 @@ +import { beforeEach, describe, expect, it } from "vitest"; +import { RepoQuerierStubAdapter } from "../adapters/drivens"; +import { ControlPlane } from "./control-plane"; + +describe("ControlPlane", () => { + const repoQuerierstub = new RepoQuerierStubAdapter(); + let controlPlane = new ControlPlane(repoQuerierstub); + + beforeEach(() => { + controlPlane = new ControlPlane(repoQuerierstub); + }); + + it.concurrent("should get auth details", async () => { + // GIVEN + const mockEmail = "john@gmail.com"; + const mockPassword = "123"; + + const resultExpected = { token: "token", refreshToken: "refreshToken" }; + + // WHEN + const result = await controlPlane.getAuthDetails(mockEmail, mockPassword); + + // THEN + expect(result).toEqual(resultExpected); + }); + + it.concurrent("should get permissions", async () => { + // GIVEN + const mockUserId = "1"; + + const resultExpected = { admin: true, user: true }; + + // WHEN + const result = await controlPlane.getPermissions(mockUserId); + + // THEN + expect(result).toEqual(resultExpected); + }); +}); diff --git a/src/services/control-plane/app/control-plane.ts b/src/services/control-plane/app/control-plane.ts new file mode 100644 index 0000000..11dd500 --- /dev/null +++ b/src/services/control-plane/app/control-plane.ts @@ -0,0 +1,38 @@ +import { RepoUser } from "../../repository/app/schemas"; +import { ForRepoQuerying } from "../ports/drivens/for-repo-querying"; +import { ForManagingAuthentication } from "../ports/drivers"; +import { AuthDetails, Permissions } from "./schemas/auth"; + +export class ControlPlane implements ForManagingAuthentication { + constructor( + private readonly repoQuerier: ForRepoQuerying, + ) {} + + async getAuthDetails(email: string, password: string): Promise { + let user: RepoUser + + try { + user = await this.repoQuerier.getUser(email); + } catch (error) { + // acá podría hacer una validación según el tipo de error + throw new Error("Wrong email"); + } + + if (password !== user.password) { + throw new Error("Wrong password"); + } + + return { token: "token", refreshToken: "refreshToken" }; + } + + async getPermissions(userId: string): Promise { + const userPermissions = await this.repoQuerier.getPermissions(userId); + + const permissions = { + admin: userPermissions.admin, + user: userPermissions.user, + }; + + return permissions; + } +} diff --git a/src/services/control-plane/app/schemas/auth.ts b/src/services/control-plane/app/schemas/auth.ts new file mode 100644 index 0000000..7533484 --- /dev/null +++ b/src/services/control-plane/app/schemas/auth.ts @@ -0,0 +1,16 @@ +export interface UserPermissions { + id: string; + userId: string; + admin: boolean; + user: boolean; +} + +export interface AuthDetails { + token: string; + refreshToken: string; +} + +export interface Permissions { + admin: boolean; + user: boolean; +} diff --git a/src/services/control-plane/ports/drivens/for-repo-querying.ts b/src/services/control-plane/ports/drivens/for-repo-querying.ts new file mode 100644 index 0000000..fd87409 --- /dev/null +++ b/src/services/control-plane/ports/drivens/for-repo-querying.ts @@ -0,0 +1,7 @@ +import { RepoUser } from "../../../repository/app/schemas"; +import { Permissions } from "../../app/schemas/auth"; + +export interface ForRepoQuerying { + getUser(email: string): Promise; + getPermissions(userId: string): Promise; +} diff --git a/src/services/control-plane/ports/drivens/index.ts b/src/services/control-plane/ports/drivens/index.ts new file mode 100644 index 0000000..3285e5a --- /dev/null +++ b/src/services/control-plane/ports/drivens/index.ts @@ -0,0 +1 @@ +export * from "./for-repo-querying"; diff --git a/src/services/control-plane/ports/drivers/for-managing-authentication.ts b/src/services/control-plane/ports/drivers/for-managing-authentication.ts new file mode 100644 index 0000000..d70c683 --- /dev/null +++ b/src/services/control-plane/ports/drivers/for-managing-authentication.ts @@ -0,0 +1,6 @@ +import { AuthDetails, Permissions } from "../../app/schemas/auth"; + +export interface ForManagingAuthentication { + getAuthDetails(email: string, password: string): Promise; + getPermissions(userId: string): Promise; +} diff --git a/src/services/control-plane/ports/drivers/index.ts b/src/services/control-plane/ports/drivers/index.ts new file mode 100644 index 0000000..6ab0d1d --- /dev/null +++ b/src/services/control-plane/ports/drivers/index.ts @@ -0,0 +1 @@ +export * from "./for-managing-authentication"; diff --git a/src/services/dashboard-api/adapters/drivens/control-authenticator-stub-adapter.ts b/src/services/dashboard-api/adapters/drivens/control-authenticator-stub-adapter.ts index 1bd80a4..5434ad2 100644 --- a/src/services/dashboard-api/adapters/drivens/control-authenticator-stub-adapter.ts +++ b/src/services/dashboard-api/adapters/drivens/control-authenticator-stub-adapter.ts @@ -16,7 +16,7 @@ export class ControlAuthenticatorStub implements ForControlAuthenticating { return Promise.resolve(authDetailsMock); } - getPermissions(_email: string, _password: string): Promise { + getPermissions(_userId: string): Promise { return Promise.resolve(permissionsMock); } } diff --git a/src/services/dashboard-api/adapters/drivens/index.ts b/src/services/dashboard-api/adapters/drivens/index.ts index 8bbd5a5..2b77826 100644 --- a/src/services/dashboard-api/adapters/drivens/index.ts +++ b/src/services/dashboard-api/adapters/drivens/index.ts @@ -1,2 +1,3 @@ export * from './control-authenticator-stub-adapter'; -export * from './repo-querier-stub-adapter'; +export * from './repo-querier-local-adapter'; +export * from './repo-querier-stub-adapter'; \ No newline at end of file diff --git a/src/services/dashboard-api/adapters/drivens/repo-querier-local-adapter.ts b/src/services/dashboard-api/adapters/drivens/repo-querier-local-adapter.ts new file mode 100644 index 0000000..e9fba09 --- /dev/null +++ b/src/services/dashboard-api/adapters/drivens/repo-querier-local-adapter.ts @@ -0,0 +1,14 @@ +import { userManagerProxy } from "../../../repository/app/composition-root"; +import { ExternalUser } from "../../../repository/app/schemas"; +import { User } from "../../app/schemas"; +import { ForRepoQuerying } from "../../ports/drivens"; + +export class RepoQuerierLocalAdapter implements ForRepoQuerying { + async getUser(email: string): Promise { + return await userManagerProxy.getUser(email) + } + + async createUser(user: User): Promise { + return await userManagerProxy.createUser(user) + } +} diff --git a/src/services/dashboard-api/app/composition-root.ts b/src/services/dashboard-api/app/composition-root.ts index a99c50c..93a9ead 100644 --- a/src/services/dashboard-api/app/composition-root.ts +++ b/src/services/dashboard-api/app/composition-root.ts @@ -6,6 +6,7 @@ import { authTRPCAdapter, } from "../adapters/drivers"; import { initTRPC } from "@trpc/server"; +import { RepoQuerierLocalAdapter } from "../adapters/drivens"; const compositionMock = () => { // DRIVENS @@ -29,7 +30,7 @@ const compositionMock = () => { }; export const { authenticatorProxyAdapter } = compositionMock(); -/* +/* const registerMock = { name: "John", email: "jhon@gmail.com", @@ -42,12 +43,12 @@ authenticatorProxyAdapter.register(registerMock); export const localTRPCCompose = () => { // DRIVENS const controlAuthenticatorStub = new ControlAuthenticatorStub(); - const repoQuerierStub = new RepoQuerierStub(); + const repoQuerierLocal = new RepoQuerierLocalAdapter(); // APP const dashboardApiMock = new DashboardApi( controlAuthenticatorStub, - repoQuerierStub + repoQuerierLocal ); // TRPC INSTANCE diff --git a/src/services/dashboard-api/app/dashboard-api.ts b/src/services/dashboard-api/app/dashboard-api.ts index 20de0bc..31ef6ec 100644 --- a/src/services/dashboard-api/app/dashboard-api.ts +++ b/src/services/dashboard-api/app/dashboard-api.ts @@ -14,11 +14,10 @@ export class DashboardApi implements ForAuthenticating { email, password ); - const permissions = await this.controlAuthenticator.getPermissions( - email, - password - ); const user = await this.repoQuerier.getUser(email); + + const permissions = await this.controlAuthenticator.getPermissions(user.id); + const result = { ...user, ...authDetails, @@ -37,8 +36,7 @@ export class DashboardApi implements ForAuthenticating { user.password ); const permissions = await this.controlAuthenticator.getPermissions( - user.email, - user.password + newUser.id ); const result = { diff --git a/src/services/dashboard-api/ports/drivens/for-control-authenticating.ts b/src/services/dashboard-api/ports/drivens/for-control-authenticating.ts index 64130c5..dc850c1 100644 --- a/src/services/dashboard-api/ports/drivens/for-control-authenticating.ts +++ b/src/services/dashboard-api/ports/drivens/for-control-authenticating.ts @@ -2,5 +2,5 @@ import { AuthDetails, Permissions } from "../../app/schemas"; export interface ForControlAuthenticating { getAuthDetails(email: string, password: string): Promise; - getPermissions(email: string, password: string): Promise; + getPermissions(userId: string): Promise; } diff --git a/src/services/repository/adapters/drivers/index.ts b/src/services/repository/adapters/drivers/index.ts index e7e16fd..5c46b06 100644 --- a/src/services/repository/adapters/drivers/index.ts +++ b/src/services/repository/adapters/drivers/index.ts @@ -1 +1,2 @@ export * from './user-manager-proxy'; +export * from './user-permission-manager-proxy'; \ No newline at end of file diff --git a/src/services/repository/adapters/drivers/user-manager-proxy.ts b/src/services/repository/adapters/drivers/user-manager-proxy.ts index 411b2ed..2c4919a 100644 --- a/src/services/repository/adapters/drivers/user-manager-proxy.ts +++ b/src/services/repository/adapters/drivers/user-manager-proxy.ts @@ -1,5 +1,5 @@ import { Repository } from "../../app/repository"; -import { ExternalUser, User } from "../../app/schemas"; +import { ExternalUser, Permissions, RepoUser, User, UserPermission } from "../../app/schemas"; import { ForManagingUser } from "../../ports/drivers"; export class UserManagerProxy implements ForManagingUser { @@ -12,4 +12,8 @@ export class UserManagerProxy implements ForManagingUser { async createUser(user: User): Promise { return this.repository.createUser(user); } + + async getInternalUser(email: string): Promise { + return this.repository.getInternalUser(email); + } } diff --git a/src/services/repository/adapters/drivers/user-permission-manager-proxy.ts b/src/services/repository/adapters/drivers/user-permission-manager-proxy.ts new file mode 100644 index 0000000..61b9e9a --- /dev/null +++ b/src/services/repository/adapters/drivers/user-permission-manager-proxy.ts @@ -0,0 +1,16 @@ +import { Repository } from "../../app/repository"; +import { Permissions, UserPermission } from "../../app/schemas"; +import { UserPermissionRepository } from "../../app/user-permission-repository"; +import { ForManagingPermission } from "../../ports/drivers/for-managing-permission"; + +export class UserPermissionManagerProxy implements ForManagingPermission { + constructor(private readonly repository: UserPermissionRepository) {} + + async getUserPermissions(userId: string): Promise { + return this.repository.getUserPermissions(userId) + } + + async createUserPermissions(userId: string, permissions: Permissions): Promise { + return this.repository.createUserPermissions(userId, permissions) + } +} diff --git a/src/services/repository/app/composition-root.ts b/src/services/repository/app/composition-root.ts index 9c6ad26..1990d18 100644 --- a/src/services/repository/app/composition-root.ts +++ b/src/services/repository/app/composition-root.ts @@ -1,6 +1,8 @@ import { LoggerStubAdapter } from "../adapters/drivens"; +import { UserPermissionManagerProxy } from "../adapters/drivers"; import { UserManagerProxy } from "./../adapters/drivers/user-manager-proxy"; import { Repository } from "./repository"; +import { UserPermissionRepository } from "./user-permission-repository"; export const compositionMock = () => { const monitorStub = new LoggerStubAdapter(); @@ -8,7 +10,13 @@ export const compositionMock = () => { const userManagerProxy = new UserManagerProxy(repositoryMock); + const userPermissionRepositoryMock = new UserPermissionRepository(monitorStub) + const userPermissionManagerProxy = new UserPermissionManagerProxy(userPermissionRepositoryMock); + return { userManagerProxy, + userPermissionManagerProxy }; }; + +export const { userManagerProxy, userPermissionManagerProxy } = compositionMock(); \ No newline at end of file diff --git a/src/services/repository/app/repository.test.ts b/src/services/repository/app/repository.test.ts index 9574a5e..765c20e 100644 --- a/src/services/repository/app/repository.test.ts +++ b/src/services/repository/app/repository.test.ts @@ -93,4 +93,22 @@ describe("Repository", () => { //THEN expect(result).toEqual(expectedResult); }); + + it.concurrent("should get internal user", async () => { + // GIVEN + const mockedEmail = "samuelcito@gmail.com"; + + const expectedResult = { + id: "1", + name: "Samuel", + email: "samuelcito@gmail.com", + password: 'password' + }; + + // WHEN + const result = await repositoryMock.getInternalUser(mockedEmail); + + // THEN + expect(result).toEqual(expectedResult); + }); }); diff --git a/src/services/repository/app/repository.ts b/src/services/repository/app/repository.ts index 6be4ce5..07f1e11 100644 --- a/src/services/repository/app/repository.ts +++ b/src/services/repository/app/repository.ts @@ -1,9 +1,16 @@ import { ForMonitoring } from "../ports/drivens"; import { ForManagingUser } from "../ports/drivers"; -import { ExternalUser, RepoUser, User } from "./schemas"; +import { + ExternalUser, + Permissions, + RepoUser, + User, + UserPermission, +} from "./schemas"; export class Repository implements ForManagingUser { private userList: RepoUser[] = []; + private userPermissionList: UserPermission[] = []; constructor(private readonly logger: ForMonitoring) {} async getUser(email: string): Promise { @@ -37,4 +44,43 @@ export class Repository implements ForManagingUser { email: newUser.email, }; } + + async getInternalUser(email: string): Promise { + const user = this.userList.find((user) => user.email === email); + if (!user) { + this.logger.log("GetInternalUser", "User not found"); + throw new Error("User not found"); + } + + return user; + } + + async getUserPermissions(userId: string): Promise { + const userPermission = this.userPermissionList.find( + (userPermission) => userPermission.userId === userId + ); + + if (!userPermission) { + this.logger.log("GetUserPermissions", "Permissions not found"); + throw new Error("Permissions not found"); + } + + return userPermission; + } + + async createUserPermissions( + userId: string, + permissions: Permissions + ): Promise { + const newUserPermission: UserPermission = { + id: crypto.randomUUID(), + userId, + admin: permissions.admin, + user: permissions.user, + }; + + this.userPermissionList.push(newUserPermission); + + return newUserPermission; + } } diff --git a/src/services/repository/app/schemas/auth.ts b/src/services/repository/app/schemas/auth.ts new file mode 100644 index 0000000..87a912d --- /dev/null +++ b/src/services/repository/app/schemas/auth.ts @@ -0,0 +1,11 @@ +export interface UserPermission { + id: string + userId: string + admin: boolean + user: boolean +} + +export interface Permissions { + admin: boolean, + user: boolean +} \ No newline at end of file diff --git a/src/services/repository/app/schemas/index.ts b/src/services/repository/app/schemas/index.ts index e5abc85..fd7d4c3 100644 --- a/src/services/repository/app/schemas/index.ts +++ b/src/services/repository/app/schemas/index.ts @@ -1 +1,2 @@ -export * from './user'; +export * from './auth'; +export * from './user'; \ No newline at end of file diff --git a/src/services/repository/app/user-permission-repository.test.ts b/src/services/repository/app/user-permission-repository.test.ts new file mode 100644 index 0000000..8abdc30 --- /dev/null +++ b/src/services/repository/app/user-permission-repository.test.ts @@ -0,0 +1,61 @@ +import { describe, expect, it } from "vitest"; +import { LoggerStubAdapter } from "../adapters/drivens"; +import { Repository } from "./repository"; + +describe("Repository", () => { + const monitorStub = new LoggerStubAdapter(); + const repositoryMock = new Repository(monitorStub); + + it.concurrent("should create user permissions", async () => { + // GIVEN + const mockedUserId = "1"; + const mockedPermissions = { + admin: true, + user: true + } + + const expectedResult = { + userId: '1', + admin: true, + user: true + }; + + // WHEN + const result = await repositoryMock.createUserPermissions(mockedUserId, mockedPermissions); + + // THEN + expect(result).toEqual(expect.objectContaining(expectedResult)); + }); + + it.concurrent("should get user permissions", async () => { + // GIVEN + const mockedUserId = "1"; + + const expectedResult = { + userId: '1', + admin: true, + user: true + }; + + // WHEN + const result = await repositoryMock.getUserPermissions(mockedUserId); + + // THEN + expect(result).toEqual(expect.objectContaining(expectedResult)); + }); + + it.concurrent( + "should throw error when permissions does not exists for user", + async () => { + // GIVEN + const mockUserId = "2"; + + // WHEN + + // THEN + await expect( + repositoryMock.getUserPermissions(mockUserId) + ).rejects.toThrowError("Permissions not found"); + } + ); +}); diff --git a/src/services/repository/app/user-permission-repository.ts b/src/services/repository/app/user-permission-repository.ts new file mode 100644 index 0000000..d7c4bca --- /dev/null +++ b/src/services/repository/app/user-permission-repository.ts @@ -0,0 +1,40 @@ +import { ForMonitoring } from "../ports/drivens"; +import { ForManagingPermission } from "../ports/drivers/for-managing-permission"; +import { + Permissions, + UserPermission, +} from "./schemas"; + +export class UserPermissionRepository implements ForManagingPermission { + private userPermissionList: UserPermission[] = []; + constructor(private readonly logger: ForMonitoring) {} + + async getUserPermissions(userId: string): Promise { + const userPermission = this.userPermissionList.find( + (userPermission) => userPermission.userId === userId + ); + + if (!userPermission) { + this.logger.log("GetUserPermissions", "Permissions not found"); + throw new Error("Permissions not found"); + } + + return userPermission; + } + + async createUserPermissions( + userId: string, + permissions: Permissions + ): Promise { + const newUserPermission: UserPermission = { + id: crypto.randomUUID(), + userId, + admin: permissions.admin, + user: permissions.user, + }; + + this.userPermissionList.push(newUserPermission); + + return newUserPermission; + } +} diff --git a/src/services/repository/ports/drivers/for-managing-permission.ts b/src/services/repository/ports/drivers/for-managing-permission.ts new file mode 100644 index 0000000..865b5cd --- /dev/null +++ b/src/services/repository/ports/drivers/for-managing-permission.ts @@ -0,0 +1,6 @@ +import { Permissions, UserPermission } from "../../app/schemas"; + +export interface ForManagingPermission { + getUserPermissions(userId: string): Promise + createUserPermissions(userId: string, permissions: Permissions): Promise +} diff --git a/src/services/repository/ports/drivers/for-managing-user.ts b/src/services/repository/ports/drivers/for-managing-user.ts index d6e941f..77cb7df 100644 --- a/src/services/repository/ports/drivers/for-managing-user.ts +++ b/src/services/repository/ports/drivers/for-managing-user.ts @@ -1,6 +1,7 @@ -import { ExternalUser, User } from "../../app/schemas"; +import { ExternalUser, Permissions, RepoUser, User, UserPermission } from "../../app/schemas"; export interface ForManagingUser { getUser(email: string): Promise; createUser(user: User, password: string): Promise; + getInternalUser(email: string): Promise }