Skip to content

Commit

Permalink
Merge pull request #72 from mapeveri/feat/71
Browse files Browse the repository at this point in the history
Add user interests #71
  • Loading branch information
mapeveri authored Dec 23, 2023
2 parents 1e9a330 + 990ee80 commit fb0d7a0
Show file tree
Hide file tree
Showing 42 changed files with 190 additions and 57 deletions.
15 changes: 15 additions & 0 deletions migrations/1703275398290-addInterestsFieldToUser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { MigrationInterface, QueryRunner } from 'typeorm';

export class AddInterestsFieldToUser1703275398290 implements MigrationInterface {
name = 'AddInterestsFieldToUser1703275398290';

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "users" ADD "interests" text NULL`);
await queryRunner.query(`UPDATE "users" SET "interests" = ARRAY[]::varchar[]`);
await queryRunner.query(`ALTER TABLE "users" ALTER COLUMN "interests" SET NOT NULL`);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "users" DROP COLUMN "interests"`);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import CreateCountryCommand from './createCountryCommand';
import Country from '@src/languages/domain/country/country';
import CountryId from '@src/languages/domain/country/valueObjects/countryId';
import LanguageCollection from '@src/languages/domain/country/valueObjects/languageCollection';
import CountryAlreadyExistsException from '@src/languages/domain/country/exceptions/CountryAlreadyExistsException';
import CountryAlreadyExistsException from '@src/languages/domain/country/exceptions/countryAlreadyExistsException';
import { Inject } from '@src/shared/domain/injector/inject.decorator';
import { CommandHandler, ICommandHandler } from '@src/shared/domain/buses/commandBus/commandHandler';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import Expression from '@src/languages/domain/expression/expression';
import CountryId from '@src/languages/domain/country/valueObjects/countryId';
import ExpressionTermCollection from '@src/languages/domain/expression/valueObjects/expressionTermCollection';
import UserId from '@src/languages/domain/user/valueObjects/userId';
import ExpressionAlreadyExistsException from '@src/languages/domain/expression/exceptions/ExpressionAlreadyExistsException';
import ExpressionAlreadyExistsException from '@src/languages/domain/expression/exceptions/expressionAlreadyExistsException';
import { Inject } from '@src/shared/domain/injector/inject.decorator';
import { CommandHandler, ICommandHandler } from '@src/shared/domain/buses/commandBus/commandHandler';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { COMMAND_BUS, CommandBus } from '@src/shared/domain/buses/commandBus/com
import { Inject } from '@src/shared/domain/injector/inject.decorator';
import { EventsHandler, IEventHandler } from '@src/shared/domain/buses/eventBus/eventsHandler';
import TermType from '@src/languages/domain/term/valueObjects/termType';
import TermCreatedFailedEvent from '@src/languages/domain/term/domainEvents/TermCreatedFailedEvent';
import TermCreatedFailedEvent from '@src/languages/domain/term/domainEvents/termCreatedFailedEvent';
import DeleteExpressionCommand from '../../command/delete/deleteExpressionCommand';

@EventsHandler(TermCreatedFailedEvent)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import Term from '@src/languages/domain/term/term';
import { Inject } from '@src/shared/domain/injector/inject.decorator';
import TermType from '@src/languages/domain/term/valueObjects/termType';
import { EVENT_BUS, EventBus } from '@src/shared/domain/buses/eventBus/eventBus';
import TermCreatedFailedEvent from '@src/languages/domain/term/domainEvents/TermCreatedFailedEvent';
import TermCreatedFailedEvent from '@src/languages/domain/term/domainEvents/termCreatedFailedEvent';
import { IProjectionHandler, ProjectionHandler } from '@src/shared/domain/buses/projectionBus/projectionHandler';

@ProjectionHandler(CreateTermProjection)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ import TermRepository, { TERM_REPOSITORY } from '@src/languages/domain/term/term
import SearchTermResponse from './searchTermResponse';
import { Inject } from '@src/shared/domain/injector/inject.decorator';
import { IQueryHandler, QueryHandler } from '@src/shared/domain/buses/queryBus/queryHandler';
import TermCriteria from '@src/languages/domain/term/termCriteria';

@QueryHandler(SearchTermQuery)
export default class SearchTermQueryHandler implements IQueryHandler<SearchTermQuery> {
constructor(@Inject(TERM_REPOSITORY) private readonly termRepository: TermRepository) {}

async execute(query: SearchTermQuery): Promise<QueryResponse> {
const terms = await this.termRepository.search(query.term);
const terms = await this.termRepository.search(TermCriteria.from({ term: query.term }));
return SearchTermResponse.fromTerms(terms);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { Command } from '@src/shared/domain/buses/commandBus/command';

export default class UpdateUserCommand implements Command {
constructor(public readonly id: string, public readonly name: string, public readonly photo: string) {}
constructor(
public readonly id: string,
public readonly name: string,
public readonly photo: string,
public readonly interests: string[],
) {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,26 @@ import UpdateUserCommand from './updateUserCommand';
import UserId from '@src/languages/domain/user/valueObjects/userId';
import { Inject } from '@src/shared/domain/injector/inject.decorator';
import { CommandHandler, ICommandHandler } from '@src/shared/domain/buses/commandBus/commandHandler';
import UserFinder from '@src/languages/domain/user/services/UserFinder';
import UserFinder from '@src/languages/domain/user/services/userFinder';
import { EVENT_BUS, EventBus } from '@src/shared/domain/buses/eventBus/eventBus';

@CommandHandler(UpdateUserCommand)
export default class UpdateUserCommandHandler implements ICommandHandler<UpdateUserCommand> {
private readonly userFinder: UserFinder;

constructor(@Inject(USER_REPOSITORY) private readonly userRepository: UserRepository) {
constructor(
@Inject(USER_REPOSITORY) private readonly userRepository: UserRepository,
@Inject(EVENT_BUS) private readonly eventBus: EventBus,
) {
this.userFinder = new UserFinder(userRepository);
}

async execute(command: UpdateUserCommand): Promise<void> {
const user = await this.userFinder.find(UserId.of(command.id));

user.update(command.name, command.photo);
user.update(command.name, command.photo, command.interests);
await this.userRepository.save(user);

void this.eventBus.publish(user.pullDomainEvents());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,12 @@ import UserRepository, { USER_REPOSITORY } from '@src/languages/domain/user/user
import UserId from '@src/languages/domain/user/valueObjects/userId';
import { COMMAND_BUS, CommandBus } from '@src/shared/domain/buses/commandBus/commandBus';
import CreateUserCommand from '../../command/create/createUserCommand';
import UpdateUserCommand from '../../command/update/updateUserCommand';
import AuthSessionCreatedEvent from '@src/languages/domain/auth/domainEvents/authSessionCreatedEvent';
import { Inject } from '@src/shared/domain/injector/inject.decorator';
import { EventsHandler, IEventHandler } from '@src/shared/domain/buses/eventBus/eventsHandler';

@EventsHandler(AuthSessionCreatedEvent)
export default class CreateOrUpdateUserOnAuthSessionCreatedEventHandler
implements IEventHandler<AuthSessionCreatedEvent>
{
export default class CreateUserOnAuthSessionCreatedEventHandler implements IEventHandler<AuthSessionCreatedEvent> {
constructor(
@Inject(USER_REPOSITORY) private readonly userRepository: UserRepository,
@Inject(COMMAND_BUS) private readonly commandBus: CommandBus,
Expand All @@ -19,21 +16,12 @@ export default class CreateOrUpdateUserOnAuthSessionCreatedEventHandler
async handle(event: AuthSessionCreatedEvent): Promise<void> {
const userId = UserId.fromEmailWithValidation(event.id, event.email);
const user = await this.userRepository.findById(userId);
if (null === user) {
await this.createUser(userId, event);
if (null !== user) {
return;
}

await this.updateUser(userId, event);
}

private async createUser(userId: UserId, event: AuthSessionCreatedEvent): Promise<void> {
await this.commandBus.dispatch(
new CreateUserCommand(userId.toString(), event.name, event.email, event.token, event.provider, event.photo),
);
}

private async updateUser(userId: UserId, event: AuthSessionCreatedEvent): Promise<void> {
await this.commandBus.dispatch(new UpdateUserCommand(userId.toString(), event.name, event.photo));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { COMMAND_BUS, CommandBus } from '@src/shared/domain/buses/commandBus/com
import { Inject } from '@src/shared/domain/injector/inject.decorator';
import { EventsHandler, IEventHandler } from '@src/shared/domain/buses/eventBus/eventsHandler';
import TermType from '@src/languages/domain/term/valueObjects/termType';
import TermCreatedFailedEvent from '@src/languages/domain/term/domainEvents/TermCreatedFailedEvent';
import TermCreatedFailedEvent from '@src/languages/domain/term/domainEvents/termCreatedFailedEvent';
import DeleteWordCommand from '../../command/delete/deleteWordCommand';

@EventsHandler(TermCreatedFailedEvent)
Expand Down
7 changes: 7 additions & 0 deletions src/languages/domain/term/termCriteria.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default class TermCriteria {
constructor(public readonly term?: string, public readonly hashtags?: string[], public readonly limit?: number) {}

static from(params: { term?: string; hashtags?: string[]; limit?: number }) {
return new TermCriteria(params.term, params.hashtags, params.limit);
}
}
3 changes: 2 additions & 1 deletion src/languages/domain/term/termRepository.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import Term from './term';
import TermCriteria from '@src/languages/domain/term/termCriteria';

interface TermRepository {
search(term: string): Promise<Term[]>;
search(criteria: TermCriteria): Promise<Term[]>;

save(term: Term): Promise<void>;
}
Expand Down
25 changes: 25 additions & 0 deletions src/languages/domain/user/domainEvents/userUpdatedEvent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { DomainEvent } from '@src/shared/domain/buses/eventBus/domainEvent';

export default class UserUpdatedEvent extends DomainEvent {
constructor(
public readonly id: string,
public readonly name: string,
public readonly photo: string,
public readonly interests: string[],
eventId = '',
) {
super(id, eventId);
}

public static fromPrimitives(payload: { [key: string]: any }): DomainEvent {
return new this(payload['id'], payload['name'], payload['photo'], payload['interests'], payload['eventId']);
}

public static eventTypeName(): string {
return 'user.updated';
}

public static aggregateTypeName(): string {
return 'user';
}
}
13 changes: 10 additions & 3 deletions src/languages/domain/user/user.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,37 @@
import { AggregateRoot } from '@src/shared/domain/aggregate/aggregateRoot';
import UserId from './valueObjects/userId';
import Email from '@src/shared/domain/valueObjects/email';
import UserUpdatedEvent from '@src/languages/domain/user/domainEvents/userUpdatedEvent';

export default class User extends AggregateRoot {
id: UserId;
name: string;
provider: string;
email: Email;
photo: string;
interests: string[];

constructor(id: UserId, name: string, provider: string, email: Email, photo: string) {
constructor(id: UserId, name: string, provider: string, email: Email, photo: string, interests: string[]) {
super();

this.id = id;
this.name = name;
this.provider = provider;
this.email = email;
this.photo = photo;
this.interests = interests;
}

static create(id: UserId, name: string, provider: string, email: Email, photo: string): User {
return new this(id, name, provider, email, photo);
return new this(id, name, provider, email, photo, []);
}

update(name: string, photo: string): void {
update(name: string, photo: string, interests: string[]): void {
this.name = name;
this.photo = photo;
this.interests = interests;

this.record(new UserUpdatedEvent(this.id.value, this.name, this.photo, this.interests));
}

toPrimitives(): object {
Expand All @@ -35,6 +41,7 @@ export default class User extends AggregateRoot {
provider: this.provider,
email: this.email.toString(),
photo: this.photo,
interests: this.interests,
};
}
}
2 changes: 2 additions & 0 deletions src/languages/infrastructure/nestjs/controllers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import CountryGetController from '../ui/api/v1/controllers/countries/countryGetC
import CountryPostController from '../ui/api/v1/controllers/countries/countryPostController';
import RefreshTokenPostController from '../ui/api/v1/controllers/auth/refreshTokenPostController';
import FindSuggestionsTermController from '@src/languages/infrastructure/ui/api/v1/controllers/terms/findSuggestionsTermController';
import UserPutController from '@src/languages/infrastructure/ui/api/v1/controllers/user/userPutController';

export const controllers = [
LoginPostController,
Expand All @@ -20,4 +21,5 @@ export const controllers = [
CountriesGetController,
CountryGetController,
CountryPostController,
UserPutController,
];
4 changes: 2 additions & 2 deletions src/languages/infrastructure/nestjs/eventHandlers.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import DeleteExpressionOnTermCreatedFailedEventHandler from '@src/languages/application/expression/event/delete/deleteExpressionOnTermCreatedFailedEventHandler';
import CreateOnExpressionCreatedEventHandler from '@src/languages/application/term/event/create/createOnExpressionCreatedEventHandler';
import CreateOnWordCreatedEventHandler from '@src/languages/application/term/event/create/createOnWordCreatedEventHandler';
import CreateOrUpdateUserOnAuthSessionCreatedEventHandler from '@src/languages/application/user/event/createOrUpdate/createOrUpdateUserOnAuthSessionCreatedEventHandler';
import CreateUserOnAuthSessionCreatedEventHandler from '@src/languages/application/user/event/create/createUserOnAuthSessionCreatedEventHandler';
import DeleteWordOnTermCreatedFailedEventHandler from '@src/languages/application/word/event/delete/deleteWordOnTermCreatedFailedEventHandler';

export const events = [
CreateOnWordCreatedEventHandler,
CreateOrUpdateUserOnAuthSessionCreatedEventHandler,
CreateUserOnAuthSessionCreatedEventHandler,
CreateOnExpressionCreatedEventHandler,
DeleteExpressionOnTermCreatedFailedEventHandler,
DeleteWordOnTermCreatedFailedEventHandler,
Expand Down
2 changes: 1 addition & 1 deletion src/languages/infrastructure/nestjs/readLayers.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { FIND_SUGGESTIONS_TERM_READ_LAYER } from '@src/languages/application/term/query/suggestion/findSuggestionsTermReadLayer';
import MongoFindSuggestionsTermReadLayer from '@src/languages/infrastructure/readLayer/MongoFindSuggestionsTermReadLayer';
import MongoFindSuggestionsTermReadLayer from '@src/languages/infrastructure/readLayer/mongoFindSuggestionsTermReadLayer';

export const readLayers = [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,40 @@ import Term from '@src/languages/domain/term/term';
import TermRepository from '@src/languages/domain/term/termRepository';
import MongoRepository from '@src/shared/infrastructure/persistence/mongo/mongoRepository';
import { Document } from 'mongodb';
import TermCriteria from '@src/languages/domain/term/termCriteria';

@Injectable()
export default class MongoTermRepository extends MongoRepository<Term> implements TermRepository {
constructor() {
super('terms');
}

async search(term: string): Promise<Term[]> {
const regexTerm = new RegExp(term, 'i');
const searchQuery = {
$or: [{ title: regexTerm }, { description: regexTerm }, { example: regexTerm }],
};
async search(criteria: TermCriteria): Promise<Term[]> {
let result = [];
const searchQuery = {};
const term = criteria.term;
const hashtags = criteria.hashtags;

const result = await this.collection.find(searchQuery).project({ _id: 0 }).toArray();
if (term) {
const regexTerm = new RegExp(term, 'i');
Object.assign(searchQuery, {
$or: [{ title: regexTerm }, { description: regexTerm }, { example: regexTerm }],
});
}

if (hashtags) {
Object.assign(searchQuery, {
$or: [{ hashtags: hashtags }],
});
}

const query = this.collection.find(searchQuery).project({ _id: 0 });

if (criteria.limit) {
query.limit(criteria.limit);
}

result = await query.toArray();

return result.map((doc: Document) => {
return Term.create(doc.id, doc.title, doc.description, doc.example, doc.type, doc.hashtags, doc.totalLikes);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,8 @@ export default new EntitySchema<User>({
photo: {
type: String,
},
interests: {
type: 'simple-array',
},
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import Term from '@src/languages/domain/term/term';
import UserRepository, { USER_REPOSITORY } from '@src/languages/domain/user/userRepository';
import { Inject } from '@src/shared/domain/injector/inject.decorator';
import TermRepository, { TERM_REPOSITORY } from '@src/languages/domain/term/termRepository';
import UserFinder from '@src/languages/domain/user/services/UserFinder';
import UserFinder from '@src/languages/domain/user/services/userFinder';
import TermCriteria from '@src/languages/domain/term/termCriteria';

export default class MongoFindSuggestionsTermReadLayer implements FindSuggestionsTermReadLayer {
private readonly userFinder: UserFinder;
Expand All @@ -18,9 +19,9 @@ export default class MongoFindSuggestionsTermReadLayer implements FindSuggestion

async find(userId: UserId): Promise<Term[]> {
const user = await this.userFinder.find(userId);
console.log(user);

const terms = await this.termRepository.search('test');
const criteria = TermCriteria.from({ hashtags: user.interests, limit: 5 });
const terms = await this.termRepository.search(criteria);

return Promise.resolve(terms);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Request } from 'express';
import FindUserQuery from '@src/languages/application/user/query/find/findUserQuery';
import { Controller, Get, HttpCode, Inject, Req, UseGuards } from '@nestjs/common';
import { JwtAuthGuard } from '@src/shared/infrastructure/nestjs/guards/JwtAuthGuard';
import { JwtAuthGuard } from '@src/shared/infrastructure/nestjs/guards/jwtAuthGuard';
import MeGetResponseDto from './meGetResponseDto';
import {
ApiBadRequestResponse,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import FindCountriesQuery from '@src/languages/application/country/query/findAll/findCountriesQuery';
import { Controller, Get, HttpCode, Inject, UseGuards } from '@nestjs/common';
import { JwtAuthGuard } from '@src/shared/infrastructure/nestjs/guards/JwtAuthGuard';
import { JwtAuthGuard } from '@src/shared/infrastructure/nestjs/guards/jwtAuthGuard';
import CountryGetResponseDto from './countryGetResponse';
import {
ApiBadRequestResponse,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import FindCountryQuery from '@src/languages/application/country/query/find/findCountryQuery';
import { Controller, Get, HttpCode, Inject, Param, UseGuards } from '@nestjs/common';
import { JwtAuthGuard } from '@src/shared/infrastructure/nestjs/guards/JwtAuthGuard';
import { JwtAuthGuard } from '@src/shared/infrastructure/nestjs/guards/jwtAuthGuard';
import CountryGetResponseDto from './countryGetResponse';
import {
ApiBadRequestResponse,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import CreateCountryCommand from '@src/languages/application/country/command/cre
import { LanguagePrimitives } from '@src/languages/domain/country/valueObjects/language';
import { Body, Controller, HttpCode, HttpStatus, Inject, Post, UseGuards } from '@nestjs/common';
import CountryPostDto from './countryPostDto';
import { JwtAuthGuard } from '@src/shared/infrastructure/nestjs/guards/JwtAuthGuard';
import { JwtAuthGuard } from '@src/shared/infrastructure/nestjs/guards/jwtAuthGuard';
import {
ApiCreatedResponse,
ApiBadRequestResponse,
Expand Down
Loading

0 comments on commit fb0d7a0

Please sign in to comment.