Skip to content
This repository has been archived by the owner on Nov 7, 2020. It is now read-only.

Commit

Permalink
feat: initial follow implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
mentos1386 committed Jul 30, 2018
1 parent 6a311a8 commit 2fd6acd
Show file tree
Hide file tree
Showing 23 changed files with 497 additions and 6 deletions.
12 changes: 7 additions & 5 deletions src/api.ts
Expand Up @@ -33,13 +33,15 @@ async function bootstrap() {
'https://github.com/evebook/api/wiki')
.setVersion(process.env.npm_package_version)
.setSchemes(process.env.NODE_ENV === 'production' ? 'https' : 'http')
.addTag('characters')
.addTag('corporations')
.addTag('alliances')
.addTag('characters', 'EVE Online Characters')
.addTag('corporations', 'EVE Online Corporations')
.addTag('alliances', 'EVE Online Alliances')
.addTag('comments')
.addTag('posts')
.addTag('search')
.addTag('status')
.addTag('follow')
.addTag('notifications')
.addTag('search', 'Search for everything in EVE Online')
.addTag('status', 'EVE Book API and ESI status')
.addTag('authentication', 'Authentication proxy for EVE SSO service')
.addBearerAuth('Authorization', 'header')
.build();
Expand Down
4 changes: 4 additions & 0 deletions src/modules/alliance/alliance.entity.ts
Expand Up @@ -13,6 +13,7 @@ import { Corporation } from '../corporation/corporation.entity';
import { Post } from '../post/post.entity';
import { IAllianceIcon } from './alliance.interface';
import { Comment } from '../comment/comment.entity';
import { Follow } from '../follow/follow.entity';

@Entity()
export class Alliance {
Expand Down Expand Up @@ -48,6 +49,9 @@ export class Alliance {
@OneToMany(type => Comment, comment => comment.alliance)
comments: Comment[];

@OneToMany(type => Follow, follow => follow.followingAlliance)
followers: Follow[];

@UpdateDateColumn()
updatedAt: Date;

Expand Down
2 changes: 2 additions & 0 deletions src/modules/api.module.ts
Expand Up @@ -20,6 +20,7 @@ import { MorganModule, MorganInterceptor } from 'nest-morgan';
import { APP_INTERCEPTOR } from '@nestjs/core';
import { LOGGER_LEVEL } from './core/logger/logger.constants';
import { StatusModule } from './core/status/status.module';
import { FollowModule } from './follow/follow.module';

@Module({
imports: [
Expand Down Expand Up @@ -52,6 +53,7 @@ import { StatusModule } from './core/status/status.module';
CommentModule,
NotificationModule,
WebsocketModule,
FollowModule,
],
providers: [
{
Expand Down
7 changes: 7 additions & 0 deletions src/modules/character/character.entity.ts
Expand Up @@ -15,6 +15,7 @@ import { ICharacterPortrait } from './character.interface';
import { KillmailParticipant } from '../killmail/participant/participant.entity';
import { Corporation } from '../corporation/corporation.entity';
import { Notification } from '../notification/notification.entity';
import { Follow } from '../follow/follow.entity';

@Entity()
export class Character {
Expand Down Expand Up @@ -49,6 +50,12 @@ export class Character {
@OneToMany(type => Notification, notification => notification.recipient)
notifications: Notification[];

@OneToMany(type => Follow, follow => follow.follower)
following: Follow[];

@OneToMany(type => Follow, follow => follow.followingCharacter)
followers: Follow[];

@CreateDateColumn()
createdAt: Date;

Expand Down
4 changes: 4 additions & 0 deletions src/modules/corporation/corporation.entity.ts
Expand Up @@ -14,6 +14,7 @@ import { Alliance } from '../alliance/alliance.entity';
import { Post } from '../post/post.entity';
import { ICorporationIcon } from './corporation.interface';
import { Comment } from '../comment/comment.entity';
import { Follow } from '../follow/follow.entity';

@Entity()
export class Corporation {
Expand Down Expand Up @@ -57,6 +58,9 @@ export class Corporation {
@OneToOne(type => Alliance, alliance => alliance.executorCorporation)
executingAlliance: Alliance;

@OneToMany(type => Follow, follow => follow.followingCorporation)
followers: Follow[];

@Column({ nullable: true })
createdAt?: Date;

Expand Down
6 changes: 6 additions & 0 deletions src/modules/follow/commands/follow.command.ts
@@ -0,0 +1,6 @@
import { ICommand } from '@nestjs/cqrs';
import { Follow } from '../follow.entity';

export class FollowCommand implements ICommand {
constructor(public readonly follow: Follow) {}
}
27 changes: 27 additions & 0 deletions src/modules/follow/commands/handlers/follow.command.ts
@@ -0,0 +1,27 @@
import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs';
import { InjectRepository } from '@nestjs/typeorm';
import { FollowCommand } from '../follow.command';
import { FollowRepository } from '../../follow.repository';

@CommandHandler(FollowCommand)
export class FollowCommandHandler implements ICommandHandler<FollowCommand> {
constructor(
@InjectRepository(FollowRepository)
private readonly repository: FollowRepository,
private readonly publisher: EventPublisher,
) {
}

async execute(command: FollowCommand, resolve: (value?) => void) {
const { follow } = command;
const entity = this.publisher.mergeObjectContext(
await this.repository.save(follow),
);

// Sends actual event
await entity.follow();

entity.commit();
resolve(entity);
}
}
7 changes: 7 additions & 0 deletions src/modules/follow/commands/handlers/index.ts
@@ -0,0 +1,7 @@
import { FollowCommandHandler } from "./follow.command";
import { UnFollowCommandHandler } from "./unfollow.command";

export const commandHandlers = [
FollowCommandHandler,
UnFollowCommandHandler,
];
27 changes: 27 additions & 0 deletions src/modules/follow/commands/handlers/unfollow.command.ts
@@ -0,0 +1,27 @@
import { CommandHandler, EventPublisher, ICommandHandler } from '@nestjs/cqrs';
import { InjectRepository } from '@nestjs/typeorm';
import { FollowRepository } from '../../follow.repository';
import { UnFollowCommand } from '../unfollow.command';

@CommandHandler(UnFollowCommand)
export class UnFollowCommandHandler implements ICommandHandler<UnFollowCommand> {
constructor(
@InjectRepository(FollowRepository)
private readonly repository: FollowRepository,
private readonly publisher: EventPublisher,
) {
}

async execute(command: UnFollowCommand, resolve: (value?) => void) {
const { follow } = command;

// Sends actual event
await follow.unFollow();

follow.commit();

await this.repository.delete(follow.id);

resolve(follow);
}
}
6 changes: 6 additions & 0 deletions src/modules/follow/commands/unfollow.command.ts
@@ -0,0 +1,6 @@
import { ICommand } from '@nestjs/cqrs';
import { Follow } from '../follow.entity';

export class UnFollowCommand implements ICommand {
constructor(public readonly follow: Follow) {}
}
16 changes: 16 additions & 0 deletions src/modules/follow/events/follow.event.ts
@@ -0,0 +1,16 @@
import { IEvent } from '@nestjs/cqrs';
import { Follow } from '../follow.entity';

export class FollowEvent implements IEvent {
constructor(public readonly follow: Follow) {
}
}

export class FollowCharacterEvent extends FollowEvent {
}

export class FollowCorporationEvent extends FollowEvent {
}

export class FollowAllianceEvent extends FollowEvent {
}
27 changes: 27 additions & 0 deletions src/modules/follow/events/handlers/follow.character.handler.ts
@@ -0,0 +1,27 @@
import { EventsHandler, IEventHandler, CommandBus } from '@nestjs/cqrs';
import { FollowEvent, FollowCharacterEvent } from '../follow.event';
import { CreateNotificationCommand } from '../../../notification/commands/create.command';
import { Notification } from '../../../notification/notification.entity';
import { NOTIFICATION_TYPE } from '../../../notification/notification.constants';
import * as uuidv4 from 'uuid/v4';

@EventsHandler(FollowCharacterEvent)
export class FollowCharacterEventHandler implements IEventHandler<FollowCharacterEvent> {

constructor(
private commandBus: CommandBus,
) {
}

async handle(event: FollowEvent) {
const eventUuid = uuidv4();

const notification = new Notification();
notification.type = NOTIFICATION_TYPE.NEW_FOLLOWER;
notification.recipient = event.follow.followingCharacter;
notification.senderCharacter = event.follow.follower;
notification.eventUuid = eventUuid;

this.commandBus.execute(new CreateNotificationCommand(notification))
}
}
5 changes: 5 additions & 0 deletions src/modules/follow/events/handlers/index.ts
@@ -0,0 +1,5 @@
import { FollowCharacterEventHandler } from "./follow.character.handler";

export const eventHandlers = [
FollowCharacterEventHandler,
];
7 changes: 7 additions & 0 deletions src/modules/follow/events/unfollow.event.ts
@@ -0,0 +1,7 @@
import { IEvent } from '@nestjs/cqrs';
import { Follow } from '../follow.entity';

export class UnFollowEvent implements IEvent {
constructor(public readonly follow: Follow) {
}
}
5 changes: 5 additions & 0 deletions src/modules/follow/follow.constants.ts
@@ -0,0 +1,5 @@

export enum FOLLOW_TYPE {
FOLLOW = 'FOLLOW',
UN_FOLLOW = 'UN_FOLLOW',
}
97 changes: 97 additions & 0 deletions src/modules/follow/follow.controller.ts
@@ -0,0 +1,97 @@
import { ApiUseTags, ApiResponse, ApiBearerAuth } from "@nestjs/swagger";
import { Controller, HttpStatus, Param, UseGuards, Post } from "@nestjs/common";
import { Character } from "../character/character.entity";
import { AuthenticatedCharacter } from "../authentication/authentication.decorators";
import { AuthenticationGuard } from "../authentication/authentication.guard";
import { DFollow } from "./follow.dto";
import { FollowService } from "./follow.service";
import { CharacterService } from "../character/character.service";
import { CorporationService } from "../corporation/corporation.service";
import { AllianceService } from "../alliance/alliance.service";
import { FOLLOW_TYPE } from "./follow.constants";

@ApiUseTags('follow')
@Controller('follow')
export class FollowController {

constructor(
private followService: FollowService,
private characterService: CharacterService,
private corporationService: CorporationService,
private allianceService: AllianceService,
){
}

@ApiResponse({
status: HttpStatus.OK,
type: DFollow,
description: 'Follow a character',
})
@ApiBearerAuth()
@UseGuards(AuthenticationGuard)
@Post('character/:characterId')
public async followCharacter(
@Param('characterId') characterId: number,
@AuthenticatedCharacter() follower: Character,
): Promise<DFollow> {
const character = await this.characterService.get(characterId);
const follow = await this.followService.checkIfFolowingCharacter(follower, character);

if (follow) {
await this.followService.unfollow(follow);
return new DFollow(FOLLOW_TYPE.UN_FOLLOW);
}

const newFollow = await this.followService.followCharacter(follower, character);
return new DFollow(FOLLOW_TYPE.FOLLOW);
}

@ApiResponse({
status: HttpStatus.OK,
type: DFollow,
description: 'Follow a corporation',
})
@ApiBearerAuth()
@UseGuards(AuthenticationGuard)
@Post('corporation/:corporationId')
public async followCorporation(
@Param('corporationId') corporationId: number,
@AuthenticatedCharacter() follower: Character,
): Promise<DFollow> {
const corporation = await this.corporationService.get(corporationId);
const follow = await this.followService.checkIfFolowingCorporation(follower, corporation);

if (follow) {
await this.followService.unfollow(follow);
return new DFollow(FOLLOW_TYPE.UN_FOLLOW);
}

const newFollow = await this.followService.followCorporation(follower, corporation);
return new DFollow(FOLLOW_TYPE.FOLLOW);
}

@ApiResponse({
status: HttpStatus.OK,
type: DFollow,
description: 'Follow an Alliance',
})
@ApiBearerAuth()
@UseGuards(AuthenticationGuard)
@Post('alliance/:allianceId')
public async followAlliance(
@Param('allianceId') allianceId: number,
@AuthenticatedCharacter() follower: Character,
): Promise<DFollow> {
const alliance = await this.allianceService.get(allianceId);
const follow = await this.followService.checkIfFolowingAlliance(follower, alliance);

if (follow) {
await this.followService.unfollow(follow);
return new DFollow(FOLLOW_TYPE.UN_FOLLOW);
}

const newFollow = await this.followService.followAlliance(follower, alliance);
return new DFollow(FOLLOW_TYPE.FOLLOW);
}

}
9 changes: 9 additions & 0 deletions src/modules/follow/follow.dto.ts
@@ -0,0 +1,9 @@
import { FOLLOW_TYPE } from "./follow.constants";

export class DFollow {
type: FOLLOW_TYPE;

constructor(type: FOLLOW_TYPE) {
this.type = type;
}
}
41 changes: 41 additions & 0 deletions src/modules/follow/follow.entity.ts
@@ -0,0 +1,41 @@
import { Entity, PrimaryGeneratedColumn, ManyToOne } from "typeorm";
import { AggregateRoot } from "@nestjs/cqrs";
import { Character } from "../character/character.entity";
import { Corporation } from "../corporation/corporation.entity";
import { Alliance } from "../alliance/alliance.entity";
import { UnFollowEvent } from "./events/unfollow.event";
import { FollowEvent, FollowCharacterEvent, FollowCorporationEvent, FollowAllianceEvent } from "./events/follow.event";

@Entity()
export class Follow extends AggregateRoot {

@PrimaryGeneratedColumn('uuid')
id: string;

@ManyToOne(type => Character, character => character.following)
follower: Character;

@ManyToOne(type => Character, character => character.followers)
followingCharacter: Character;

@ManyToOne(type => Corporation, corporation => corporation.followers)
followingCorporation: Corporation;

@ManyToOne(type => Alliance, alliance => alliance.followers)
followingAlliance: Alliance;

async unFollow(): Promise<void> {
await this.apply(new UnFollowEvent(this));
}

async follow(): Promise<void> {
if (this.followingCharacter) {
await this.apply(new FollowCharacterEvent(this));
} else if(this.followingCorporation) {
await this.apply(new FollowCorporationEvent(this));
} else if(this.followingAlliance) {
await this.apply(new FollowAllianceEvent(this));
}
}

}

0 comments on commit 2fd6acd

Please sign in to comment.