Skip to content

Commit

Permalink
feat: add dynamo gsi and implement repositories + fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
ribeirogab committed Sep 20, 2024
1 parent ec23d38 commit 12f8716
Show file tree
Hide file tree
Showing 15 changed files with 371 additions and 108 deletions.
12 changes: 12 additions & 0 deletions infra/lambda/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,23 @@ resource "aws_dynamodb_table" "auth_resource_table" {
type = "S"
}

attribute {
name = "ReferenceId"
type = "S"
}

ttl {
attribute_name = "TTL"
enabled = true
}

global_secondary_index {
name = "ReferenceIdIndex"
hash_key = "PK"
range_key = "ReferenceId"
projection_type = "ALL"
}

tags = {
Environment = var.environment
}
Expand Down
5 changes: 5 additions & 0 deletions src/configs/dynamo.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ import type { EnvConfig } from './env.config';

export enum DynamoPartitionKeysEnum {
VerificationCode = 'verification-code',
User = 'user',
}

export enum DynamoGSIEnum {
ReferenceIdIndex = 'ReferenceIdIndex',
}

@injectable()
Expand Down
37 changes: 33 additions & 4 deletions src/controllers/password.controller.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,48 @@
import type { FastifyReply, FastifyRequest } from 'fastify';
import { injectable } from 'tsyringe';
import { inject, injectable } from 'tsyringe';

import { HttpStatusCodesEnum } from '@/constants';
import type {
RecoveryPasswordService,
RecoveryPasswordVerifyService,
ResetPasswordService,
ResetPasswordServiceDto,
} from '@/interfaces';

@injectable()
export class PasswordController {
public recovery(_request: FastifyRequest, reply: FastifyReply) {
constructor(
@inject('RecoveryPasswordVerifyService')
private readonly recoveryPasswordVerifyService: RecoveryPasswordVerifyService,

@inject('RecoveryPasswordService')
private readonly recoveryPasswordService: RecoveryPasswordService,

@inject('ResetPasswordService')
private readonly resetPasswordService: ResetPasswordService,
) {}

public async recovery(request: FastifyRequest, reply: FastifyReply) {
const { email } = request.body as { email: string };

await this.recoveryPasswordService.execute({ email });

return reply.code(HttpStatusCodesEnum.NO_CONTENT).send();
}

public recoveryVerify(_request: FastifyRequest, reply: FastifyReply) {
public async recoveryVerify(request: FastifyRequest, reply: FastifyReply) {
const { code } = request.body as { code: string };

await this.recoveryPasswordVerifyService.execute({ code });

return reply.code(HttpStatusCodesEnum.NO_CONTENT).send();
}

public reset(_request: FastifyRequest, reply: FastifyReply) {
public async reset(request: FastifyRequest, reply: FastifyReply) {
const { code, email, password } = request.body as ResetPasswordServiceDto;

await this.resetPasswordService.execute({ code, email, password });

return reply.code(HttpStatusCodesEnum.NO_CONTENT).send();
}
}
11 changes: 8 additions & 3 deletions src/interfaces/models/verification-code.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,14 @@ export enum VerificationCodeTypeEnum {
Registration = 'registration',
}

export enum VerificationCodeReservedFieldEnum {
code_expires_at = 'code_expires_at',
code_type = 'code_type',
code = 'code',
}

export type VerificationCode = {
data: Record<string, string | number>;
type: VerificationCodeTypeEnum;
expires_at: string;
code_type: VerificationCodeTypeEnum;
code_expires_at: string;
code: string;
};
15 changes: 12 additions & 3 deletions src/interfaces/repositories/verification-code.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,24 @@ import type {
} from '../models/verification-code';

export type VerificationCodeRepositoryFilterDto = {
type: VerificationCodeTypeEnum;
code_type: VerificationCodeTypeEnum;
code: string;
};

export type VerificationCodeRepositoryCreateDto = Omit<
VerificationCode,
'code'
> & {
content: Record<string, string | number>;
};

export type VerificationCodeRepositoryFindOneByContentDto = {
content: { key: string; value: string };
type: VerificationCodeTypeEnum;
code_type: VerificationCodeTypeEnum;
};

export interface VerificationCodeRepository {
create(dto: Omit<VerificationCode, 'code'>): Promise<VerificationCode>;
create(dto: VerificationCodeRepositoryCreateDto): Promise<VerificationCode>;

findOne(
dto: VerificationCodeRepositoryFilterDto,
Expand All @@ -25,4 +32,6 @@ export interface VerificationCodeRepository {
findOneByContent(
dto: VerificationCodeRepositoryFindOneByContentDto,
): Promise<VerificationCode | null>;

removeReservedFields<T>(dto: VerificationCode): T;
}
80 changes: 60 additions & 20 deletions src/repositories/session.repository.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
import { injectable } from 'tsyringe';
import {
DeleteItemCommand,
type DeleteItemCommandInput,
GetItemCommand,
type GetItemCommandInput,
PutItemCommand,
type PutItemCommandInput,
} from '@aws-sdk/client-dynamodb';
import { marshall, unmarshall } from '@aws-sdk/util-dynamodb';
import { inject, injectable } from 'tsyringe';

import type { DynamoConfig } from '@/configs';
import type {
Session,
SessionRepository as SessionRepositoryInterface,
Expand All @@ -14,38 +24,68 @@ import type {

@injectable()
export class SessionRepository implements SessionRepositoryInterface {
private sessions: Session[] = [];
private readonly PK = 'session';

async upsert(session: Session): Promise<Session> {
const index = this.sessions.findIndex(
(userSession) => userSession.user_id === session.user_id,
);
constructor(
@inject('DynamoConfig') private readonly dynamoConfig: DynamoConfig,
) {}

if (index !== -1) {
// Update an existing session
this.sessions[index] = session;
} else {
this.sessions.push(session);
}
public async upsert(session: Session): Promise<Session> {
const params: PutItemCommandInput = {
TableName: this.dynamoConfig.tableName,
Item: marshall({
PK: this.PK,
SK: `user_id:${session.user_id}`,
Content: {
access_token: session.access_token,
refresh_token: session.refresh_token,
expires_at: session.expires_at,
user_id: session.user_id,
},
TTL: this.dynamoConfig.getTTL(new Date(session.expires_at)),
}),
};

await this.dynamoConfig.client.send(new PutItemCommand(params));

return session;
}

async deleteByUserId({ user_id }: { user_id: string }): Promise<void> {
this.sessions = this.sessions.filter(
(session) => session.user_id !== user_id,
);
public async deleteByUserId({ user_id }: { user_id: string }): Promise<void> {
const params: DeleteItemCommandInput = {
TableName: this.dynamoConfig.tableName,
Key: marshall({
PK: this.PK,
SK: `user_id:${user_id}`,
}),
};

await this.dynamoConfig.client.send(new DeleteItemCommand(params));
}

async findByUserId({
public async findByUserId({
user_id,
}: {
user_id: string;
}): Promise<Session | null> {
const session = this.sessions.find(
(userSession) => userSession.user_id === user_id,
const params: GetItemCommandInput = {
TableName: this.dynamoConfig.tableName,
Key: marshall({
PK: this.PK,
SK: `user_id:${user_id}`,
}),
};

const { Item } = await this.dynamoConfig.client.send(
new GetItemCommand(params),
);

return session || null;
if (!Item) {
return null;
}

const session = unmarshall(Item).Content as Session;

return session;
}
}
Loading

0 comments on commit 12f8716

Please sign in to comment.