Skip to content
This repository has been archived by the owner on May 11, 2021. It is now read-only.

Commit

Permalink
feat(tip): add disabling tips
Browse files Browse the repository at this point in the history
  • Loading branch information
igorkamyshev committed Mar 28, 2019
1 parent 86fa050 commit 3b285b0
Show file tree
Hide file tree
Showing 14 changed files with 184 additions and 28 deletions.
13 changes: 13 additions & 0 deletions back/evolutions/4.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
CREATE TABLE public.disabled_tip (
"token" character varying NOT NULL,
"expireAt" timestamp with time zone NOT NULL,
"userLogin" character varying NOT NULL
);

ALTER TABLE ONLY public.disabled_tip
ADD CONSTRAINT "PK_disabled_tip_token" PRIMARY KEY ("token"),
ADD CONSTRAINT "FK_disabled_tip_login" FOREIGN KEY ("userLogin") REFERENCES public."user"(login);

#DOWN

DROP TABLE public.disabled_tip;
2 changes: 2 additions & 0 deletions back/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"bcryptjs": "^2.4.3",
"cors": "^2.8.5",
"handlebars": "^4.1.0",
"md5": "^2.2.1",
"morgan": "^1.9.1",
"nanoid": "^2.0.1",
"nest-telegram": "^0.2.3",
Expand All @@ -33,6 +34,7 @@
},
"devDependencies": {
"@types/cors": "^2.8.4",
"@types/md5": "^2.1.33",
"@types/string-similarity": "^3.0.0",
"nodemon": "^1.18.9"
}
Expand Down
8 changes: 4 additions & 4 deletions back/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ export class AppModule implements NestModule {
public configure(consumer: MiddlewareConsumer) {
this.telegramBot.init(this.moduleRef)

if (this.config.isDev()) {
// in dev use long poll
this.telegramBot.startPolling()
}
// if (this.config.isDev()) {
// // in dev use long poll
// this.telegramBot.startPolling()
// }
}
}
19 changes: 19 additions & 0 deletions back/src/mind/application/TipsFilter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Injectable } from '@nestjs/common'

import { TipModel } from '@shared/models/mind/TipModel'

import { DisabledTipRepository } from '../domain/DisabledTipRepository'

@Injectable()
export class TipsFilter {
public constructor(private readonly disabledTipRepo: DisabledTipRepository) {}

public async filter(
tips: TipModel[],
userLogin: string,
): Promise<TipModel[]> {
const disabledTokens = await this.disabledTipRepo.findTokens(userLogin)

return tips.filter(tip => !disabledTokens.includes(tip.token))
}
}
28 changes: 22 additions & 6 deletions back/src/mind/application/TypoFinder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,34 @@ export class TypoFinder {
private matches(variants: string[]) {
const TYPO_THRESHOLD = 0.8

// TODO: remove duplicates like {fd => fs} and {fs => fd}
return variants
const pairs = variants
.map(variant => ({
original: variant,
bestMatch: findBestMatch(variant, variants.filter(v => v !== variant))
.bestMatch,
}))
.filter(({ bestMatch }) => !!bestMatch)
.filter(({ bestMatch }) => bestMatch.rating > TYPO_THRESHOLD)
.map(({ original, bestMatch }) => ({
original,
suggest: bestMatch.target,
}))
.map(({ original, bestMatch }) => [original, bestMatch.target])

// Do not rewrite this with `reduce`, please
const sameSets: string[][] = []

pairs.forEach(pair => {
const exist = sameSets.find(same =>
pair.some(suggestion => same.includes(suggestion)),
)

if (!exist) {
// add new set — pair
sameSets.push(pair)
return
}

// add pait to exist set
exist.push(...pair)
})

return sameSets.map(same => [...new Set(same)])
}
}
35 changes: 28 additions & 7 deletions back/src/mind/application/adviser/TypoAdviser.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import * as md5 from 'md5'

import { TipModel } from '@shared/models/mind/TipModel'
import { TipAction } from '@shared/enum/TipAction'

Expand All @@ -17,18 +19,37 @@ export class TypoAdviser implements Adviser {

const now = new Date()

// TODO: add real action and meta (original and suggest)
return [
...sourceTypos.map(() => ({
...sourceTypos.map(sources => ({
date: now,
theme: `Possibly typo in source`,
action: TipAction.Nothing,
action: TipAction.MergeSources,
meta: sources,
token: this.createToken(sources, TipAction.MergeSources, userLogin),
})),
...categoryTypos.map(() => ({
...categoryTypos.map(categories => ({
date: now,
theme: `Possibly typo in category`,
action: TipAction.Nothing,
action: TipAction.MergeCategories,
meta: categories,
token: this.createToken(
categories,
TipAction.MergeCategories,
userLogin,
),
})),
]
}

private createToken(
variants: string[],
action: TipAction,
userLogin: string,
): string {
const payload = {
variants: variants.sort(),
action,
userLogin,
}

return md5(JSON.stringify(payload))
}
}
25 changes: 25 additions & 0 deletions back/src/mind/domain/DisabledTip.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Column, Entity, PrimaryColumn, ManyToOne } from 'typeorm'
import { addYears } from 'date-fns'
import { User } from '@back/user/domain/User.entity'

@Entity()
export class DisabledTip {
// Token must identify tip (include "for who")
@PrimaryColumn()
public readonly token: string

@Column()
public readonly expireAt: Date

@ManyToOne(type => User)
public readonly user: User

public constructor(token: string, user: User, expireAt: Date = null) {
this.token = token
this.user = user

// I guess my app will die after 100 years 🤓
// if not: people from the future, please forgive me
this.expireAt = !!expireAt ? expireAt : addYears(new Date(), 100)
}
}
30 changes: 30 additions & 0 deletions back/src/mind/domain/DisabledTipRepository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Injectable } from '@nestjs/common'
import { InjectRepository } from '@nestjs/typeorm'
import { Repository } from 'typeorm'

import { DisabledTip } from './DisabledTip.entity'

@Injectable()
class DisabledTipRepo {
public constructor(
@InjectRepository(DisabledTip)
private readonly disabledTipRepo: Repository<DisabledTip>,
) {}

public async findTokens(userLogin: string): Promise<string[]> {
const now = new Date().toISOString()

const result = await this.disabledTipRepo
.createQueryBuilder('tip')
.innerJoin('tip.user', 'user', 'user.login = :userLogin', {
userLogin,
})
.where('tip.expireAt >= :now', { now })
.getMany()

return result.map(({ token }) => token)
}
}

export const DisabledTipRepository = DisabledTipRepo
export type DisabledTipRepository = DisabledTipRepo
14 changes: 12 additions & 2 deletions back/src/mind/mind.module.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common'
import { ModuleRef } from '@nestjs/core'
import { TypeOrmModule } from '@nestjs/typeorm'

import { MoneyModule } from '@back/money/money.module'
import { UserModule } from '@back/user/user.module'
Expand All @@ -8,11 +9,20 @@ import { TypoFinder } from './application/TypoFinder'
import { TipController } from './presentation/http/controller/TipController'
import { TypoAdviser } from './application/adviser/TypoAdviser'
import { AdviserUnity } from './infrastructure/adviser/AdviserUnity'
import { TipsFilter } from './application/TipsFilter'
import { DisabledTip } from './domain/DisabledTip.entity'
import { DisabledTipRepository } from './domain/DisabledTipRepository'

@Module({
imports: [UserModule, MoneyModule],
imports: [UserModule, MoneyModule, TypeOrmModule.forFeature([DisabledTip])],
controllers: [TipController],
providers: [TypoFinder, TypoAdviser, AdviserUnity],
providers: [
TypoFinder,
TypoAdviser,
AdviserUnity,
TipsFilter,
DisabledTipRepository,
],
})
export class MindModule implements NestModule {
public constructor(
Expand Down
12 changes: 10 additions & 2 deletions back/src/mind/presentation/http/controller/TipController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { OnlyForUsers } from '@back/user/presentation/http/security/OnlyForUsers
import { TokenPayload } from '@back/user/application/dto/TokenPayload'
import { CurrentUser } from '@back/user/presentation/http/decorator/CurrentUser'
import { AdviserUnity } from '@back/mind/infrastructure/adviser/AdviserUnity'
import { TipsFilter } from '@back/mind/application/TipsFilter'

import { TipResponse } from '../reponse/TipResponse'

Expand All @@ -18,7 +19,10 @@ import { TipResponse } from '../reponse/TipResponse'
@ApiUseTags('mind')
@ApiBearerAuth()
export class TipController {
public constructor(private readonly adviser: AdviserUnity) {}
public constructor(
private readonly adviser: AdviserUnity,
private readonly tipsFilter: TipsFilter,
) {}

@Get()
@ApiOperation({ title: 'Get all available tips' })
Expand All @@ -31,6 +35,10 @@ export class TipController {
@CurrentUser()
user: TokenPayload,
): Promise<TipResponse[]> {
return this.adviser.giveAdvice(user.login)
const allTips = await this.adviser.giveAdvice(user.login)

const activeTips = await this.tipsFilter.filter(allTips, user.login)

return activeTips
}
}
11 changes: 7 additions & 4 deletions back/src/mind/presentation/http/reponse/TipResponse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,18 @@ import { TipModel } from '@shared/models/mind/TipModel'
import { TipAction } from '@shared/enum/TipAction'

export class TipResponse implements TipModel {
@ApiModelProperty({ example: '1fhkjsdhfsj23' })
public readonly token: string

@ApiModelProperty({ example: new Date() })
public readonly date: Date

@ApiModelProperty({ example: 'New version' })
public readonly theme: string

@ApiModelProperty({
example: TipAction.Nothing,
example: TipAction.MergeSources,
enum: Object.values(TipAction),
})
public readonly action: TipAction

@ApiModelProperty({ example: ['NASA', 'NAsA'] })
public readonly meta: any
}
3 changes: 2 additions & 1 deletion shared/enum/TipAction.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export enum TipAction {
Nothing,
MergeSources,
MergeCategories,
}
5 changes: 3 additions & 2 deletions shared/models/mind/TipModel.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { TipAction } from '@shared/enum/TipAction'

export interface TipModel {
export interface TipModel<Meta = any> {
token: string
date: Date
theme: string
action: TipAction
meta: Meta
}
7 changes: 7 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1216,6 +1216,13 @@
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.120.tgz#cf265d06f6c7a710db087ed07523ab8c1a24047b"
integrity sha512-jQ21kQ120mo+IrDs1nFNVm/AsdFxIx2+vZ347DbogHJPd/JzKNMOqU6HCYin1W6v8l5R9XSO2/e9cxmn7HAnVw==

"@types/md5@^2.1.33":
version "2.1.33"
resolved "https://registry.yarnpkg.com/@types/md5/-/md5-2.1.33.tgz#8c8dba30df4ad0e92296424f08c4898dd808e8df"
integrity sha512-8+X960EtKLoSblhauxLKy3zzotagjoj3Jt1Tx9oaxUdZEPIBl+mkrUz6PNKpzJgkrKSN9YgkWTA29c0KnLshmA==
dependencies:
"@types/node" "*"

"@types/mime@*":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.0.tgz#5a7306e367c539b9f6543499de8dd519fac37a8b"
Expand Down

0 comments on commit 3b285b0

Please sign in to comment.