Skip to content

Commit

Permalink
Merge pull request #363 from thematters/develop
Browse files Browse the repository at this point in the history
Update production server
  • Loading branch information
guoliu authored Jun 24, 2019
2 parents 221e430 + 78d3175 commit e526dda
Show file tree
Hide file tree
Showing 9 changed files with 266 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,5 @@ exports.up = async knex =>
`)

exports.down = function(knex, Promise) {
return knex.raw(/*sql*/ `drop view ${table}`)
return knex.raw(/*sql*/ `drop view if exists ${table}`)
}
115 changes: 115 additions & 0 deletions db/migrations/20190619172950_recreate_article_activity_view.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
const table = 'article_activity_view'

const materialized = 'article_activity_materialized'

exports.up = async knex => {
// Drop materialzied view
await knex.raw(`drop materialized view if exists ${materialized}`)

// Drop old view
await knex.raw(`drop view if exists ${table}`)

// Create new view
await knex.raw(`
create view ${table} as
select
article.*,
greatest (
latest_comment,
latest_collected_by,
latest_appreciation) as latest_activity
from
article
left join (
/* last comment activity */
select
max(created_at) as latest_comment,
article_id
from
comment
group by
article_id) as c on article.id = c.article_id
left join (
/* last collected by activity */
select
max(created_at) as latest_collected_by,
article_id
from
collection
group by
article_id) as a on article.id = a.article_id
left join (
/* last appreciation activity */
select
max(created_at) as latest_appreciation,
reference_id
from
transaction
where purpose = 'appreciate'
group by
reference_id) as ts on article.id = ts.reference_id
`)

// Re-create materialized view
await knex.raw(`
create materialized view ${materialized} as
select *
from article_activity_view
`)
}

exports.down = async knex => {
// Drop materialized view
await knex.raw(`drop materialized view if exists ${materialized}`)

// Drop new created view
await knex.raw(`drop view if exists ${table}`)

// Re-create old view
await knex.raw(`
create view ${table} as
select
article.*,
greatest (
latest_comment,
latest_downstream,
latest_appreciation) as latest_activity
from
article
left join (
/* last comment activity */
select
max(created_at) as latest_comment,
article_id
from
comment
group by
article_id) as c on article.id = c.article_id
left join (
/* last downstream activity */
select
max(created_at) as latest_downstream,
upstream_id
from
article
group by
upstream_id) as a on article.id = a.upstream_id
left join (
/* last appreciation activity */
select
max(created_at) as latest_appreciation,
reference_id
from
transaction
where purpose = 'appreciate'
group by
reference_id) as ts on article.id = ts.reference_id
`)

// Re-create materialized view
await knex.raw(`
create materialized view ${materialized} as
select *
from article_activity_view
`)
}
107 changes: 97 additions & 10 deletions src/connectors/articleService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,11 +202,21 @@ export class ArticleService extends BaseService {
/**
* Count articles by a given authorId (user).
*/
countByAuthor = async (authorId: string): Promise<number> => {
const result = await this.knex(this.table)
.where({ authorId, state: ARTICLE_STATE.active })
countByAuthor = async (
authorId: string,
activeOnly: boolean = true
): Promise<number> => {
let qs = this.knex(this.table)
.where({ authorId })
.count()
.first()

if (activeOnly) {
qs = qs.where({ state: ARTICLE_STATE.active })
}

const result = await qs

return parseInt(result.count, 10)
}

Expand Down Expand Up @@ -1060,7 +1070,6 @@ export class ArticleService extends BaseService {
/**
* Create a collection for article
*/

createCollection = async ({
entranceId,
articleIds
Expand All @@ -1078,6 +1087,48 @@ export class ArticleService extends BaseService {
return this.baseBatchCreate(items, 'collection')
}

/**
* Insert a single record to collection for article
*/
insertCollection = async ({
entranceId,
articleId,
order
}: {
entranceId: string
articleId: string
order: number
}) =>
this.baseCreate(
{
entranceId,
articleId,
order,
createdAt: new Date(),
updatedAt: new Date()
},
'collection'
)

/**
* Update a collection order by given entrance id and article id.
*/
updateCollectionOrder = async ({
entranceId,
articleId,
order
}: {
entranceId: string
articleId: string
order: number
}) => {
const [updatedItem] = await this.knex('collection')
.where({ entranceId, articleId })
.update({ order })
.returning('*')
return updatedItem
}

/**
* Delete a collection for article
*/
Expand All @@ -1091,6 +1142,21 @@ export class ArticleService extends BaseService {
return this.baseBatchDelete(ids, table)
}

/**
* Delete record of a collection by given entrance id and an array of article id.
*/
deleteCollectionByArticleIds = async ({
entranceId,
articleIds
}: {
entranceId: string
articleIds: string[]
}) =>
this.knex('collection')
.where({ entranceId })
.whereIn('articleId', articleIds)
.del()

/**
* Find single collection by given entrance id and article id.
*/
Expand All @@ -1115,14 +1181,20 @@ export class ArticleService extends BaseService {
offset = 0
}: {
entranceId: string
limit?: number
limit?: number | null
offset?: number
}) =>
await this.knex('collection')
}) => {
const query = this.knex('collection')
.select('article_id')
.where({ entranceId })
.limit(limit)
.offset(offset)
.orderBy('order', 'asc')

if (limit) {
query.limit(limit)
}
return query
}

/**
* Find an article is collected by which articles.
Expand Down Expand Up @@ -1164,6 +1236,22 @@ export class ArticleService extends BaseService {
return parseInt(result.count, 10)
}

/**
* Count an article is collected by how many active articles.
*/
countActiveCollectedBy = async (id: string) => {
const query = this.knex('collection')
.rightJoin('article', 'collection.entrance_id', 'article.id')
.where({
'collection.article_id': id,
'article.state': ARTICLE_STATE.active
})
.countDistinct('entrance_id')
.first()
const result = await query
return parseInt(result.count, 10)
}

/*********************************
* *
* Response *
Expand Down Expand Up @@ -1197,7 +1285,7 @@ export class ArticleService extends BaseService {
)
)
.from('collection')
.rightJoin('article', 'collection.article_id', 'article.id')
.rightJoin('article', 'collection.entrance_id', 'article.id')
.where({ 'collection.article_id': id, 'article.state': state })
})

Expand Down Expand Up @@ -1296,7 +1384,6 @@ export class ArticleService extends BaseService {
if (first) {
query.limit(first)
}

return query
}

Expand Down
3 changes: 2 additions & 1 deletion src/connectors/queue/publication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ class PublicationQueue {
}

private firstPostAward = async (authorId: string) => {
const count = await this.articleService.countByAuthor(authorId)
const count = await this.articleService.countByAuthor(authorId, false)

if (count === 1) {
const trx = await this.systemService.firstPostAward(authorId)

Expand Down
4 changes: 2 additions & 2 deletions src/connectors/queue/schedule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,14 +192,14 @@ class ScheduleQueue {
// repeat: { cron: '0 4 * * *', tz: 'Asia/Hong_Kong' }
// })

// refresh articleActivityMaterialized every 1.1 hour, for hottest recommendation
// refresh articleActivityMaterialized every 2 minutes, for hottest recommendation
this.q.add(
QUEUE_JOB.refreshView,
{ view: MATERIALIZED_VIEW.articleActivityMaterialized },
{
priority: QUEUE_PRIORITY.MEDIUM,
repeat: {
every: 1000 * 60 * 16 // every 15 + 1 minutes
every: 1000 * 60 * 2 // every 2 minutes
}
}
)
Expand Down
44 changes: 36 additions & 8 deletions src/mutations/article/setCollection.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { get } from 'lodash'
import { difference } from 'lodash'
import { MutationToSetCollectionResolver } from 'definitions'
import { EntityNotFoundError, ForbiddenError } from 'common/errors'
import { fromGlobalId } from 'common/utils'
Expand All @@ -8,8 +8,6 @@ const resolver: MutationToSetCollectionResolver = async (
{ input: { id, collection } },
{ viewer, dataSources: { articleService, notificationService } }
) => {
const userName = get(viewer, 'userName')

const entranceId = fromGlobalId(id).id
const article = await articleService.baseFindById(entranceId)
if (!article) {
Expand All @@ -20,14 +18,44 @@ const resolver: MutationToSetCollectionResolver = async (
throw new ForbiddenError('viewer has no permission')
}

const articleIds = collection.map(id => fromGlobalId(id).id)
// Compare new and old collections
const oldIds = (await articleService.findCollections({
entranceId,
limit: null
})).map(({ articleId }: any) => articleId)
const newIds = collection.map(id => fromGlobalId(id).id)
const addItems: any[] = []
const updateItems: any[] = []
const deleteItems: any[] = []
const diff = difference(newIds, oldIds)

// Gather data
newIds.map((id: string, index: number) => {
const indexOf = oldIds.indexOf(id)
if (indexOf < 0) {
addItems.push({ entranceId, articleId: id, order: index })
}
if (indexOf >= 0 && index !== indexOf) {
updateItems.push({ entranceId, articleId: id, order: index })
}
})

// Clean all existing collection and then insert
await articleService.deleteCollection({ entranceId })
await articleService.createCollection({ entranceId, articleIds })
// Add and update
await Promise.all([
...addItems.map((data: any) => articleService.insertCollection(data)),
...updateItems.map((data: any) =>
articleService.updateCollectionOrder(data)
)
])

// Delete unwanted
await articleService.deleteCollectionByArticleIds({
entranceId,
articleIds: difference(oldIds, newIds)
})

// trigger notifications
articleIds.forEach(async (id: string) => {
diff.forEach(async (id: string) => {
const collection = await articleService.baseFindById(id)
notificationService.trigger({
event: 'article_new_collected',
Expand Down
4 changes: 3 additions & 1 deletion src/mutations/user/confirmVerificationCode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ const resolver: MutationToConfirmVerificationCodeResolver = async (
{ input },
{ viewer, dataSources: { userService } }
) => {
const { email: rawEmail } = input
const email = rawEmail ? rawEmail.toLowerCase() : null
const [code] = await userService.findVerificationCodes({
where: { ...input, status: 'active' }
where: { ...input, email, status: 'active' }
})

if (!code) {
Expand Down
Loading

0 comments on commit e526dda

Please sign in to comment.