Skip to content

Commit

Permalink
Added subscribers demonstration.
Browse files Browse the repository at this point in the history
  • Loading branch information
stemmlerjs committed Sep 26, 2019
1 parent b211266 commit 8d18135
Show file tree
Hide file tree
Showing 21 changed files with 378 additions and 19 deletions.
1 change: 0 additions & 1 deletion src/infra/sequelize/config/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ module.exports.connection = new Sequelize(database, username, password, {
port: 3306,
dialectOptions: {
multipleStatements: true,
useUTC: true,
},
pool: {
max: 5,
Expand Down
88 changes: 86 additions & 2 deletions src/infra/sequelize/migrations/20190625131808-initial-migration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,54 @@ import runner from '../runner'

export default {
up: (queryInterface, Sequelize) => {
const CREATE_BASE_USER = () => (
queryInterface.createTable('base_user', {
base_user_id: {
type: Sequelize.UUID,
defaultValue: Sequelize.UUIDV4,
allowNull: false,
primaryKey: true
},
first_name: {
type: Sequelize.STRING(250),
allowNull: false
},
last_name: {
type: Sequelize.STRING(250),
allowNull: false
},
user_email: {
type: Sequelize.STRING(250),
allowNull: false,
unique: true
},
user_password: {
type: Sequelize.STRING,
allowNull: true,
defaultValue: null
},
is_email_verified: {
type: Sequelize.BOOLEAN,
allowNull: false,
defaultValue: false
},
username: {
type: Sequelize.STRING(250),
allowNull: true
},
created_at: {
type: Sequelize.DATE,
allowNull: false,
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP'),
},
updated_at: {
type: Sequelize.DATE,
allowNull: false,
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP')
}
})
)

const CREATE_ARTIST = () => (
queryInterface.createTable('artist', {
artist_id: {
Expand Down Expand Up @@ -63,16 +111,52 @@ export default {
})
)

const CREATE_TRADER = () => (
queryInterface.createTable('trader', {
trader_id: {
type: Sequelize.UUID,
defaultValue: Sequelize.UUIDV4,
allowNull: false,
primaryKey: true
},
trader_base_id: {
type: Sequelize.UUID,
allowNull: false,
primaryKey: true,
references: {
model: 'base_user',
key: 'base_user_id'
},
onDelete: 'cascade',
onUpdate: 'cascade',
},
created_at: {
type: Sequelize.DATE,
allowNull: false,
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP'),
},
updated_at: {
type: Sequelize.DATE,
allowNull: false,
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP')
}
})
)

return runner.run([
() => CREATE_BASE_USER(),
() => CREATE_ARTIST(),
() => CREATE_VINYL()
() => CREATE_VINYL(),
() => CREATE_TRADER()
])
},

down: (queryInterface, Sequelize) => {
return runner.run([
() => queryInterface.dropTable('artist'),
() => queryInterface.dropTable('vinyl'),
() => queryInterface.dropTable('artist')
() => queryInterface.dropTable('trader'),
() => queryInterface.dropTable('base_user')
])
}
};
9 changes: 9 additions & 0 deletions src/infra/sequelize/models/baseUser.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,15 @@ module.exports = function(sequelize, DataTypes) {
allowNull: false,
unique: true
},
is_email_verified: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: false
},
username: {
type: DataTypes.STRING(250),
allowNull: true
},
user_password: {
type: DataTypes.STRING,
allowNull: true,
Expand Down
Binary file added src/modules/.DS_Store
Binary file not shown.
2 changes: 2 additions & 0 deletions src/modules/notification/domain/slackChannel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

export type SlackChannel = 'growth' | 'support';
2 changes: 2 additions & 0 deletions src/modules/notification/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

import "./subscribers";
8 changes: 8 additions & 0 deletions src/modules/notification/services/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

import { SlackService } from "./slack";

const slackService = new SlackService();

export {
slackService
}
33 changes: 33 additions & 0 deletions src/modules/notification/services/slack/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@

import axios from 'axios'
import { SlackChannel } from '../../domain/slackChannel';

export interface ISlackService {
sendMessage (text: string, channel: SlackChannel): Promise<any>
}

export class SlackService implements ISlackService {
private growthChannelHookUrl: string = 'https://hooks.slack.com/services/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';
private supportChannelHookUrl: string = 'https://hooks.slack.com/services/THKgeessd/Beese26CQ/mI66effeggeJCNa8bFVOwyAS';

constructor () {

}

private getWebookUrl (channel: SlackChannel): string {
switch (channel) {
case 'growth':
return this.growthChannelHookUrl;
case 'support':
return this.supportChannelHookUrl;
default:
return "";
}
}

sendMessage (text: string, channel: SlackChannel): Promise<any> {
const url: string = this.getWebookUrl(channel);
return axios.post(url, { text });
}

}
37 changes: 37 additions & 0 deletions src/modules/notification/subscribers/AfterUserCreated.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@

import { IHandle } from "../../../core/domain/events/IHandle";
import { DomainEvents } from "../../../core/domain/events/DomainEvents";
import { UserCreatedEvent } from "../../users/domain/events/userCreatedEvent";
import { NotifySlackChannel } from "../useCases/notifySlackChannel/NotifySlackChannel";
import { User } from "../../users/domain/user";

export class AfterUserCreated implements IHandle<UserCreatedEvent> {
private notifySlackChannel: NotifySlackChannel;

constructor (notifySlackChannel: NotifySlackChannel) {
this.setupSubscriptions();
this.notifySlackChannel = notifySlackChannel;
}

setupSubscriptions(): void {
DomainEvents.register(this.onUserCreatedEvent.bind(this), UserCreatedEvent.name);
}

private craftSlackMessage (user: User): string {
return `Hey! Guess who just joined us? => ${user.firstName} ${user.lastName}\n
Need to reach 'em? Their email is ${user.email}.`
}

private async onUserCreatedEvent (event: UserCreatedEvent): Promise<void> {
const { user } = event;

try {
await this.notifySlackChannel.execute({
channel: 'growth',
message: this.craftSlackMessage(user)
})
} catch (err) {

}
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@

import { IHandle } from "../../../../core/domain/events/IHandle";
import { VinylCreatedEvent } from "../../../vinyl/domain/events/vinylCreatedEvent";
import { DomainEvents } from "../../../../core/domain/events/DomainEvents";
import { IVinylRepo } from "../../../vinyl/repos/vinylRepo";
import { IVinylRepo } from "../../vinyl/repos/vinylRepo";
import { IHandle } from "../../../core/domain/events/IHandle";
import { VinylCreatedEvent } from "../../vinyl/domain/events/vinylCreatedEvent";
import { DomainEvents } from "../../../core/domain/events/DomainEvents";

export class VinylCreatedSubscription implements IHandle<VinylCreatedEvent> {
export class AfterVinylCreated implements IHandle<VinylCreatedEvent> {
private vinylRepo: IVinylRepo;

constructor () {
Expand Down
8 changes: 8 additions & 0 deletions src/modules/notification/subscribers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

import { AfterUserCreated } from "./AfterUserCreated";
import { notifySlackChannel } from "../useCases/notifySlackChannel";
import { AfterVinylCreated } from "./AfterVinylCreated";

// Subscribers
new AfterUserCreated(notifySlackChannel);
new AfterVinylCreated();
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@

import { UseCase } from "../../../../core/domain/UseCase";
import { SlackChannel } from "../../domain/slackChannel";
import { ISlackService } from "../../services/slack";

interface Request {
channel: SlackChannel;
message: string;
}

export class NotifySlackChannel implements UseCase<Request, Promise<void>> {
private slackService: ISlackService;

constructor (slackService: ISlackService) {
this.slackService = slackService;
}

async execute (req: Request): Promise<void> {
await this.slackService.sendMessage(req.message, req.channel);
}
}

9 changes: 9 additions & 0 deletions src/modules/notification/useCases/notifySlackChannel/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@

import { NotifySlackChannel } from "./NotifySlackChannel";
import { slackService } from "../../services";

const notifySlackChannel = new NotifySlackChannel(slackService);

export {
notifySlackChannel
}
14 changes: 12 additions & 2 deletions src/modules/users/domain/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ interface UserProps {
profilePicture?: string;
googleId?: number;
facebookId?: number;
username?: string;
}

export class User extends AggregateRoot<UserProps> {
Expand Down Expand Up @@ -59,6 +60,14 @@ export class User extends AggregateRoot<UserProps> {
return this.props.facebookId;
}

get username (): string {
return this.props.username;
}

set username (value: string) {
this.props.username = value;
}

private constructor (props: UserProps, id?: UniqueEntityID) {
super(props, id);
}
Expand All @@ -72,7 +81,7 @@ export class User extends AggregateRoot<UserProps> {
}

public static create (props: UserProps, id?: UniqueEntityID): Result<User> {

const guardedProps = [
{ argument: props.firstName, argumentName: 'firstName' },
{ argument: props.lastName, argumentName: 'lastName' },
Expand All @@ -92,7 +101,8 @@ export class User extends AggregateRoot<UserProps> {

else {
const user = new User({
...props
...props,
username: props.username ? props.username : '',
}, id);

const idWasProvided = !!id;
Expand Down
2 changes: 2 additions & 0 deletions src/modules/users/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

import "./subscribers";
28 changes: 26 additions & 2 deletions src/modules/users/mappers/UserMap.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@

import { Mapper } from "../../../core/infra/Mapper";
import { User } from "../domain/user";
import { UniqueEntityID } from "../../../core/domain/UniqueEntityID";
import { UserEmail } from "../domain/userEmail";
import { UserPassword } from "../domain/userPassword";

export class UserMap extends Mapper<User> {

Expand All @@ -8,9 +12,29 @@ export class UserMap extends Mapper<User> {
base_user_id: user.id.toString(),
user_email: user.email.value,
user_password: user.password.value,
user_first_name: user.firstName,
user_last_name: user.lastName
first_name: user.firstName,
last_name: user.lastName,
is_email_verified: user.isEmailVerified,
username: user.username
}
}

public static toDomain (raw: any): User {
const userEmailOrError = UserEmail.create(raw.user_email);
const userPasswordOrError = UserPassword.create(raw.user_password);

const userOrError = User.create({
email: userEmailOrError.getValue(),
password: userPasswordOrError.getValue(),
firstName: raw.first_name,
lastName: raw.last_name,
isEmailVerified: raw.is_email_verified,
username: raw.username
}, new UniqueEntityID(raw.base_user_id))

userOrError.isFailure ? console.log(userOrError.error) : '';

return userOrError.isSuccess ? userOrError.getValue() : null;
}

}
Loading

0 comments on commit 8d18135

Please sign in to comment.