Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(campaign): support article w/o campaign stage #4225

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -661,7 +661,7 @@ input EditArticleInput {

input ArticleCampaignInput {
campaign: ID!
stage: ID!
stage: ID
}

input AppreciateArticleInput {
Expand Down
49 changes: 45 additions & 4 deletions src/connectors/campaignService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
shortHash,
toDatetimeRangeString,
fromDatetimeRangeString,
fromGlobalId,
// excludeSpam,
} from 'common/utils'
import { AtomService, NotificationService } from 'connectors'
Expand Down Expand Up @@ -298,7 +299,7 @@ export class CampaignService {

public updateArticleCampaigns = async (
article: Pick<Article, 'id' | 'authorId'>,
newCampaigns: Array<{ campaignId: string; campaignStageId: string }>
newCampaigns: Array<{ campaignId: string; campaignStageId?: string }>
) => {
const mutatedCampaignIds = []
const knexRO = this.connections.knexRO
Expand Down Expand Up @@ -326,7 +327,7 @@ export class CampaignService {
await this.models.update({
table: 'campaign_article',
where: { articleId: article.id, campaignId },
data: { campaignStageId },
data: { campaignStageId: campaignStageId ?? null },
})
mutatedCampaignIds.push(campaignId)
}
Expand All @@ -353,7 +354,7 @@ export class CampaignService {
public submitArticleToCampaign = async (
article: Pick<Article, 'id' | 'authorId'>,
campaignId: string,
campaignStageId: string
campaignStageId?: string
) => {
await this.validate({
userId: article.authorId,
Expand All @@ -372,7 +373,7 @@ export class CampaignService {
userId,
}: {
campaignId: string
campaignStageId: string
campaignStageId?: string
userId: string
}) => {
const campaign = await this.models.campaignIdLoader.load(campaignId)
Expand All @@ -392,6 +393,10 @@ export class CampaignService {
throw new ActionFailedError(`user not applied to campaign ${campaignId}`)
}

if (!campaignStageId) {
return
}

const stage = await this.models.campaignStageIdLoader.load(campaignStageId)
if (!stage) {
throw new CampaignStageNotFoundError('stage not found')
Expand Down Expand Up @@ -437,6 +442,7 @@ export class CampaignService {
application.createdAt.getTime() < end.getTime()
? OFFICIAL_NOTICE_EXTEND_TYPE.write_challenge_applied
: OFFICIAL_NOTICE_EXTEND_TYPE.write_challenge_applied_late_bird,
entities: [{ type: 'target', entityTable: 'campaign', entity: campaign }],
recipientId: updated.userId,
data: { link: campaign.link ?? '' },
})
Expand Down Expand Up @@ -522,4 +528,39 @@ export class CampaignService {
update: { campaignId: id, boost },
where: { campaignId: id },
})

public validateCampaigns = async (
campaigns: Array<{ campaign: string; stage?: string }>,
userId: string
) => {
const _campaigns = campaigns.map(
({ campaign: campaignGlobalId, stage: stageGlobalId }) => {
const { id: campaignId, type: campaignIdType } =
fromGlobalId(campaignGlobalId)
if (campaignIdType !== NODE_TYPES.Campaign) {
throw new UserInputError('invalid campaign id')
}

if (!stageGlobalId) {
return { campaign: campaignId }
}

const { id: stageId, type: stageIdType } = fromGlobalId(stageGlobalId)
if (stageIdType !== NODE_TYPES.CampaignStage) {
throw new UserInputError('invalid stage id')
}

return { campaign: campaignId, stage: stageId }
}
)

for (const { campaign, stage } of _campaigns) {
await this.validate({
userId,
campaignId: campaign,
campaignStageId: stage,
})
}
return _campaigns
}
}
2 changes: 1 addition & 1 deletion src/connectors/queue/publication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -506,7 +506,7 @@ export class PublicationQueue {
campaigns,
}: {
article: Article
campaigns: Array<{ campaign: string; stage: string }>
campaigns: Array<{ campaign: string; stage?: string }>
}) => {
const campaignService = new CampaignService(this.connections)
for (const { campaign, stage } of campaigns) {
Expand Down
2 changes: 1 addition & 1 deletion src/definitions/campaign.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export interface CampaignUser {
export interface CampaignArticle {
id: string
campaignId: string
campaignStageId: string | null
campaignStageId?: string | null
articleId: string
createdAt: Date
featured: boolean
Expand Down
2 changes: 2 additions & 0 deletions src/definitions/notification.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ type NoticeEntityType =
| 'tag'
| 'article'
| 'circle'
| 'campaign'

type NotificationType =
| BaseNoticeType
Expand Down Expand Up @@ -373,6 +374,7 @@ interface NoticeWriteChallengeAppliedParams extends NotificationRequiredParams {
event:
| OFFICIAL_NOTICE_EXTEND_TYPE.write_challenge_applied
| OFFICIAL_NOTICE_EXTEND_TYPE.write_challenge_applied_late_bird
entities: [NotificationEntity<'target', 'campaign'>]
recipientId: string
data: { link: string }
}
Expand Down
2 changes: 1 addition & 1 deletion src/definitions/schema.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -436,7 +436,7 @@ export type GQLArticleCampaign = {

export type GQLArticleCampaignInput = {
campaign: Scalars['ID']['input']
stage: Scalars['ID']['input']
stage?: InputMaybe<Scalars['ID']['input']>
}

export type GQLArticleConnection = GQLConnection & {
Expand Down
45 changes: 5 additions & 40 deletions src/mutations/article/editArticle.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,4 @@
import type {
Article,
Draft,
Circle,
GQLMutationResolvers,
DataSources,
} from 'definitions'
import type { Article, Draft, Circle, GQLMutationResolvers } from 'definitions'

import { invalidateFQC } from '@matters/apollo-response-cache'
import { stripHtml } from '@matters/ipns-site-generator'
Expand Down Expand Up @@ -330,9 +324,10 @@ const resolver: GQLMutationResolvers['editArticle'] = async (
* campaigns
*/
if (campaigns !== undefined) {
const _campaigns = await validateCampaigns(campaigns ?? [], viewer.id, {
campaignService,
})
const _campaigns = await campaignService.validateCampaigns(
campaigns ?? [],
viewer.id
)
const mutated = await campaignService.updateArticleCampaigns(
article,
_campaigns.map(({ campaign, stage }) => ({
Expand Down Expand Up @@ -423,34 +418,4 @@ const resolver: GQLMutationResolvers['editArticle'] = async (
return node
}

const validateCampaigns = async (
campaigns: Array<{ campaign: string; stage: string }>,
userId: string,
{ campaignService }: Pick<DataSources, 'campaignService'>
) => {
const _campaigns = campaigns.map(
({ campaign: campaignGlobalId, stage: stageGlobalId }) => {
const { id: campaignId, type: campaignIdType } =
fromGlobalId(campaignGlobalId)
if (campaignIdType !== NODE_TYPES.Campaign) {
throw new UserInputError('invalid campaign id')
}
const { id: stageId, type: stageIdType } = fromGlobalId(stageGlobalId)
if (stageIdType !== NODE_TYPES.CampaignStage) {
throw new UserInputError('invalid stage id')
}

return { campaign: campaignId, stage: stageId }
}
)
for (const { campaign, stage } of _campaigns) {
await campaignService.validate({
userId,
campaignId: campaign,
campaignStageId: stage,
})
}
return _campaigns
}

export default resolver
32 changes: 1 addition & 31 deletions src/mutations/draft/putDraft.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ const resolver: GQLMutationResolvers['putDraft'] = async (
campaigns:
campaigns &&
JSON.stringify(
await validateCampaigns(campaigns, viewer.id, { campaignService })
await campaignService.validateCampaigns(campaigns, viewer.id)
),
},
isUndefined // to drop only undefined
Expand Down Expand Up @@ -380,34 +380,4 @@ const validateConnections = async ({
)
}

const validateCampaigns = async (
campaigns: Array<{ campaign: string; stage: string }>,
userId: string,
{ campaignService }: Pick<DataSources, 'campaignService'>
) => {
const _campaigns = campaigns.map(
({ campaign: campaignGlobalId, stage: stageGlobalId }) => {
const { id: campaignId, type: campaignIdType } =
fromGlobalId(campaignGlobalId)
if (campaignIdType !== NODE_TYPES.Campaign) {
throw new UserInputError('invalid campaign id')
}
const { id: stageId, type: stageIdType } = fromGlobalId(stageGlobalId)
if (stageIdType !== NODE_TYPES.CampaignStage) {
throw new UserInputError('invalid stage id')
}

return { campaign: campaignId, stage: stageId }
}
)
for (const { campaign, stage } of _campaigns) {
await campaignService.validate({
userId,
campaignId: campaign,
campaignStageId: stage,
})
}
return _campaigns
}

export default resolver
6 changes: 4 additions & 2 deletions src/queries/draft/campaigns.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ const resolver: GQLDraftResolvers['campaigns'] = (
}
return Promise.all(
campaigns.map(
async ({ campaign, stage }: { campaign: string; stage: string }) => ({
async ({ campaign, stage }: { campaign: string; stage?: string }) => ({
campaign: await atomService.campaignIdLoader.load(campaign),
stage: await atomService.campaignStageIdLoader.load(stage),
stage: stage
? await atomService.campaignStageIdLoader.load(stage)
: null,
})
)
)
Expand Down
9 changes: 8 additions & 1 deletion src/types/__test__/1/draft.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -567,7 +567,7 @@ describe('put draft', () => {
type: NODE_TYPES.CampaignStage,
id: stages[0].id,
})
const { campaigns } = await putDraft(
const { id: draftId, campaigns } = await putDraft(
{
draft: {
title: Math.random().toString(),
Expand All @@ -588,5 +588,12 @@ describe('put draft', () => {
)
expect(campaigns[0].campaign.id).toBe(campaignGlobalId)
expect(campaigns[0].stage.id).toBe(stageGlobalId)

// remove stage
const { campaigns: campaigns2 } = await putDraft(
{ draft: { id: draftId, campaigns: [{ campaign: campaignGlobalId }] } },
connections
)
expect(campaigns2[0].stage).toBeNull()
})
})
6 changes: 3 additions & 3 deletions src/types/article.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export default /* GraphQL */ `
# Article #
##############
"Publish an article onto IPFS."
publishArticle(input: PublishArticleInput!): Draft! @auth(mode: "${AUTH_MODE.oauth}", group: "${SCOPE_GROUP.level2}") @purgeCache(type: "${NODE_TYPES.Draft}") @rateLimit(limit:${PUBLISH_ARTICLE_RATE_LIMIT}, period:720)
publishArticle(input: PublishArticleInput!): Draft! @auth(mode: "${AUTH_MODE.oauth}", group: "${SCOPE_GROUP.level2}") @purgeCache(type: "${NODE_TYPES.Draft}") @rateLimit(limit: ${PUBLISH_ARTICLE_RATE_LIMIT}, period: 720)

"Edit an article."
editArticle(input: EditArticleInput!): Article! @auth(mode: "${AUTH_MODE.oauth}", group: "${SCOPE_GROUP.level3}") @purgeCache(type: "${NODE_TYPES.Article}")
Expand All @@ -23,7 +23,7 @@ export default /* GraphQL */ `
toggleBookmarkArticle(input: ToggleItemInput!): Article! @auth(mode: "${AUTH_MODE.oauth}", group: "${SCOPE_GROUP.level1}") @purgeCache(type: "${NODE_TYPES.Article}")

"Appreciate an article."
appreciateArticle(input: AppreciateArticleInput!): Article! @auth(mode: "${AUTH_MODE.oauth}", group: "${SCOPE_GROUP.level3}") @purgeCache(type: "${NODE_TYPES.Article}") @rateLimit(limit:5, period:60)
appreciateArticle(input: AppreciateArticleInput!): Article! @auth(mode: "${AUTH_MODE.oauth}", group: "${SCOPE_GROUP.level3}") @purgeCache(type: "${NODE_TYPES.Article}") @rateLimit(limit: 5, period: 60)

"Read an article."
readArticle(input: ReadArticleInput!): Article!
Expand Down Expand Up @@ -418,7 +418,7 @@ export default /* GraphQL */ `

input ArticleCampaignInput {
campaign: ID!
stage: ID!
stage: ID
}

input AppreciateArticleInput {
Expand Down
4 changes: 2 additions & 2 deletions src/types/campaign.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { AUTH_MODE, NODE_TYPES, CACHE_TTL } from 'common/enums'
export default /* GraphQL */ `
extend type Query {
campaign(input: CampaignInput!): Campaign @logCache(type: "${NODE_TYPES.Campaign}")
campaigns(input:CampaignsInput!): CampaignConnection!
campaigns(input: CampaignsInput!): CampaignConnection!
}

extend type Mutation {
Expand Down Expand Up @@ -100,7 +100,7 @@ export default /* GraphQL */ `
announcements: [Article!]!

applicationPeriod: DatetimeRange
writingPeriod:DatetimeRange
writingPeriod: DatetimeRange
stages: [CampaignStage!]!

state: CampaignState!
Expand Down
2 changes: 1 addition & 1 deletion src/types/comment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const PUT_COMMENT_RATE_LIMIT = isProd ? 6 : 1000
export default /* GraphQL */ `
extend type Mutation {
"Publish or update a comment."
putComment(input: PutCommentInput!): Comment! @auth(mode: "${AUTH_MODE.oauth}", group: "${SCOPE_GROUP.level2}") @purgeCache(type: "${NODE_TYPES.Comment}") @rateLimit(limit:${PUT_COMMENT_RATE_LIMIT}, period:60)
putComment(input: PutCommentInput!): Comment! @auth(mode: "${AUTH_MODE.oauth}", group: "${SCOPE_GROUP.level2}") @purgeCache(type: "${NODE_TYPES.Comment}") @rateLimit(limit: ${PUT_COMMENT_RATE_LIMIT}, period: 60)

"Remove a comment."
deleteComment(input: DeleteCommentInput!): Comment! @auth(mode: "${AUTH_MODE.oauth}", group: "${SCOPE_GROUP.level2}") @purgeCache(type: "${NODE_TYPES.Comment}")
Expand Down
2 changes: 1 addition & 1 deletion src/types/moment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export default /* GraphQL */ `
moment(input: MomentInput!): Moment
}
extend type Mutation {
putMoment(input: PutMomentInput!): Moment! @auth(mode: "${AUTH_MODE.oauth}") @rateLimit(limit:${POST_MOMENT_RATE_LIMIT}, period:300) @logCache(type: "${NODE_TYPES.Moment}")
putMoment(input: PutMomentInput!): Moment! @auth(mode: "${AUTH_MODE.oauth}") @rateLimit(limit: ${POST_MOMENT_RATE_LIMIT}, period: 300) @logCache(type: "${NODE_TYPES.Moment}")
deleteMoment(input: DeleteMomentInput!): Moment! @auth(mode: "${AUTH_MODE.oauth}") @purgeCache(type: "${NODE_TYPES.Moment}")

likeMoment(input: LikeMomentInput!): Moment! @auth(mode: "${AUTH_MODE.oauth}") @purgeCache(type: "${NODE_TYPES.Moment}")
Expand Down
12 changes: 6 additions & 6 deletions src/types/system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,17 @@ export default /* GraphQL */ `

extend type Mutation {
"Upload a single file."
singleFileUpload(input: SingleFileUploadInput!): Asset! @auth(mode: "${AUTH_MODE.oauth}", group: "${SCOPE_GROUP.level3}") @rateLimit(limit:${UPLOAD_RATE_LIMIT}, period:720)
directImageUpload(input: DirectImageUploadInput!): Asset! @auth(mode: "${AUTH_MODE.oauth}", group: "${SCOPE_GROUP.level3}") @rateLimit(limit:${UPLOAD_RATE_LIMIT}, period:720)
singleFileUpload(input: SingleFileUploadInput!): Asset! @auth(mode: "${AUTH_MODE.oauth}", group: "${SCOPE_GROUP.level3}") @rateLimit(limit: ${UPLOAD_RATE_LIMIT}, period: 720)
directImageUpload(input: DirectImageUploadInput!): Asset! @auth(mode: "${AUTH_MODE.oauth}", group: "${SCOPE_GROUP.level3}") @rateLimit(limit: ${UPLOAD_RATE_LIMIT}, period: 720)

"Add specific user behavior record."
logRecord(input: LogRecordInput!): Boolean

"Add blocked search keyword to blocked_search_word db"
addBlockedSearchKeyword(input:KeywordInput!): BlockedSearchKeyword! @auth(mode: "${AUTH_MODE.admin}")
addBlockedSearchKeyword(input: KeywordInput!): BlockedSearchKeyword! @auth(mode: "${AUTH_MODE.admin}")

"Delete blocked search keywords from search_history db"
deleteBlockedSearchKeywords(input:KeywordsInput!): Boolean @auth(mode: "${AUTH_MODE.admin}")
deleteBlockedSearchKeywords(input: KeywordsInput!): Boolean @auth(mode: "${AUTH_MODE.admin}")

"Submit inappropriate content report"
submitReport(input: SubmitReportInput!): Report! @auth(mode: "${AUTH_MODE.oauth}")
Expand All @@ -48,7 +48,7 @@ export default /* GraphQL */ `
putAnnouncement(input: PutAnnouncementInput!): Announcement! @auth(mode: "${AUTH_MODE.admin}")
deleteAnnouncements(input: DeleteAnnouncementsInput!): Boolean! @auth(mode: "${AUTH_MODE.admin}")
putRestrictedUsers(input: PutRestrictedUsersInput!): [User!]! @complexity(value: 1, multipliers: ["input.ids"]) @auth(mode: "${AUTH_MODE.admin}")
putIcymiTopic(input:PutIcymiTopicInput!): IcymiTopic @auth(mode: "${AUTH_MODE.admin}")
putIcymiTopic(input: PutIcymiTopicInput!): IcymiTopic @auth(mode: "${AUTH_MODE.admin}")
setSpamStatus(input: SetSpamStatusInput!): Article! @auth(mode: "${AUTH_MODE.admin}")
}

Expand Down Expand Up @@ -139,7 +139,7 @@ export default /* GraphQL */ `
badgedUsers(input: BadgedUsersInput!): UserConnection!
restrictedUsers(input: ConnectionArgs!): UserConnection!
reports(input: ConnectionArgs!): ReportConnection!
icymiTopics(input:ConnectionArgs!): IcymiTopicConnection!
icymiTopics(input: ConnectionArgs!): IcymiTopicConnection!
}


Expand Down
Loading
Loading