Skip to content

feat: implement control plane #3

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@
"@types/express": "^4.17.17",
"cors": "^2.8.5",
"express": "^4.18.2",
"jsonwebtoken": "^9.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"zod": "^3.21.4"
},
"devDependencies": {
"@types/jsonwebtoken": "^9.0.1",
"@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11",
"@vitejs/plugin-react": "^3.1.0",
Expand Down
2 changes: 2 additions & 0 deletions src/services/control-plane/adapters/drivens/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './logger-stub-adapter'
export * from './repo-querier-stub-adapter'
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { ForMonitoringAuthDetails } from '../../ports/drivens/for-monitoring'

export class LoggerStubAdapter implements ForMonitoringAuthDetails {
log(event: string, message: string) {
console.log(event, message)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { ExternalUser } from '../../../repository/app/schemas'
import { ForRepoQuerying } from '../../ports/drivens'

const userMock: ExternalUser = {
id: '1',
name: 'John Doe',
email: '[email protected]',
admin: false,
user: true,
}

export class RepoQuerierAdapter implements ForRepoQuerying {
getUser(_email: string): Promise<ExternalUser> {
return Promise.resolve(userMock)
}
}
15 changes: 15 additions & 0 deletions src/services/control-plane/adapters/drivers/auth-details-proxy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { ControlPlane } from '../../app/control-plane'
import { AuthDetails, Permissions } from '../../app/schemas'
import { ForManagingAuthDetails } from '../../ports/drivers/for-managing-auth-details'

export class AuthDetailsProxy implements ForManagingAuthDetails {
constructor(private readonly controlPlane: ControlPlane) {}

async getAuthDetails(email: string, password: string): Promise<AuthDetails> {
return this.controlPlane.getAuthDetails(email, password)
}

async getPermissions(email: string): Promise<Permissions> {
return this.controlPlane.getPermissions(email)
}
}
1 change: 1 addition & 0 deletions src/services/control-plane/adapters/drivers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './auth-details-proxy'
30 changes: 30 additions & 0 deletions src/services/control-plane/app/composition-root.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { LoggerStubAdapter } from '../adapters/drivens'
import { RepoQuerierAdapter } from '../adapters/drivens/repo-querier-stub-adapter'
import { AuthDetailsProxy } from '../adapters/drivers'
import { ControlPlane } from './control-plane'

const compositionMock = () => {
const monitorStub = new LoggerStubAdapter()
const repoQueryStub = new RepoQuerierAdapter()
const dashboardApiMock = new ControlPlane(monitorStub, repoQueryStub)

const authenticatorProxyAdapter = new AuthDetailsProxy(dashboardApiMock)

return {
authenticatorProxyAdapter,
}
}

export const { authenticatorProxyAdapter } = compositionMock()

const registerMock = {
name: 'John',
email: '[email protected]',
password: 'password',
}

authenticatorProxyAdapter.getAuthDetails(

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Esto está bien para probar de manera local, pero lo eliminaría para prod ya que está probado mediante los test que esto funcione correctamente con datos de prueba

registerMock.email,
registerMock.password
)
authenticatorProxyAdapter.getPermissions(registerMock.email)
51 changes: 51 additions & 0 deletions src/services/control-plane/app/control-plane.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { describe, expect, it } from 'vitest'
import { RepoQuerierAdapter } from '../adapters/drivens/repo-querier-stub-adapter'
import { LoggerStubAdapter } from '../adapters/drivens'
import { ControlPlane } from './control-plane'
import { AuthDetails, Permissions } from './schemas'

describe('ControlPlane', () => {
const monitorStub = new LoggerStubAdapter()
const repoQueryStub = new RepoQuerierAdapter()
const controPlaneMock = new ControlPlane(monitorStub, repoQueryStub)

it.concurrent('should not expect a 123 token', async () => {
//GIVEN
const mockedParams = {
email: `12`,
password: '12345678',
}

const expectedResult: AuthDetails = {
token: '123',
refreshToken: '123',
}

//WHEN
let result
try {
result = await controPlaneMock.getAuthDetails(
mockedParams.email,
mockedParams.password
)
} catch (error) {}

//THEN
expect(result).not.toEqual(expectedResult)
})

it.concurrent('should get user permissions', async () => {
//GIVEN

const expectedResult: Permissions = {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Realmente no me acuerdo si lo había dicho en el vídeo sobre tener dos properties para los roles, pero es posible que con solo saber si es admin o no sea suficiente, ya que si no es admin... es user.

admin: false,
user: true,
}

//WHEN
const result = await controPlaneMock.getPermissions('[email protected]')

//THEN
expect(result).toEqual(expectedResult)
})
})
72 changes: 72 additions & 0 deletions src/services/control-plane/app/control-plane.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { ForRepoQuerying } from '../ports/drivens'
import { ForMonitoringAuthDetails } from '../ports/drivens/for-monitoring'
import { ForManagingAuthDetails } from '../ports/drivers/for-managing-auth-details'
import { AuthDetails, Permissions } from './schemas'
import jwt from 'jsonwebtoken'

export class ControlPlane implements ForManagingAuthDetails {
private secretKey: string = 'mySecretKey'

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dejar espacios entre unidades lógicas

Suggested change
private secretKey: string = 'mySecretKey'
private secretKey: string = 'mySecretKey'

constructor(
private readonly logger: ForMonitoringAuthDetails,
private readonly repoQuerier: ForRepoQuerying
) {}

async getAuthDetails(email: string, password: string): Promise<AuthDetails> {
const generateToken = (
payload: object,
secretKey: string,
expiresIn: string
): string => {
const token = jwt.sign(payload, secretKey, { expiresIn })

return token
}
const generateRefreshToken = (
payload: object,
secretKey: string,
expiresIn: string
): string => {
const refreshToken = jwt.sign(payload, secretKey, { expiresIn })

return refreshToken
}
const token = generateToken({ email, password }, this.secretKey, '30m')

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dejar espacios entre unidades lógicas

Suggested change
const token = generateToken({ email, password }, this.secretKey, '30m')
const token = generateToken({ email, password }, this.secretKey, '30m')

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'30m' podría ser una variable que puede ser modificable por algún método para que sea congruente en toda la app

if (!token) {
this.logger.log('Token creation', 'FAILED')
throw new Error('Failed creating token, please check credentials')
}

const refreshToken = generateRefreshToken(
{ email, password },
this.secretKey,
'1d'
)

if (!refreshToken) {
this.logger.log('RefreshToken creation', 'FAILED')
throw new Error('Failed creating Refreshtoken, please check credentials')
}

const result = {
token,
refreshToken,
}

console.log('AUTHDETAILS', result)

return { token, refreshToken }
}

async getPermissions(email: string): Promise<Permissions> {
const user = await this.repoQuerier.getUser(email)

const result = {
admin: user?.admin,
user: user?.user,
}

console.log('PERMISSIONS', result)

return { admin: user?.admin, user: user?.user }
}
}
4 changes: 4 additions & 0 deletions src/services/control-plane/app/schemas/details.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface AuthDetails {
token: string
refreshToken: string
}
2 changes: 2 additions & 0 deletions src/services/control-plane/app/schemas/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './details'
export * from './permissions'
4 changes: 4 additions & 0 deletions src/services/control-plane/app/schemas/permissions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface Permissions {
admin: boolean
user: boolean
}
3 changes: 3 additions & 0 deletions src/services/control-plane/ports/drivens/for-monitoring.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export interface ForMonitoringAuthDetails {
log(event: string, message: string): void
}
5 changes: 5 additions & 0 deletions src/services/control-plane/ports/drivens/for-repo-querying.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { ExternalUser } from '../../../repository/app/schemas'

export interface ForRepoQuerying {
getUser(email: string): Promise<ExternalUser>
}
2 changes: 2 additions & 0 deletions src/services/control-plane/ports/drivens/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './for-monitoring'
export * from './for-repo-querying'
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { AuthDetails, Permissions } from '../../app/schemas'

export interface ForManagingAuthDetails {
getAuthDetails(email: string, password: string): Promise<AuthDetails>
getPermissions(email: string): Promise<Permissions>
}
1 change: 1 addition & 0 deletions src/services/control-plane/ports/drivers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './for-managing-auth-details'
4 changes: 2 additions & 2 deletions src/services/dashboard-api/adapters/drivens/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export * from './control-authenticator-stub-adapter';
export * from './repo-querier-stub-adapter';
export * from './control-authenticator-stub-adapter'
export * from './repo-querier-stub-adapter'
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
import { ExternalUser } from "../../../repository/app/schemas";
import { User } from "../../app/schemas";
import { ForRepoQuerying } from "../../ports/drivens";
import { ExternalUser } from '../../../repository/app/schemas'
import { User } from '../../app/schemas'
import { ForRepoQuerying } from '../../ports/drivens'

const userMock: ExternalUser = {
id: "1",
name: "John Doe",
email: "[email protected]",
};
id: '1',
name: 'John Doe',
email: '[email protected]',
admin: false,
user: true,
}

export class RepoQuerierStub implements ForRepoQuerying {
getUser(_email: string): Promise<ExternalUser> {
return Promise.resolve(userMock);
return Promise.resolve(userMock)
}

createUser(_user: User): Promise<ExternalUser> {
return Promise.resolve(userMock);
return Promise.resolve(userMock)
}
}
78 changes: 41 additions & 37 deletions src/services/dashboard-api/app/dashboard-api.test.ts
Original file line number Diff line number Diff line change
@@ -1,71 +1,75 @@
import { describe, expect, it } from "vitest";
import { ControlAuthenticatorStub } from "../adapters/drivens/control-authenticator-stub-adapter";
import { RepoQuerierStub } from "../adapters/drivens/repo-querier-stub-adapter";
import { DashboardApi } from "./dashboard-api";
import { AuthenticatedUser, User } from "./schemas";
import { describe, expect, it } from 'vitest'
import { ControlAuthenticatorStub } from '../adapters/drivens/control-authenticator-stub-adapter'
import { RepoQuerierStub } from '../adapters/drivens/repo-querier-stub-adapter'
import { DashboardApi } from './dashboard-api'
import { AuthenticatedUser, User } from './schemas'

describe("DashboardApi", () => {
const controlAuthenticatorStub = new ControlAuthenticatorStub();
const repoQuerierStub = new RepoQuerierStub();
describe('DashboardApi', () => {
const controlAuthenticatorStub = new ControlAuthenticatorStub()
const repoQuerierStub = new RepoQuerierStub()
const dashboardApiMock = new DashboardApi(
controlAuthenticatorStub,
repoQuerierStub
);
)

it.concurrent("should login", async () => {
it.concurrent('should login', async () => {
//GIVEN
const mockedParams = {
email: "[email protected]",
password: "12345678",
};
email: '[email protected]',
password: '12345678',
}

const expectedResult: AuthenticatedUser = {
id: "1",
name: "John Doe",
email: "[email protected]",
token: "token",
refreshToken: "refreshToken",
id: '1',
name: 'John Doe',
email: '[email protected]',
token: 'token',
refreshToken: 'refreshToken',
permissions: {
admin: true,
user: true,
},
};
admin: false,
user: true,
}

//WHEN
const result = await dashboardApiMock.login(
mockedParams.email,
mockedParams.password
);
)

//THEN
expect(result).toEqual(expectedResult);
});
expect(result).toEqual(expectedResult)
})

it.concurrent("should register", async () => {
it.concurrent('should register', async () => {
//GIVEN

const mockedUser: User = {
name: "John",
email: "[email protected]",
password: "password",
};
name: 'John',
email: '[email protected]',
password: 'password',
}

const expectedResult: AuthenticatedUser = {
id: "1",
name: "John Doe",
email: "[email protected]",
token: "token",
refreshToken: "refreshToken",
id: '1',
name: 'John Doe',
email: '[email protected]',
token: 'token',
refreshToken: 'refreshToken',
permissions: {
admin: true,
user: true,
},
};
admin: false,
user: true,
}

//WHEN
const result = await dashboardApiMock.register(mockedUser);
const result = await dashboardApiMock.register(mockedUser)

//THEN
expect(result).toEqual(expectedResult);
});
});
expect(result).toEqual(expectedResult)
})
})
Loading