diff --git a/package.json b/package.json index ab60cac8f..ef6c78f7a 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "@graphql-tools/utils": "^10.0.0", "@keyv/redis": "^2.6.1", "@matters/apollo-response-cache": "^2.0.0-alpha.0", - "@matters/ipns-site-generator": "^0.1.6", + "@matters/ipns-site-generator": "^0.1.7", "@matters/matters-editor": "^0.3.0-alpha.4", "@matters/passport-likecoin": "^1.0.0", "@matters/slugify": "^0.7.3", diff --git a/schema.graphql b/schema.graphql index feba11be2..ac2cb0bef 100644 --- a/schema.graphql +++ b/schema.graphql @@ -54,25 +54,9 @@ type Mutation { """Follow or unfollow tag.""" toggleFollowTag(input: ToggleItemInput!): Tag! - - """Create or update tag.""" - putTag(input: PutTagInput!): Tag! - - """Update member, permission and othters of a tag.""" - updateTagSetting(input: UpdateTagSettingInput!): Tag! - - """Add one tag to articles.""" - addArticlesTags(input: AddArticlesTagsInput!): Tag! - - """Update articles' tag.""" - updateArticlesTags(input: UpdateArticlesTagsInput!): Tag! - - """Delete one tag from articles""" - deleteArticlesTags(input: DeleteArticlesTagsInput!): Tag! toggleArticleRecommend(input: ToggleRecommendInput!): Article! updateArticleState(input: UpdateArticleStateInput!): Article! updateArticleSensitive(input: UpdateArticleSensitiveInput!): Article! - toggleTagRecommend(input: ToggleRecommendInput!): Tag! deleteTags(input: DeleteTagsInput!): Boolean renameTag(input: RenameTagInput!): Tag! mergeTags(input: MergeTagsInput!): Tag! @@ -168,9 +152,6 @@ type Mutation { """Reset user or payment password.""" resetPassword(input: ResetPasswordInput!): Boolean - """Change user email.""" - changeEmail(input: ChangeEmailInput!): User! @deprecated(reason: "use 'setEmail' instead") - """Set user email.""" setEmail(input: SetEmailInput!): User! @@ -180,11 +161,7 @@ type Mutation { """Set user currency preference.""" setCurrency(input: SetCurrencyInput!): User! - """Register user, can only be used on matters.{town,news} website.""" - userRegister(input: UserRegisterInput!): AuthResult! @deprecated(reason: "use 'emailLogin' instead") - """Login user.""" - userLogin(input: UserLoginInput!): AuthResult! @deprecated(reason: "use 'emailLogin' instead") emailLogin(input: EmailLoginInput!): AuthResult! """Get signing message.""" @@ -208,15 +185,9 @@ type Mutation { """Remove a social login from current user.""" removeSocialLogin(input: RemoveSocialLoginInput!): User! - """Reset crypto wallet.""" - resetWallet(input: ResetWalletInput!): User! @deprecated(reason: "use 'removeWalletLogin' instead") - """Logout user.""" userLogout: Boolean! - """Generate or claim a Liker ID through LikeCoin""" - generateLikerId: User! @deprecated(reason: "No longer in use") - """Reset Liker ID""" resetLikerId(input: ResetLikerIdInput!): User! @@ -413,7 +384,6 @@ type Article implements Node & PinnableWork { """ This value determines if this article is an author selected article or not. """ - sticky: Boolean! @deprecated(reason: "Use pinned instead") pinned: Boolean! """Translation of article title and content.""" @@ -431,15 +401,6 @@ type Article implements Node & PinnableWork { """Cumulative reading time in seconds""" readTime: Float! - """Drafts linked to this article.""" - drafts: [Draft!] @deprecated(reason: "Use Article.newestUnpublishedDraft or Article.newestPublishedDraft instead") - - """Newest unpublished draft linked to this article.""" - newestUnpublishedDraft: Draft - - """Newest published draft linked to this article.""" - newestPublishedDraft: Draft! - """Revision Count""" revisionCount: Int! @@ -551,42 +512,15 @@ type Tag implements Node { articles(input: TagArticlesInput!): ArticleConnection! articlesExcludeSpam(input: TagArticlesInput!): ArticleConnection! - """This value determines if this article is selected by this tag or not.""" - selected(input: TagSelectedInput!): Boolean! - """Time of this tag was created.""" createdAt: DateTime! - """Tag's cover link.""" - cover: String - - """Description of this tag.""" - description: String - - """Editors of this tag.""" - editors(input: TagEditorsInput): [User!] - - """Creator of this tag.""" - creator: User - - """Owner of this tag.""" - owner: User - """This value determines if current viewer is following or not.""" isFollower: Boolean - """Followers of this tag.""" - followers(input: ConnectionArgs!): UserConnection! - - """Participants of this tag.""" - participants(input: ConnectionArgs!): UserConnection! - """Tags recommended based on relations to current tag.""" recommended(input: ConnectionArgs!): TagConnection! - """This value determines if it is official.""" - isOfficial: Boolean - """Counts of this tag.""" numArticles: Int! numAuthors: Int! @@ -639,7 +573,6 @@ type ArticleTranslation { type TagOSS { boost: Float! score: Float! - selected: Boolean! } type ArticleConnection implements Connection { @@ -695,9 +628,6 @@ input PublishArticleInput { input EditArticleInput { id: ID! state: ArticleState - - """deprecated, use pinned instead""" - sticky: Boolean pinned: Boolean title: String summary: String @@ -772,36 +702,6 @@ input MergeTagsInput { content: String! } -input PutTagInput { - id: ID - content: String - cover: ID - description: String -} - -input UpdateTagSettingInput { - id: ID! - type: UpdateTagSettingType! - editors: [ID!] -} - -input AddArticlesTagsInput { - id: ID! - articles: [ID!] - selected: Boolean -} - -input UpdateArticlesTagsInput { - id: ID! - articles: [ID!] - isSelected: Boolean! -} - -input DeleteArticlesTagsInput { - id: ID! - articles: [ID!] -} - enum TagArticlesSortBy { byHottestDesc byCreatedAtDesc @@ -815,16 +715,6 @@ input TagArticlesInput { sortBy: TagArticlesSortBy = byCreatedAtDesc } -input TagSelectedInput { - id: ID - mediaHash: String -} - -input TagEditorsInput { - excludeAdmin: Boolean - excludeOwner: Boolean -} - input TransactionsReceivedByArgs { after: String first: Int @@ -874,14 +764,6 @@ enum RecommendTypes { search } -enum UpdateTagSettingType { - adopt - leave - add_editor - remove_editor - leave_editor -} - input CampaignInput { shortHash: String! } @@ -2580,9 +2462,6 @@ type Recommendation { """Activities based on user's following, sort by creation time.""" following(input: RecommendationFollowingInput!): FollowingActivityConnection! - """Articles recommended based on recently read article tags.""" - readTagsArticles(input: ConnectionArgs!): ArticleConnection! @deprecated(reason: "Merged into following") - """Global articles sort by publish time.""" newest(input: ConnectionArgs!): ArticleConnection! newestExcludeSpam(input: ConnectionArgs!): ArticleConnection! @@ -2802,9 +2681,6 @@ type Liker { """Total LIKE left in wallet.""" total: Float! - - """Rate of LikeCoin/USD""" - rateUSD: Float @deprecated(reason: "No longer in use") } type UserOSS { @@ -3123,11 +2999,6 @@ input UserRegisterInput { referralCode: String } -input UserLoginInput { - email: String! - password: String! -} - input GenerateSigningMessageInput { address: String! purpose: SigningMessagePurpose @@ -3145,12 +3016,6 @@ input WalletLoginInput { """nonce from generateSigningMessage""" nonce: String! - """required for wallet register""" - email: String @deprecated(reason: "No longer in use") - - """email verification code, required for wallet register""" - codeId: ID @deprecated(reason: "No longer in use") - """used in register""" language: UserLanguage referralCode: String @@ -3171,7 +3036,6 @@ input UpdateNotificationSettingInput { input UpdateUserInfoInput { displayName: String - userName: String @deprecated(reason: "use 'setUserName' instead") avatar: ID description: String language: UserLanguage @@ -3265,9 +3129,6 @@ enum VerificationCodeType { register email_verify email_otp - email_reset @deprecated(reason: "No longer in use") - email_reset_confirm @deprecated(reason: "No longer in use") - password_reset @deprecated(reason: "No longer in use") payment_password_reset } diff --git a/src/connectors/__test__/tagService.test.ts b/src/connectors/__test__/tagService.test.ts index 5932a9a65..0fecf2d51 100644 --- a/src/connectors/__test__/tagService.test.ts +++ b/src/connectors/__test__/tagService.test.ts @@ -123,23 +123,10 @@ describe('findArticleIds', () => { }) }) -test('findArticleCovers', async () => { - const covers = await tagService.findArticleCovers({ id: '2' }) - expect(covers).toBeDefined() - - const cached = await tagService.findArticleCovers({ id: '2' }) - expect(cached).toEqual(covers) -}) - test('create', async () => { const content = 'foo' const tag = await tagService.create( - { - content, - creator: '0', - editors: [], - owner: '0', - }, + { content, creator: '0' }, { columns: ['id', 'content'], } diff --git a/src/connectors/likecoin/index.ts b/src/connectors/likecoin/index.ts index 0f6e7f644..271fc0462 100644 --- a/src/connectors/likecoin/index.ts +++ b/src/connectors/likecoin/index.ts @@ -53,19 +53,6 @@ const ERROR_CODES = { INSUFFICIENT_PERMISSION: 'INSUFFICIENT_PERMISSION', } -type LikeCoinLocale = - | 'en' - | 'zh' - | 'cn' - | 'de' - | 'es' - | 'fr' - | 'it' - | 'ja' - | 'ko' - | 'pt' - | 'ru' - type RequestProps = { endpoint: string headers?: { [key: string]: any } @@ -235,78 +222,6 @@ export class LikeCoin { return data.access_token } - /** - * Register - */ - public check = async ({ user, email }: { user: string; email?: string }) => { - try { - const res = await this.request({ - endpoint: ENDPOINTS.check, - method: 'POST', - data: { - user, - email, - }, - }) - const data = _.get(res, 'data') - - if (data === 'OK') { - return user - } else { - throw res - } - } catch (e) { - const data = _.get(e, 'response.data') - const alternative = _.get(data, 'alternative') - - if (alternative) { - return alternative - } - - throw e - } - } - - public register = async ({ - user, - token, - displayName, - email, - locale = 'zh', - isEmailEnabled, - ip, - }: { - user: string - token: string - ip?: string - displayName?: string - email?: string - locale?: LikeCoinLocale - isEmailEnabled?: boolean - }) => { - const res = await this.request({ - endpoint: ENDPOINTS.register, - withClientCredential: true, - method: 'POST', - data: { - user, - token, - displayName, - email, - locale, - isEmailEnabled, - }, - ip, - }) - const data = _.get(res, 'data') - - if (data.accessToken && data.refreshToken) { - return data - } else { - throw res - } - } - /** * Claim, Transfer or Bind */ @@ -356,19 +271,6 @@ export class LikeCoin { return data.cosmosLIKE || data.walletLIKE } - public rate = async (currency: 'usd' | 'twd' = 'usd') => { - const res = await this.request({ - endpoint: ENDPOINTS.rate, - method: 'GET', - params: { - currency, - }, - }) - const price = _.get(res, 'data.price') - - return price - } - /** * Check if user is a civic liker */ diff --git a/src/connectors/queue/publication.ts b/src/connectors/queue/publication.ts index fd8759e61..68f328142 100644 --- a/src/connectors/queue/publication.ts +++ b/src/connectors/queue/publication.ts @@ -448,22 +448,12 @@ export class PublicationQueue { let tags = articleVersion.tags as string[] if (tags && tags.length > 0) { - // get tag editor - const tagEditors = environment.mattyId - ? [environment.mattyId, article.authorId] - : [article.authorId] - // create tag records, return tag record if already exists const dbTags = ( (await Promise.all( tags.filter(Boolean).map((content: string) => tagService.create( - { - content, - creator: article.authorId, - editors: tagEditors, - owner: article.authorId, - }, + { content, creator: article.authorId }, { columns: ['id', 'content'], skipCreate: normalizeTagInput(content) !== content, diff --git a/src/connectors/tagService.ts b/src/connectors/tagService.ts index 5e6a456b3..f2c74b7f1 100644 --- a/src/connectors/tagService.ts +++ b/src/connectors/tagService.ts @@ -6,11 +6,8 @@ import { difference } from 'lodash' import { ARTICLE_STATE, MAX_TAGS_PER_ARTICLE_LIMIT, - DEFAULT_TAKE_PER_PAGE, TAG_ACTION, MATERIALIZED_VIEW, - CACHE_PREFIX, - CACHE_TTL, } from 'common/enums' import { environment } from 'common/environment' import { TooManyTagsForArticleError, ForbiddenError } from 'common/errors' @@ -20,7 +17,7 @@ import { normalizeTagInput, excludeSpam as excludeSpamModifier, } from 'common/utils' -import { BaseService, CacheService, SystemService } from 'connectors' +import { BaseService, SystemService } from 'connectors' const logger = getLogger('service-tag') @@ -100,54 +97,6 @@ export class TagService extends BaseService { // .join(this.table, 'tag.id', 'article_tag.tag_id') .whereIn('article_id', articleIds) - /** - * Find tags by a given creator id (user). - */ - public findByCreator = async (userId: string) => { - const query = this.knex - .select() - .from(this.table) - .where({ creator: userId }) - .orderBy('id', 'desc') - - return query - } - - /** - * Find tags by a given editor id (user). - */ - public findByEditor = async (userId: string) => { - const query = this.knex - .select() - .from(this.table) - .where(this.knex.raw(`editors @> ARRAY[?]`, [userId])) - .orderBy('id', 'desc') - - return query - } - - /** - * Find tags by a given owner id (user). - */ - public findByOwner = async (userId: string) => - this.knex - .select() - .from(this.table) - .where({ owner: userId }) - .orderBy('id', 'desc') - - /** - * Find tags by a given maintainer id (user). - * - */ - public findByMaintainer = async (userId: string) => - this.knex - .select() - .from(this.table) - .where({ owner: userId }) - .orWhere(this.knex.raw(`editors @> ARRAY[?]`, [userId])) - .orderBy('id', 'desc') - public findByAuthorUsage = async ({ userId, skip, @@ -227,21 +176,7 @@ export class TagService extends BaseService { * this create may return null if skipCreate */ public create = async ( - { - content, - cover, - creator, - description, - editors, - owner, - }: { - content: string - cover?: string | null - creator: string - description?: string - editors: string[] - owner: string - }, + { content, creator }: { content: string; creator: string }, { // options columns = ['*'], @@ -253,7 +188,7 @@ export class TagService extends BaseService { ) => { const tag = await this.baseFindOrCreate({ where: { content }, - data: { content, cover, creator, description, editors, owner }, + data: { content, creator }, table: this.table, columns, modifier: (builder: Knex.QueryBuilder) => { @@ -270,98 +205,6 @@ export class TagService extends BaseService { return tag } - /** - * Count of a tag's participants. - * - */ - public countParticipants = async ({ - id, - exclude, - }: { - id: string - exclude?: string[] - }) => { - const subquery = this.knex.raw( - `( - SELECT - at.*, article.author_id - FROM - article_tag AS at - INNER JOIN - article ON article.id = at.article_id - WHERE - at.tag_id = ? - ) AS base`, - [id] - ) - - const result = await this.knex - .from(function (this: Knex.QueryBuilder) { - this.select('author_id') - .from(subquery) - .groupBy('author_id') - .as('source') - - if (exclude) { - this.whereNotIn('author_id', exclude) - } - }) - .count() - .first() - - return parseInt(result ? (result.count as string) : '0', 10) - } - - /** - * Find a tag's participants. - * - */ - public findParticipants = async ({ - id, - skip, - take, - exclude, - }: { - id: string - skip?: number - take?: number - exclude?: string[] - }) => { - const subquery = this.knex.raw( - `( - SELECT - at.*, article.author_id - FROM - article_tag AS at - INNER JOIN - article ON article.id = at.article_id - WHERE - at.tag_id = ? - ORDER BY - at.created_at - ) AS base`, - [id] - ) - - const query = this.knex - .select('author_id') - .from(subquery) - .groupBy('author_id') - - if (exclude) { - query.whereNotIn('author_id', exclude) - } - - if (skip !== undefined && Number.isFinite(skip)) { - query.offset(skip) - } - if (take !== undefined && Number.isFinite(take)) { - query.limit(take) - } - - return query - } - /********************************* * * * Follow * @@ -404,35 +247,6 @@ export class TagService extends BaseService { }) .del() - /** - * Find followers of a tag using id as pagination index. - * - */ - public findFollowers = async ({ - targetId, - skip, - take, - }: { - targetId: string - skip?: string - take?: number - }) => { - const query = this.knex - .select() - .from('action_tag') - .where({ targetId, action: TAG_ACTION.follow }) - .orderBy('id', 'desc') - - if (skip) { - query.andWhere('id', '<', skip) - } - if (take || take === 0) { - query.limit(take) - } - - return query - } - public isActionEnabled = async ({ userId, action, @@ -475,14 +289,6 @@ export class TagService extends BaseService { : this.knex.from('action_tag').where(data).del() } - public countFollowers = async (targetId: string) => { - const result = await this.knex('action_tag') - .where({ targetId, action: TAG_ACTION.follow }) - .count() - .first() - return parseInt(result ? (result.count as string) : '0', 10) - } - /********************************* * * * Search * @@ -787,16 +593,6 @@ export class TagService extends BaseService { return parseInt(result ? (result.count as string) : '0', 10) } - public addTagRecommendation = (tagId: string) => - this.baseFindOrCreate({ - where: { tagId }, - data: { tagId }, - table: 'matters_choice_tag', - }) - - public removeTagRecommendation = (tagId: string) => - this.knex('matters_choice_tag').where({ tagId }).del() - /********************************* * * * Article * @@ -804,28 +600,22 @@ export class TagService extends BaseService { *********************************/ public createArticleTags = async ({ articleIds, - creator, tagIds, - selected, + creator, }: { articleIds: string[] - creator: string tagIds: string[] - selected?: boolean + creator: string }) => { articleIds = Array.from(new Set(articleIds)) tagIds = Array.from(new Set(tagIds)) const items = articleIds .map((articleId) => - tagIds.map((tagId) => ({ - articleId, - creator, - tagId, - ...(selected === true ? { selected } : {}), - })) + tagIds.map((tagId) => ({ articleId, creator, tagId })) ) .flat(1) + return this.baseBatchCreate(items, 'article_tag') } @@ -1038,18 +828,6 @@ export class TagService extends BaseService { return result.map(({ articleId }: { articleId: string }) => articleId) } - public deleteArticleTagsByArticleIds = async ({ - articleIds, - tagId, - }: { - articleIds: string[] - tagId: string - }) => - this.knex('article_tag') - .whereIn('article_id', articleIds) - .andWhere({ tagId }) - .del() - public deleteArticleTagsByTagIds = async ({ articleId, tagIds, @@ -1062,53 +840,6 @@ export class TagService extends BaseService { .andWhere({ articleId }) .del() - public isArticleSelected = async ({ - articleId, - tagId, - }: { - articleId: string - tagId: string - }) => { - const result = await this.knex('article_tag').where({ - articleId, - tagId, - selected: true, - }) - return result.length > 0 - } - - /** - * Find article covers by tag id. - */ - public findArticleCovers = async ({ - id, - }: { - id: string - }): Promise> => { - const cache = new CacheService(CACHE_PREFIX.TAG_COVERS, this.redis) - return cache.getObject({ - keys: { id }, - expire: CACHE_TTL.MEDIUM, - getter: async () => - this.knexRO - .select('article_version_newest.cover') - .from('article_tag') - .join( - 'article_version_newest', - 'article_tag.article_id', - 'article_version_newest.article_id' - ) - .join('article', 'article_tag.article_id', 'article.id') - .whereNotNull('article_version_newest.cover') - .andWhere({ - tagId: id, - state: ARTICLE_STATE.active, - }) - .limit(DEFAULT_TAKE_PER_PAGE) - .orderBy('article_tag.id', 'asc'), - }) - } - /********************************* * * * OSS * @@ -1126,21 +857,21 @@ export class TagService extends BaseService { tagIds, content, creator, - editors, - owner, }: { tagIds: string[] content: string creator: string - editors: string[] - owner: string }) => { // create new tag - const newTag = await this.create({ content, creator, editors, owner }) + const newTag = await this.create({ content, creator }) // move article tags to new tag const articleIds = await this.findArticleIdsByTagIds(tagIds) - await this.createArticleTags({ articleIds, creator, tagIds: [newTag.id] }) + await this.createArticleTags({ + articleIds, + tagIds: [newTag.id], + creator, + }) // delete article tags await this.knex('article_tag').whereIn('tag_id', tagIds).del() @@ -1225,19 +956,11 @@ export class TagService extends BaseService { } // create tag records - const tagEditors = environment.mattyId - ? [environment.mattyId, article.authorId] - : [article.authorId] const dbTags = ( await Promise.all( tags.filter(Boolean).map(async (content: string) => this.create( - { - content, - creator: article.authorId, - editors: tagEditors, - owner: article.authorId, - }, + { content, creator: article.authorId }, { columns: ['id', 'content'], skipCreate: normalizeTagInput(content) !== content, // || content.length > MAX_TAG_CONTENT_LENGTH, @@ -1260,8 +983,8 @@ export class TagService extends BaseService { // add await this.createArticleTags({ articleIds: [article.id], - creator: article.authorId, tagIds: addIds, + creator: article.authorId, }) // delete unwanted diff --git a/src/connectors/userService.ts b/src/connectors/userService.ts index 686cb5030..848183368 100644 --- a/src/connectors/userService.ts +++ b/src/connectors/userService.ts @@ -685,15 +685,15 @@ export class UserService extends BaseService { await trx('push_device').where({ userId: id }).del() // remove tag owner and editors - await trx.raw(` - UPDATE - tag - SET - owner = NULL, - editors = array_remove(editors, owner::text) - WHERE - owner = ${id} - `) + // await trx.raw(` + // UPDATE + // tag + // SET + // owner = NULL, + // editors = array_remove(editors, owner::text) + // WHERE + // owner = ${id} + // `) return user }) @@ -1894,76 +1894,6 @@ export class UserService extends BaseService { return user } - public updateLiker = ({ - likerId, - ...data - }: { - [key: string]: any - likerId: string - }) => - this.knex - .select() - .from('user_oauth_likecoin') - .where({ likerId }) - .update(data) - - // register a new LikerId by a given userName - public registerLikerId = async ({ - userId, - userName, - ip, - }: { - userId: string - userName: string - ip?: string - }) => { - // check - const likerId = await this.likecoin.check({ user: userName }) - - // register - const oAuthService = new OAuthService(this.connections) - const tokens = await oAuthService.generateTokenForLikeCoin({ userId }) - const { accessToken, refreshToken, scope } = await this.likecoin.register({ - user: likerId, - token: tokens.accessToken, - ip, - }) - - // save to db - return this.saveLiker({ - userId, - likerId, - accountType: 'general', - accessToken, - refreshToken, - scope, - }) - } - - // Promote a platform temp LikerID - public claimLikerId = async ({ - userId, - liker, - ip, - }: { - userId: string - liker: UserOAuthLikeCoin - ip?: string - }) => { - const oAuthService = new OAuthService(this.connections) - const tokens = await oAuthService.generateTokenForLikeCoin({ userId }) - - await this.likecoin.edit({ - action: 'claim', - payload: { user: liker.likerId, platformToken: tokens.accessToken }, - ip, - }) - - return this.knex('user_oauth_likecoin') - .where({ likerId: liker.likerId }) - .update({ accountType: 'general' }) - } - // Transfer a platform temp LikerID's LIKE and binding to target LikerID public transferLikerId = async ({ fromLiker, diff --git a/src/definitions/schema.d.ts b/src/definitions/schema.d.ts index 528f721cf..2adb1feb1 100644 --- a/src/definitions/schema.d.ts +++ b/src/definitions/schema.d.ts @@ -73,12 +73,6 @@ export type Scalars = { Upload: { input: any; output: any } } -export type GQLAddArticlesTagsInput = { - articles?: InputMaybe> - id: Scalars['ID']['input'] - selected?: InputMaybe -} - export type GQLAddCollectionsArticlesInput = { articles: Array collections: Array @@ -220,11 +214,6 @@ export type GQLArticle = GQLNode & donationCount: Scalars['Int']['output'] /** Donations of this article, grouped by sender */ donations: GQLArticleDonationConnection - /** - * Drafts linked to this article. - * @deprecated Use Article.newestUnpublishedDraft or Article.newestPublishedDraft instead - */ - drafts?: Maybe> /** List of featured comments of this article. */ featuredComments: GQLCommentConnection /** This value determines if current viewer has appreciated or not. */ @@ -241,15 +230,12 @@ export type GQLArticle = GQLNode & license: GQLArticleLicenseType /** Media hash, composed of cid encoding, of this article. */ mediaHash: Scalars['String']['output'] - /** Newest published draft linked to this article. */ - newestPublishedDraft: GQLDraft - /** Newest unpublished draft linked to this article. */ - newestUnpublishedDraft?: Maybe oss: GQLArticleOss /** The number determines how many comments can be set as pinned comment. */ pinCommentLeft: Scalars['Int']['output'] /** The number determines how many pinned comments can be set. */ pinCommentLimit: Scalars['Int']['output'] + /** This value determines if this article is an author selected article or not. */ pinned: Scalars['Boolean']['output'] /** List of pinned comments. */ pinnedComments?: Maybe> @@ -285,11 +271,6 @@ export type GQLArticle = GQLNode & slug: Scalars['String']['output'] /** State of this article. */ state: GQLArticleState - /** - * This value determines if this article is an author selected article or not. - * @deprecated Use pinned instead - */ - sticky: Scalars['Boolean']['output'] /** This value determines if current Viewer has subscribed of not. */ subscribed: Scalars['Boolean']['output'] /** Subscribers of this article. */ @@ -1339,11 +1320,6 @@ export type GQLDeleteAnnouncementsInput = { ids?: InputMaybe> } -export type GQLDeleteArticlesTagsInput = { - articles?: InputMaybe> - id: Scalars['ID']['input'] -} - export type GQLDeleteCollectionArticlesInput = { articles: Array collection: Scalars['ID']['input'] @@ -1479,8 +1455,6 @@ export type GQLEditArticleInput = { requestForDonation?: InputMaybe sensitive?: InputMaybe state?: InputMaybe - /** deprecated, use pinned instead */ - sticky?: InputMaybe summary?: InputMaybe tags?: InputMaybe> title?: InputMaybe @@ -1734,11 +1708,6 @@ export type GQLLiker = { civicLiker: Scalars['Boolean']['output'] /** Liker ID of LikeCoin */ likerId?: Maybe - /** - * Rate of LikeCoin/USD - * @deprecated No longer in use - */ - rateUSD?: Maybe /** Total LIKE left in wallet. */ total: Scalars['Float']['output'] } @@ -1836,8 +1805,6 @@ export type GQLMonthlyDatum = { export type GQLMutation = { __typename?: 'Mutation' - /** Add one tag to articles. */ - addArticlesTags: GQLTag /** Add blocked search keyword to blocked_search_word db */ addBlockedSearchKeyword: GQLBlockedSearchKeyword /** Add articles to the begining of the collections. */ @@ -1851,11 +1818,6 @@ export type GQLMutation = { applyCampaign: GQLCampaign /** Appreciate an article. */ appreciateArticle: GQLArticle - /** - * Change user email. - * @deprecated use 'setEmail' instead - */ - changeEmail: GQLUser /** Let Traveloggers owner claims a Logbook, returns transaction hash */ claimLogbooks: GQLClaimLogbooksResult /** Clear read history for user. */ @@ -1867,8 +1829,6 @@ export type GQLMutation = { /** Create Stripe Connect account for Payout */ connectStripeAccount: GQLConnectStripeAccountResult deleteAnnouncements: Scalars['Boolean']['output'] - /** Delete one tag from articles */ - deleteArticlesTags: GQLTag /** Delete blocked search keywords from search_history db */ deleteBlockedSearchKeywords?: Maybe /** Remove articles from the collection. */ @@ -1883,12 +1843,8 @@ export type GQLMutation = { directImageUpload: GQLAsset /** Edit an article. */ editArticle: GQLArticle + /** Login user. */ emailLogin: GQLAuthResult - /** - * Generate or claim a Liker ID through LikeCoin - * @deprecated No longer in use - */ - generateLikerId: GQLUser /** Get signing message. */ generateSigningMessage: GQLSigningMessageResult /** Invite others to join circle */ @@ -1932,8 +1888,6 @@ export type GQLMutation = { putRemark?: Maybe putRestrictedUsers: Array putSkippedListItem?: Maybe> - /** Create or update tag. */ - putTag: GQLTag putWritingChallenge: GQLWritingChallenge /** Read an article. */ readArticle: GQLArticle @@ -1950,11 +1904,6 @@ export type GQLMutation = { resetLikerId: GQLUser /** Reset user or payment password. */ resetPassword?: Maybe - /** - * Reset crypto wallet. - * @deprecated use 'removeWalletLogin' instead - */ - resetWallet: GQLUser sendCampaignAnnouncement?: Maybe /** Send verification code for email. */ sendVerificationCode?: Maybe @@ -1994,7 +1943,6 @@ export type GQLMutation = { toggleSeedingUsers: Array> /** Subscribe or Unsubscribe article */ toggleSubscribeArticle: GQLArticle - toggleTagRecommend: GQLTag toggleUsersBadge: Array> toggleWritingChallengeFeaturedArticles: GQLCampaign unbindLikerId: GQLUser @@ -2008,15 +1956,11 @@ export type GQLMutation = { unvoteComment: GQLComment updateArticleSensitive: GQLArticle updateArticleState: GQLArticle - /** Update articles' tag. */ - updateArticlesTags: GQLTag updateCampaignApplicationState: GQLCampaign /** Update a comments' state. */ updateCommentsState: Array /** Update user notification settings. */ updateNotificationSetting: GQLUser - /** Update member, permission and othters of a tag. */ - updateTagSetting: GQLTag /** Update referralCode of a user, used in OSS. */ updateUserExtra: GQLUser /** Update user information. */ @@ -2025,18 +1969,8 @@ export type GQLMutation = { updateUserRole: GQLUser /** Update state of a user, used in OSS. */ updateUserState?: Maybe> - /** - * Login user. - * @deprecated use 'emailLogin' instead - */ - userLogin: GQLAuthResult /** Logout user. */ userLogout: Scalars['Boolean']['output'] - /** - * Register user, can only be used on matters.{town,news} website. - * @deprecated use 'emailLogin' instead - */ - userRegister: GQLAuthResult /** Verify user email. */ verifyEmail: GQLAuthResult /** Upvote or downvote a comment. */ @@ -2045,10 +1979,6 @@ export type GQLMutation = { walletLogin: GQLAuthResult } -export type GQLMutationAddArticlesTagsArgs = { - input: GQLAddArticlesTagsInput -} - export type GQLMutationAddBlockedSearchKeywordArgs = { input: GQLKeywordInput } @@ -2077,10 +2007,6 @@ export type GQLMutationAppreciateArticleArgs = { input: GQLAppreciateArticleInput } -export type GQLMutationChangeEmailArgs = { - input: GQLChangeEmailInput -} - export type GQLMutationClaimLogbooksArgs = { input: GQLClaimLogbooksInput } @@ -2101,10 +2027,6 @@ export type GQLMutationDeleteAnnouncementsArgs = { input: GQLDeleteAnnouncementsInput } -export type GQLMutationDeleteArticlesTagsArgs = { - input: GQLDeleteArticlesTagsInput -} - export type GQLMutationDeleteBlockedSearchKeywordsArgs = { input: GQLKeywordsInput } @@ -2241,10 +2163,6 @@ export type GQLMutationPutSkippedListItemArgs = { input: GQLPutSkippedListItemInput } -export type GQLMutationPutTagArgs = { - input: GQLPutTagInput -} - export type GQLMutationPutWritingChallengeArgs = { input: GQLPutWritingChallengeInput } @@ -2277,10 +2195,6 @@ export type GQLMutationResetPasswordArgs = { input: GQLResetPasswordInput } -export type GQLMutationResetWalletArgs = { - input: GQLResetWalletInput -} - export type GQLMutationSendCampaignAnnouncementArgs = { input: GQLSendCampaignAnnouncementInput } @@ -2365,10 +2279,6 @@ export type GQLMutationToggleSubscribeArticleArgs = { input: GQLToggleItemInput } -export type GQLMutationToggleTagRecommendArgs = { - input: GQLToggleRecommendInput -} - export type GQLMutationToggleUsersBadgeArgs = { input: GQLToggleUsersBadgeInput } @@ -2409,10 +2319,6 @@ export type GQLMutationUpdateArticleStateArgs = { input: GQLUpdateArticleStateInput } -export type GQLMutationUpdateArticlesTagsArgs = { - input: GQLUpdateArticlesTagsInput -} - export type GQLMutationUpdateCampaignApplicationStateArgs = { input: GQLUpdateCampaignApplicationStateInput } @@ -2425,10 +2331,6 @@ export type GQLMutationUpdateNotificationSettingArgs = { input: GQLUpdateNotificationSettingInput } -export type GQLMutationUpdateTagSettingArgs = { - input: GQLUpdateTagSettingInput -} - export type GQLMutationUpdateUserExtraArgs = { input: GQLUpdateUserExtraInput } @@ -2445,14 +2347,6 @@ export type GQLMutationUpdateUserStateArgs = { input: GQLUpdateUserStateInput } -export type GQLMutationUserLoginArgs = { - input: GQLUserLoginInput -} - -export type GQLMutationUserRegisterArgs = { - input: GQLUserRegisterInput -} - export type GQLMutationVerifyEmailArgs = { input: GQLVerifyEmailInput } @@ -2921,13 +2815,6 @@ export type GQLPutSkippedListItemInput = { value?: InputMaybe } -export type GQLPutTagInput = { - content?: InputMaybe - cover?: InputMaybe - description?: InputMaybe - id?: InputMaybe -} - export type GQLPutWritingChallengeInput = { announcements?: InputMaybe> applicationPeriod?: InputMaybe @@ -3079,11 +2966,6 @@ export type GQLRecommendation = { /** Global circles sort by created time. */ newestCircles: GQLCircleConnection newestExcludeSpam: GQLArticleConnection - /** - * Articles recommended based on recently read article tags. - * @deprecated Merged into following - */ - readTagsArticles: GQLArticleConnection /** Selected tag list */ selectedTags: GQLTagConnection /** Global tag list, sort by activities in recent 14 days. */ @@ -3130,10 +3012,6 @@ export type GQLRecommendationNewestExcludeSpamArgs = { input: GQLConnectionArgs } -export type GQLRecommendationReadTagsArticlesArgs = { - input: GQLConnectionArgs -} - export type GQLRecommendationSelectedTagsArgs = { input: GQLRecommendInput } @@ -3526,38 +3404,20 @@ export type GQLTag = GQLNode & { articlesExcludeSpam: GQLArticleConnection /** Content of this tag. */ content: Scalars['String']['output'] - /** Tag's cover link. */ - cover?: Maybe /** Time of this tag was created. */ createdAt: Scalars['DateTime']['output'] - /** Creator of this tag. */ - creator?: Maybe deleted: Scalars['Boolean']['output'] - /** Description of this tag. */ - description?: Maybe - /** Editors of this tag. */ - editors?: Maybe> - /** Followers of this tag. */ - followers: GQLUserConnection /** Unique id of this tag. */ id: Scalars['ID']['output'] /** This value determines if current viewer is following or not. */ isFollower?: Maybe - /** This value determines if it is official. */ - isOfficial?: Maybe /** Counts of this tag. */ numArticles: Scalars['Int']['output'] numAuthors: Scalars['Int']['output'] oss: GQLTagOss - /** Owner of this tag. */ - owner?: Maybe - /** Participants of this tag. */ - participants: GQLUserConnection /** Tags recommended based on relations to current tag. */ recommended: GQLTagConnection remark?: Maybe - /** This value determines if this article is selected by this tag or not. */ - selected: Scalars['Boolean']['output'] } /** This type contains content, count and related data of an article tag. */ @@ -3570,31 +3430,11 @@ export type GQLTagArticlesExcludeSpamArgs = { input: GQLTagArticlesInput } -/** This type contains content, count and related data of an article tag. */ -export type GQLTagEditorsArgs = { - input?: InputMaybe -} - -/** This type contains content, count and related data of an article tag. */ -export type GQLTagFollowersArgs = { - input: GQLConnectionArgs -} - -/** This type contains content, count and related data of an article tag. */ -export type GQLTagParticipantsArgs = { - input: GQLConnectionArgs -} - /** This type contains content, count and related data of an article tag. */ export type GQLTagRecommendedArgs = { input: GQLConnectionArgs } -/** This type contains content, count and related data of an article tag. */ -export type GQLTagSelectedArgs = { - input: GQLTagSelectedInput -} - export type GQLTagArticlesInput = { after?: InputMaybe first?: InputMaybe @@ -3618,21 +3458,10 @@ export type GQLTagEdge = { node: GQLTag } -export type GQLTagEditorsInput = { - excludeAdmin?: InputMaybe - excludeOwner?: InputMaybe -} - export type GQLTagOss = { __typename?: 'TagOSS' boost: Scalars['Float']['output'] score: Scalars['Float']['output'] - selected: Scalars['Boolean']['output'] -} - -export type GQLTagSelectedInput = { - id?: InputMaybe - mediaHash?: InputMaybe } export type GQLTagsInput = { @@ -3863,12 +3692,6 @@ export type GQLUpdateArticleStateInput = { state: GQLArticleState } -export type GQLUpdateArticlesTagsInput = { - articles?: InputMaybe> - id: Scalars['ID']['input'] - isSelected: Scalars['Boolean']['input'] -} - export type GQLUpdateCampaignApplicationStateInput = { campaign: Scalars['ID']['input'] state: GQLCampaignApplicationState @@ -3885,19 +3708,6 @@ export type GQLUpdateNotificationSettingInput = { type: GQLNotificationSettingType } -export type GQLUpdateTagSettingInput = { - editors?: InputMaybe> - id: Scalars['ID']['input'] - type: GQLUpdateTagSettingType -} - -export type GQLUpdateTagSettingType = - | 'add_editor' - | 'adopt' - | 'leave' - | 'leave_editor' - | 'remove_editor' - export type GQLUpdateUserExtraInput = { id: Scalars['ID']['input'] referralCode?: InputMaybe @@ -3913,8 +3723,6 @@ export type GQLUpdateUserInfoInput = { paymentPointer?: InputMaybe profileCover?: InputMaybe referralCode?: InputMaybe - /** @deprecated use 'setUserName' instead */ - userName?: InputMaybe } export type GQLUpdateUserRoleInput = { @@ -4203,11 +4011,6 @@ export type GQLUserInput = { export type GQLUserLanguage = 'en' | 'zh_hans' | 'zh_hant' -export type GQLUserLoginInput = { - email: Scalars['String']['input'] - password: Scalars['String']['input'] -} - export type GQLUserNotice = GQLNotice & { __typename?: 'UserNotice' /** List of notice actors. */ @@ -4325,10 +4128,7 @@ export type GQLUserStatus = { export type GQLVerificationCodeType = | 'email_otp' - | 'email_reset' - | 'email_reset_confirm' | 'email_verify' - | 'password_reset' | 'payment_password_reset' | 'register' @@ -4362,16 +4162,6 @@ export type GQLWalletTransactionsArgs = { } export type GQLWalletLoginInput = { - /** - * email verification code, required for wallet register - * @deprecated No longer in use - */ - codeId?: InputMaybe - /** - * required for wallet register - * @deprecated No longer in use - */ - email?: InputMaybe ethAddress: Scalars['String']['input'] /** used in register */ language?: InputMaybe @@ -4697,7 +4487,6 @@ export type GQLResolversInterfaceTypes< /** Mapping between all available schema types and the resolvers types */ export type GQLResolversTypes = ResolversObject<{ - AddArticlesTagsInput: GQLAddArticlesTagsInput AddCollectionsArticlesInput: GQLAddCollectionsArticlesInput AddCreditInput: GQLAddCreditInput AddCreditResult: ResolverTypeWrapper< @@ -4919,7 +4708,6 @@ export type GQLResolversTypes = ResolversObject<{ DatetimeRange: ResolverTypeWrapper DatetimeRangeInput: GQLDatetimeRangeInput DeleteAnnouncementsInput: GQLDeleteAnnouncementsInput - DeleteArticlesTagsInput: GQLDeleteArticlesTagsInput DeleteCollectionArticlesInput: GQLDeleteCollectionArticlesInput DeleteCollectionsInput: GQLDeleteCollectionsInput DeleteCommentInput: GQLDeleteCommentInput @@ -5106,7 +4894,6 @@ export type GQLResolversTypes = ResolversObject<{ PutRemarkInput: GQLPutRemarkInput PutRestrictedUsersInput: GQLPutRestrictedUsersInput PutSkippedListItemInput: GQLPutSkippedListItemInput - PutTagInput: GQLPutTagInput PutWritingChallengeInput: GQLPutWritingChallengeInput Query: ResolverTypeWrapper<{}> QuoteCurrency: GQLQuoteCurrency @@ -5212,9 +4999,7 @@ export type GQLResolversTypes = ResolversObject<{ TagEdge: ResolverTypeWrapper< Omit & { node: GQLResolversTypes['Tag'] } > - TagEditorsInput: GQLTagEditorsInput TagOSS: ResolverTypeWrapper - TagSelectedInput: GQLTagSelectedInput TagsInput: GQLTagsInput TagsSort: GQLTagsSort ToggleCircleMemberInput: GQLToggleCircleMemberInput @@ -5267,12 +5052,9 @@ export type GQLResolversTypes = ResolversObject<{ UnvoteCommentInput: GQLUnvoteCommentInput UpdateArticleSensitiveInput: GQLUpdateArticleSensitiveInput UpdateArticleStateInput: GQLUpdateArticleStateInput - UpdateArticlesTagsInput: GQLUpdateArticlesTagsInput UpdateCampaignApplicationStateInput: GQLUpdateCampaignApplicationStateInput UpdateCommentsStateInput: GQLUpdateCommentsStateInput UpdateNotificationSettingInput: GQLUpdateNotificationSettingInput - UpdateTagSettingInput: GQLUpdateTagSettingInput - UpdateTagSettingType: GQLUpdateTagSettingType UpdateUserExtraInput: GQLUpdateUserExtraInput UpdateUserInfoInput: GQLUpdateUserInfoInput UpdateUserRoleInput: GQLUpdateUserRoleInput @@ -5317,7 +5099,6 @@ export type GQLResolversTypes = ResolversObject<{ UserInfoFields: GQLUserInfoFields UserInput: GQLUserInput UserLanguage: GQLUserLanguage - UserLoginInput: GQLUserLoginInput UserNotice: ResolverTypeWrapper UserNoticeType: GQLUserNoticeType UserOSS: ResolverTypeWrapper @@ -5368,7 +5149,6 @@ export type GQLResolversTypes = ResolversObject<{ /** Mapping between all available schema types and the resolvers parents */ export type GQLResolversParentTypes = ResolversObject<{ - AddArticlesTagsInput: GQLAddArticlesTagsInput AddCollectionsArticlesInput: GQLAddCollectionsArticlesInput AddCreditInput: GQLAddCreditInput AddCreditResult: Omit & { @@ -5524,7 +5304,6 @@ export type GQLResolversParentTypes = ResolversObject<{ DatetimeRange: GQLDatetimeRange DatetimeRangeInput: GQLDatetimeRangeInput DeleteAnnouncementsInput: GQLDeleteAnnouncementsInput - DeleteArticlesTagsInput: GQLDeleteArticlesTagsInput DeleteCollectionArticlesInput: GQLDeleteCollectionArticlesInput DeleteCollectionsInput: GQLDeleteCollectionsInput DeleteCommentInput: GQLDeleteCommentInput @@ -5669,7 +5448,6 @@ export type GQLResolversParentTypes = ResolversObject<{ PutRemarkInput: GQLPutRemarkInput PutRestrictedUsersInput: GQLPutRestrictedUsersInput PutSkippedListItemInput: GQLPutSkippedListItemInput - PutTagInput: GQLPutTagInput PutWritingChallengeInput: GQLPutWritingChallengeInput Query: {} ReadArticleInput: GQLReadArticleInput @@ -5745,9 +5523,7 @@ export type GQLResolversParentTypes = ResolversObject<{ edges?: Maybe> } TagEdge: Omit & { node: GQLResolversParentTypes['Tag'] } - TagEditorsInput: GQLTagEditorsInput TagOSS: TagModel - TagSelectedInput: GQLTagSelectedInput TagsInput: GQLTagsInput ToggleCircleMemberInput: GQLToggleCircleMemberInput ToggleItemInput: GQLToggleItemInput @@ -5787,11 +5563,9 @@ export type GQLResolversParentTypes = ResolversObject<{ UnvoteCommentInput: GQLUnvoteCommentInput UpdateArticleSensitiveInput: GQLUpdateArticleSensitiveInput UpdateArticleStateInput: GQLUpdateArticleStateInput - UpdateArticlesTagsInput: GQLUpdateArticlesTagsInput UpdateCampaignApplicationStateInput: GQLUpdateCampaignApplicationStateInput UpdateCommentsStateInput: GQLUpdateCommentsStateInput UpdateNotificationSettingInput: GQLUpdateNotificationSettingInput - UpdateTagSettingInput: GQLUpdateTagSettingInput UpdateUserExtraInput: GQLUpdateUserExtraInput UpdateUserInfoInput: GQLUpdateUserInfoInput UpdateUserRoleInput: GQLUpdateUserRoleInput @@ -5833,7 +5607,6 @@ export type GQLResolversParentTypes = ResolversObject<{ } UserInfo: UserModel UserInput: GQLUserInput - UserLoginInput: GQLUserLoginInput UserNotice: NoticeItemModel UserOSS: UserModel UserPostMomentActivity: Omit< @@ -6155,11 +5928,6 @@ export type GQLArticleResolvers< ContextType, RequireFields > - drafts?: Resolver< - Maybe>, - ParentType, - ContextType - > featuredComments?: Resolver< GQLResolversTypes['CommentConnection'], ParentType, @@ -6189,16 +5957,6 @@ export type GQLArticleResolvers< ContextType > mediaHash?: Resolver - newestPublishedDraft?: Resolver< - GQLResolversTypes['Draft'], - ParentType, - ContextType - > - newestUnpublishedDraft?: Resolver< - Maybe, - ParentType, - ContextType - > oss?: Resolver pinCommentLeft?: Resolver pinCommentLimit?: Resolver @@ -6265,7 +6023,6 @@ export type GQLArticleResolvers< shortHash?: Resolver slug?: Resolver state?: Resolver - sticky?: Resolver subscribed?: Resolver subscribers?: Resolver< GQLResolversTypes['UserConnection'], @@ -7675,7 +7432,6 @@ export type GQLLikerResolvers< ParentType, ContextType > - rateUSD?: Resolver, ParentType, ContextType> total?: Resolver __isTypeOf?: IsTypeOfResolverFn }> @@ -7778,12 +7534,6 @@ export type GQLMutationResolvers< ContextType = Context, ParentType extends GQLResolversParentTypes['Mutation'] = GQLResolversParentTypes['Mutation'] > = ResolversObject<{ - addArticlesTags?: Resolver< - GQLResolversTypes['Tag'], - ParentType, - ContextType, - RequireFields - > addBlockedSearchKeyword?: Resolver< GQLResolversTypes['BlockedSearchKeyword'], ParentType, @@ -7826,12 +7576,6 @@ export type GQLMutationResolvers< ContextType, RequireFields > - changeEmail?: Resolver< - GQLResolversTypes['User'], - ParentType, - ContextType, - RequireFields - > claimLogbooks?: Resolver< GQLResolversTypes['ClaimLogbooksResult'], ParentType, @@ -7867,12 +7611,6 @@ export type GQLMutationResolvers< ContextType, RequireFields > - deleteArticlesTags?: Resolver< - GQLResolversTypes['Tag'], - ParentType, - ContextType, - RequireFields - > deleteBlockedSearchKeywords?: Resolver< Maybe, ParentType, @@ -7933,7 +7671,6 @@ export type GQLMutationResolvers< ContextType, RequireFields > - generateLikerId?: Resolver generateSigningMessage?: Resolver< GQLResolversTypes['SigningMessageResult'], ParentType, @@ -8083,12 +7820,6 @@ export type GQLMutationResolvers< ContextType, RequireFields > - putTag?: Resolver< - GQLResolversTypes['Tag'], - ParentType, - ContextType, - RequireFields - > putWritingChallenge?: Resolver< GQLResolversTypes['WritingChallenge'], ParentType, @@ -8142,12 +7873,6 @@ export type GQLMutationResolvers< ContextType, RequireFields > - resetWallet?: Resolver< - GQLResolversTypes['User'], - ParentType, - ContextType, - RequireFields - > sendCampaignAnnouncement?: Resolver< Maybe, ParentType, @@ -8274,12 +7999,6 @@ export type GQLMutationResolvers< ContextType, RequireFields > - toggleTagRecommend?: Resolver< - GQLResolversTypes['Tag'], - ParentType, - ContextType, - RequireFields - > toggleUsersBadge?: Resolver< Array>, ParentType, @@ -8343,12 +8062,6 @@ export type GQLMutationResolvers< ContextType, RequireFields > - updateArticlesTags?: Resolver< - GQLResolversTypes['Tag'], - ParentType, - ContextType, - RequireFields - > updateCampaignApplicationState?: Resolver< GQLResolversTypes['Campaign'], ParentType, @@ -8367,12 +8080,6 @@ export type GQLMutationResolvers< ContextType, RequireFields > - updateTagSetting?: Resolver< - GQLResolversTypes['Tag'], - ParentType, - ContextType, - RequireFields - > updateUserExtra?: Resolver< GQLResolversTypes['User'], ParentType, @@ -8397,19 +8104,7 @@ export type GQLMutationResolvers< ContextType, RequireFields > - userLogin?: Resolver< - GQLResolversTypes['AuthResult'], - ParentType, - ContextType, - RequireFields - > userLogout?: Resolver - userRegister?: Resolver< - GQLResolversTypes['AuthResult'], - ParentType, - ContextType, - RequireFields - > verifyEmail?: Resolver< GQLResolversTypes['AuthResult'], ParentType, @@ -9068,12 +8763,6 @@ export type GQLRecommendationResolvers< ContextType, RequireFields > - readTagsArticles?: Resolver< - GQLResolversTypes['ArticleConnection'], - ParentType, - ContextType, - RequireFields - > selectedTags?: Resolver< GQLResolversTypes['TagConnection'], ParentType, @@ -9312,48 +9001,17 @@ export type GQLTagResolvers< RequireFields > content?: Resolver - cover?: Resolver, ParentType, ContextType> createdAt?: Resolver - creator?: Resolver, ParentType, ContextType> deleted?: Resolver - description?: Resolver< - Maybe, - ParentType, - ContextType - > - editors?: Resolver< - Maybe>, - ParentType, - ContextType, - Partial - > - followers?: Resolver< - GQLResolversTypes['UserConnection'], - ParentType, - ContextType, - RequireFields - > id?: Resolver isFollower?: Resolver< Maybe, ParentType, ContextType > - isOfficial?: Resolver< - Maybe, - ParentType, - ContextType - > numArticles?: Resolver numAuthors?: Resolver oss?: Resolver - owner?: Resolver, ParentType, ContextType> - participants?: Resolver< - GQLResolversTypes['UserConnection'], - ParentType, - ContextType, - RequireFields - > recommended?: Resolver< GQLResolversTypes['TagConnection'], ParentType, @@ -9361,12 +9019,6 @@ export type GQLTagResolvers< RequireFields > remark?: Resolver, ParentType, ContextType> - selected?: Resolver< - GQLResolversTypes['Boolean'], - ParentType, - ContextType, - RequireFields - > __isTypeOf?: IsTypeOfResolverFn }> @@ -9399,7 +9051,6 @@ export type GQLTagOssResolvers< > = ResolversObject<{ boost?: Resolver score?: Resolver - selected?: Resolver __isTypeOf?: IsTypeOfResolverFn }> diff --git a/src/mutations/article/addArticlesTags.ts b/src/mutations/article/addArticlesTags.ts deleted file mode 100644 index 0da11724a..000000000 --- a/src/mutations/article/addArticlesTags.ts +++ /dev/null @@ -1,93 +0,0 @@ -import type { GQLMutationResolvers } from 'definitions' - -import _difference from 'lodash/difference' -import _some from 'lodash/some' - -import { MAX_TAGS_PER_ARTICLE_LIMIT, USER_STATE } from 'common/enums' -import { environment } from 'common/environment' -import { - ForbiddenByStateError, - ForbiddenError, - TagNotFoundError, - TooManyTagsForArticleError, - UserInputError, -} from 'common/errors' -import { fromGlobalId } from 'common/utils' - -const resolver: GQLMutationResolvers['addArticlesTags'] = async ( - _, - { input: { id, articles, selected } }, - { viewer, dataSources: { atomService, articleService, tagService } } -) => { - if (!viewer.userName) { - throw new ForbiddenError('user has no username') - } - - if (viewer.state === USER_STATE.frozen) { - throw new ForbiddenByStateError(`${viewer.state} user has no permission`) - } - - if (!articles) { - throw new UserInputError('"articles" is required in update') - } - - const { id: dbId } = fromGlobalId(id) - const tag = await tagService.baseFindById(dbId) - if (!tag) { - throw new TagNotFoundError('tag not found') - } - - // add only allow: owner, editor, matty - const isOwner = tag.owner === viewer.id - const isEditor = _some(tag.editors, (editor) => editor === viewer.id) - const isMatty = viewer.id === environment.mattyId - const isMaintainer = isOwner || isEditor || isMatty - - if (!isMaintainer && selected) { - throw new ForbiddenError('not allow add tag to article') - } - - if (!isMatty && tag.id === environment.mattyChoiceTagId) { - throw new ForbiddenError('not allow to add official tag') - } - - // compare new and old article ids that have this tag (dedupe) - const oldIds = await tagService.findArticleIdsByTagIds([dbId]) - const newIds = articles.map((articleId) => fromGlobalId(articleId).id) - const addIds = _difference(newIds, oldIds) - - // not-maintainer can only add his/her own articles - if (!isMaintainer) { - const count = await atomService.count({ - table: 'article', - where: { authorId: viewer.id }, - whereIn: ['id', addIds], - }) - if (count !== addIds.length) { - throw new ForbiddenError('not allow add tag to article') - } - } - - // check article tags - for (const articleId of addIds) { - const tagIds = await articleService.findTagIds({ id: articleId }) - - if (tagIds.length > MAX_TAGS_PER_ARTICLE_LIMIT - 1) { - throw new TooManyTagsForArticleError( - `not allow more than ${MAX_TAGS_PER_ARTICLE_LIMIT} tags on article ${articleId}` - ) - } - } - - // add tag to articles - await tagService.createArticleTags({ - articleIds: addIds, - creator: viewer.id, - tagIds: [dbId], - selected, - }) - - return tag -} - -export default resolver diff --git a/src/mutations/article/deleteArticlesTags.ts b/src/mutations/article/deleteArticlesTags.ts deleted file mode 100644 index f06857157..000000000 --- a/src/mutations/article/deleteArticlesTags.ts +++ /dev/null @@ -1,61 +0,0 @@ -import type { GQLMutationResolvers } from 'definitions' - -import _some from 'lodash/some' - -import { USER_STATE } from 'common/enums' -import { environment } from 'common/environment' -import { - AuthenticationError, - ForbiddenByStateError, - ForbiddenError, - TagNotFoundError, - UserInputError, -} from 'common/errors' -import { fromGlobalId } from 'common/utils' - -const resolver: GQLMutationResolvers['deleteArticlesTags'] = async ( - root, - { input: { id, articles } }, - { viewer, dataSources: { tagService } } -) => { - if (!viewer.id) { - throw new AuthenticationError('visitor has no permission') - } - - if (viewer.state === USER_STATE.frozen) { - throw new ForbiddenByStateError(`${viewer.state} user has no permission`) - } - - if (!articles) { - throw new UserInputError('"articles" is required in update') - } - - const { id: dbId } = fromGlobalId(id) - const tag = await tagService.baseFindById(dbId) - if (!tag) { - throw new TagNotFoundError('tag not found') - } - - // delete only allow: owner, editor, matty - const isOwner = tag.owner === viewer.id - const isEditor = _some(tag.editors, (editor) => editor === viewer.id) - const isMatty = viewer.id === environment.mattyId - const isMaintainer = isOwner || isEditor || isMatty - - if (!isMaintainer) { - throw new ForbiddenError('only editor, creator and matty can manage tag') - } - - // compare new and old article ids which have this tag - const deleteIds = articles.map((articleId) => fromGlobalId(articleId).id) - - // delete unwanted - await tagService.deleteArticleTagsByArticleIds({ - articleIds: deleteIds, - tagId: dbId, - }) - - return tag -} - -export default resolver diff --git a/src/mutations/article/editArticle.ts b/src/mutations/article/editArticle.ts index f8b6c8ca8..6134a71de 100644 --- a/src/mutations/article/editArticle.ts +++ b/src/mutations/article/editArticle.ts @@ -44,7 +44,6 @@ const resolver: GQLMutationResolvers['editArticle'] = async ( input: { id, state, - sticky, pinned, tags, title, @@ -128,9 +127,9 @@ const resolver: GQLMutationResolvers['editArticle'] = async ( } /** - * Pinned or Sticky + * Pinned */ - const isPinned = pinned ?? sticky + const isPinned = pinned if (typeof isPinned === 'boolean') { article = await articleService.updatePinned(article.id, viewer.id, isPinned) } diff --git a/src/mutations/article/index.ts b/src/mutations/article/index.ts index ce45be2b3..d1c68f96f 100644 --- a/src/mutations/article/index.ts +++ b/src/mutations/article/index.ts @@ -1,20 +1,14 @@ -import addArticlesTags from './addArticlesTags' import appreciateArticle from './appreciateArticle' -import deleteArticlesTags from './deleteArticlesTags' import deleteTags from './deleteTags' import editArticle from './editArticle' import mergeTags from './mergeTags' import publishArticle from './publishArticle' -import putTag from './putTag' import readArticle from './readArticle' import renameTag from './renameTag' import toggleArticleRecommend from './toggleArticleRecommend' import toggleSubscribeArticle from './toggleSubscribeArticle' -import toggleTagRecommend from './toggleTagRecommend' import updateArticleSensitive from './updateArticleSensitive' -import updateArticlesTags from './updateArticlesTags' import updateArticleState from './updateArticleState' -import updateTagSetting from './updateTagSetting' export default { Mutation: { @@ -29,11 +23,5 @@ export default { deleteTags, renameTag, mergeTags, - putTag, - addArticlesTags, - deleteArticlesTags, - updateArticlesTags, - updateTagSetting, - toggleTagRecommend, }, } diff --git a/src/mutations/article/mergeTags.ts b/src/mutations/article/mergeTags.ts index a36d3f2e8..219e1fa36 100644 --- a/src/mutations/article/mergeTags.ts +++ b/src/mutations/article/mergeTags.ts @@ -19,8 +19,6 @@ const resolver: GQLMutationResolvers['mergeTags'] = async ( tagIds, content, creator: environment.mattyId, - editors: [environment.mattyId], - owner: environment.mattyId, }) // invalidate extra nodes diff --git a/src/mutations/article/putTag.ts b/src/mutations/article/putTag.ts deleted file mode 100644 index 1882ba705..000000000 --- a/src/mutations/article/putTag.ts +++ /dev/null @@ -1,148 +0,0 @@ -import type { GQLMutationResolvers } from 'definitions' - -import { - ASSET_TYPE, - MAX_TAG_CONTENT_LENGTH, - MAX_TAG_DESCRIPTION_LENGTH, - USER_STATE, -} from 'common/enums' -import { environment } from 'common/environment' -import { - AssetNotFoundError, - DuplicateTagError, - ForbiddenByStateError, - ForbiddenError, - NameInvalidError, - TagNotFoundError, - UserInputError, -} from 'common/errors' -import { - fromGlobalId, - normalizeTagInput, // stripAllPunct, -} from 'common/utils' - -const resolver: GQLMutationResolvers['putTag'] = async ( - _, - { input: { id, content, cover, description } }, - { viewer, dataSources: { systemService, tagService, atomService } } -) => { - if (!viewer.userName) { - throw new ForbiddenError('user has no username') - } - - if (viewer.state === USER_STATE.frozen) { - throw new ForbiddenByStateError(`${viewer.state} user has no permission`) - } - - // check if cover exists when receving parameter cover - let coverId - if (cover) { - const asset = await systemService.findAssetByUUID(cover) - if ( - !asset || - asset.type !== ASSET_TYPE.tagCover || - asset.authorId !== viewer.id - ) { - throw new AssetNotFoundError('tag cover asset does not exists') - } - coverId = asset.id - } else if (cover === null) { - coverId = null - } - - const tagContent = (content && normalizeTagInput(content)) || '' - - if (!tagContent || tagContent.length > MAX_TAG_CONTENT_LENGTH) { - throw new NameInvalidError( - `invalid tag name, either empty or too long (>${MAX_TAG_CONTENT_LENGTH})` - ) - } - if (description && description?.length > MAX_TAG_DESCRIPTION_LENGTH) { - throw new NameInvalidError( - `invalid too long tag description (>${MAX_TAG_DESCRIPTION_LENGTH})` - ) - } - - if (!id) { - // check if any same tag content exists - const tags = await tagService.findByContent({ content: tagContent }) - if (tags.length > 0) { - throw new DuplicateTagError(`dulpicate tag content: ${tagContent}`) - } - - const newTag = await tagService.create( - { - content: tagContent, - creator: viewer.id, - description, - editors: Array.from( - new Set( - environment.mattyId ? [environment.mattyId, viewer.id] : [viewer.id] - ) - ), - owner: viewer.id, - cover: coverId, - } - // ['id', 'content', 'description', 'creator', 'editors', 'owner', 'cover'] - ) - - return newTag - } else { - // update tag - const { id: dbId } = fromGlobalId(id) - const tag = await tagService.baseFindById(dbId) - if (!tag) { - throw new TagNotFoundError('tag not found') - } - - // update only allow: owner, editor, matty - const isOwner = tag.owner === viewer.id - const isEditor = !!tag.editors?.some((editor: any) => editor === viewer.id) - const isMatty = viewer.id === environment.mattyId - const isMaintainer = isOwner || isEditor || isMatty - - if (!isMaintainer) { - throw new ForbiddenError('only owner, editor, and matty can manage tag') - } - - // gather tag update params - const updateParams: { [key: string]: any } = {} - - if (tagContent) { - if (tagContent !== tag.content) { - const tags = await tagService.findByContent({ content: tagContent }) - if (tags.length > 0) { - throw new DuplicateTagError(`dulpicate tag content: ${tagContent}`) - } - } - updateParams.content = tagContent - } - if (typeof description !== 'undefined' && description !== null) { - updateParams.description = description - } - if (typeof coverId !== 'undefined') { - updateParams.cover = coverId - } - if (Object.keys(updateParams).length === 0) { - throw new UserInputError('bad request') - } - - const updateTag = await tagService.baseUpdate(dbId, updateParams) - - // delete unused tag cover - if (tag.cover && tag.cover !== updateTag.cover) { - const coverAsset = await atomService.findUnique({ - where: { id: tag.cover }, - table: 'asset', - }) - if (coverAsset) { - await systemService.deleteAssetAndAssetMap({ - [`${coverAsset.id}`]: coverAsset.path, - }) - } - } - return updateTag - } -} - -export default resolver diff --git a/src/mutations/article/toggleTagRecommend.ts b/src/mutations/article/toggleTagRecommend.ts deleted file mode 100644 index af29d00e1..000000000 --- a/src/mutations/article/toggleTagRecommend.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type { GQLMutationResolvers } from 'definitions' - -import { TagNotFoundError } from 'common/errors' -import { fromGlobalId } from 'common/utils' - -const resolver: GQLMutationResolvers['toggleTagRecommend'] = async ( - _, - { input: { id, enabled } }, - { dataSources: { tagService, atomService } } -) => { - const { id: dbId } = fromGlobalId(id) - const tag = await atomService.tagIdLoader.load(dbId) - if (!tag) { - throw new TagNotFoundError('target tag does not exists') - } - - await (enabled - ? tagService.addTagRecommendation - : tagService.removeTagRecommendation)(dbId) - - return tag -} - -export default resolver diff --git a/src/mutations/article/updateArticlesTags.ts b/src/mutations/article/updateArticlesTags.ts deleted file mode 100644 index 31624f203..000000000 --- a/src/mutations/article/updateArticlesTags.ts +++ /dev/null @@ -1,60 +0,0 @@ -import type { GQLMutationResolvers } from 'definitions' - -import _some from 'lodash/some' - -import { USER_STATE } from 'common/enums' -import { environment } from 'common/environment' -import { - AuthenticationError, - ForbiddenByStateError, - ForbiddenError, - TagNotFoundError, - UserInputError, -} from 'common/errors' -import { fromGlobalId } from 'common/utils' - -const resolver: GQLMutationResolvers['updateArticlesTags'] = async ( - root, - { input: { id, articles, isSelected } }, - { viewer, dataSources: { tagService } } -) => { - if (!viewer.id) { - throw new AuthenticationError('visitor has no permission') - } - - if (viewer.state === USER_STATE.frozen) { - throw new ForbiddenByStateError(`${viewer.state} user has no permission`) - } - - if (!articles) { - throw new UserInputError('"articles" is required in update') - } - - const { id: dbId } = fromGlobalId(id) - const tag = await tagService.baseFindById(dbId) - if (!tag) { - throw new TagNotFoundError('tag not found') - } - - // update only allow: owner, editor, matty - const isOwner = tag.owner === viewer.id - const isEditor = _some(tag.editors, (editor) => editor === viewer.id) - const isMatty = viewer.id === environment.mattyId - const isMaintainer = isOwner || isEditor || isMatty - - if (!isMaintainer) { - throw new ForbiddenError('only owner, editor and matty can manage tag') - } - - // set article as selected or not - const { id: articleId } = fromGlobalId(articles[0]) - await tagService.putArticleTag({ - articleId, - tagId: dbId, - data: { selected: isSelected }, - }) - - return tag -} - -export default resolver diff --git a/src/mutations/article/updateTagSetting.ts b/src/mutations/article/updateTagSetting.ts deleted file mode 100644 index 4f8ae9dc9..000000000 --- a/src/mutations/article/updateTagSetting.ts +++ /dev/null @@ -1,183 +0,0 @@ -import type { GQLMutationResolvers, Tag } from 'definitions' - -import _difference from 'lodash/difference' -import _some from 'lodash/some' -import _uniq from 'lodash/uniq' - -import { - CACHE_KEYWORD, - NODE_TYPES, - USER_STATE, - UPDATE_TAG_SETTING_TYPE, -} from 'common/enums' -import { environment } from 'common/environment' -import { - AuthenticationError, - ForbiddenByStateError, - ForbiddenError, - TagEditorsReachLimitError, - TagNotFoundError, - UserInputError, -} from 'common/errors' -import { fromGlobalId } from 'common/utils' - -const resolver: GQLMutationResolvers['updateTagSetting'] = async ( - _, - { input: { id, type, editors } }, - { viewer, dataSources: { systemService, tagService } } -) => { - if (!viewer.id) { - throw new AuthenticationError('viewer has no permission') - } - - if (viewer.state === USER_STATE.frozen) { - throw new ForbiddenByStateError(`${viewer.state} user has no permission`) - } - - const { id: tagId } = fromGlobalId(id) - const tag = await tagService.baseFindById(tagId) - - if (!tag) { - throw new TagNotFoundError('tag not found') - } - - const { mattyId } = environment - const isOwner = tag.owner === viewer.id - const isMatty = viewer.id === mattyId - - let updatedTag - - switch (type) { - case UPDATE_TAG_SETTING_TYPE.adopt: { - // check feature is enabled - const feature = await systemService.getFeatureFlag('tag_adoption') - if ( - feature && - !(await systemService.isFeatureEnabled(feature.flag, viewer)) - ) { - throw new ForbiddenError('viewer has no permission') - } - - // if tag has been adopted, throw error - if (tag.owner) { - throw new ForbiddenError('viewer has no permission') - } - - // update - updatedTag = await tagService.baseUpdate(tagId, { - owner: viewer.id, - editors: _uniq([...(tag.editors ?? []), viewer.id]), - }) - - break - } - case UPDATE_TAG_SETTING_TYPE.leave: { - // if tag has no owner or owner is not viewer, throw error - if (!tag.owner || (tag.owner && !isOwner)) { - throw new ForbiddenError('viewer has no permission') - } - - // remove viewer from editors - const newEditors = isMatty - ? undefined - : (tag.editors || []).filter((item: string) => item !== viewer.id) - - // update - updatedTag = await tagService.baseUpdate(tagId, { - owner: null, - editors: newEditors, - }) - - break - } - case UPDATE_TAG_SETTING_TYPE.add_editor: { - // only owner can add editors - if (!isOwner) { - throw new ForbiddenError('viewer has no permission') - } - if (!editors || editors.length === 0) { - throw new UserInputError('editors are invalid') - } - - // gather valid editors - const newEditors = - (editors - .map((editor) => { - const { id: editorId } = fromGlobalId(editor) - if (!(tag.editors ?? []).includes(editorId)) { - return editorId - } - }) - .filter((editorId) => editorId !== undefined) as string[]) || [] - - // editors composed by 4 editors, matty and owner - const dedupedEditors = _uniq([...(tag.editors ?? []), ...newEditors]) - if (dedupedEditors.length > 6) { - throw new TagEditorsReachLimitError('number of editors reaches limit') - } - - // update - updatedTag = await tagService.baseUpdate(tagId, { - editors: dedupedEditors, - }) - - break - } - case UPDATE_TAG_SETTING_TYPE.remove_editor: { - // only owner can remove editors - if (!isOwner) { - throw new ForbiddenError('viewer has no permission') - } - if (!editors || editors.length === 0) { - throw new UserInputError('editors are invalid') - } - - // gather valid editors - const removeEditors = - (editors - .map((editor) => { - const { id: editorId } = fromGlobalId(editor) - if (editorId === tag.owner || editorId === mattyId) { - return - } - return editorId - }) - .filter((editorId) => editorId !== undefined) as string[]) || [] - - // update - updatedTag = await tagService.baseUpdate(tagId, { - editors: _difference(tag.editors, removeEditors), - }) - break - } - case UPDATE_TAG_SETTING_TYPE.leave_editor: { - const isEditor = _some(tag.editors, (editor) => editor === viewer.id) - if (!isEditor) { - throw new ForbiddenError('viewer has no permission') - } - if (isOwner || isMatty) { - throw new ForbiddenError('viewer cannot leave') - } - - // update - updatedTag = await tagService.baseUpdate(tagId, { - editors: _difference(tag.editors, [viewer.id]), - }) - - break - } - default: { - throw new UserInputError('unknown update tag type') - } - } - - if (updatedTag) { - // invalidate extra nodes - ;(updatedTag as Tag & { [CACHE_KEYWORD]: any })[CACHE_KEYWORD] = [ - { id: viewer.id, type: NODE_TYPES.User }, - ] - } - return updatedTag -} - -export default resolver diff --git a/src/mutations/user/changeEmail.ts b/src/mutations/user/changeEmail.ts deleted file mode 100644 index 54efc8f04..000000000 --- a/src/mutations/user/changeEmail.ts +++ /dev/null @@ -1,97 +0,0 @@ -import type { GQLMutationResolvers } from 'definitions' - -import { VERIFICATION_CODE_STATUS, VERIFICATION_CODE_TYPE } from 'common/enums' -import { - CodeExpiredError, - CodeInactiveError, - CodeInvalidError, - EmailExistsError, - UserNotFoundError, -} from 'common/errors' - -const resolver: GQLMutationResolvers['changeEmail'] = async ( - _, - { - input: { - oldEmail: rawOldEmail, - oldEmailCodeId, - newEmail: rawNewEmail, - newEmailCodeId, - }, - }, - { dataSources: { userService, atomService } } -) => { - const oldEmail = rawOldEmail ? rawOldEmail.toLowerCase() : null - const newEmail = rawNewEmail ? rawNewEmail.toLowerCase() : null - - const [[oldCode], [newCode]] = await Promise.all([ - userService.findVerificationCodes({ - where: { - uuid: oldEmailCodeId, - email: oldEmail, - type: VERIFICATION_CODE_TYPE.email_reset, - }, - }), - userService.findVerificationCodes({ - where: { - uuid: newEmailCodeId, - email: newEmail, - type: VERIFICATION_CODE_TYPE.email_reset_confirm, - }, - }), - ]) - - // check codes - const isOldCodeVerified = oldCode.status === VERIFICATION_CODE_STATUS.verified - const isNewCodeVerified = newCode.status === VERIFICATION_CODE_STATUS.verified - const hasExpiredCode = - oldCode.status === VERIFICATION_CODE_STATUS.expired || - newCode.status === VERIFICATION_CODE_STATUS.expired - const hasInactiveCode = - oldCode.status === VERIFICATION_CODE_STATUS.inactive || - newCode.status === VERIFICATION_CODE_STATUS.inactive - - if (hasExpiredCode) { - throw new CodeExpiredError('code is expired') - } - if (hasInactiveCode) { - throw new CodeInactiveError('code is retired') - } - if (!isOldCodeVerified || !isNewCodeVerified) { - throw new CodeInvalidError('code does not exists') - } - - // check email - const user = await userService.findByEmail(oldCode.email) - if (!user) { - throw new UserNotFoundError('target user does not exists') - } - - // check new email - const isNewEmailExisted = await userService.findByEmail(newCode.email) - if (isNewEmailExisted) { - throw new EmailExistsError('email already exists') - } - - const newUser = await atomService.update({ - table: 'user', - where: { id: user.id }, - data: { - email: newCode.email, - }, - }) - - // mark code status as used - await userService.markVerificationCodeAs({ - codeId: oldCode.id, - status: VERIFICATION_CODE_STATUS.used, - }) - await userService.markVerificationCodeAs({ - codeId: newCode.id, - status: VERIFICATION_CODE_STATUS.used, - }) - - return newUser -} - -export default resolver diff --git a/src/mutations/user/generateLikerId.ts b/src/mutations/user/generateLikerId.ts deleted file mode 100644 index 3cbc28aec..000000000 --- a/src/mutations/user/generateLikerId.ts +++ /dev/null @@ -1,41 +0,0 @@ -import type { GQLMutationResolvers } from 'definitions' - -import { ForbiddenError } from 'common/errors' - -const resolver: GQLMutationResolvers['generateLikerId'] = async ( - _, - __, - { viewer, dataSources: { userService } } -) => { - if (!viewer.userName) { - throw new ForbiddenError('user has no username') - } - - const { ip } = viewer - - const liker = await userService.findLiker({ userId: viewer.id }) - - // generate - if (!liker || !liker.likerId) { - await userService.registerLikerId({ - userId: viewer.id, - userName: viewer.userName, - ip, - }) - } - - // claim - else { - if (liker.accountType === 'temporal') { - await userService.claimLikerId({ - userId: viewer.id, - liker, - ip, - }) - } - } - - return userService.baseFindById(viewer.id) -} - -export default resolver diff --git a/src/mutations/user/index.ts b/src/mutations/user/index.ts index bc5bb7d05..6e6156331 100644 --- a/src/mutations/user/index.ts +++ b/src/mutations/user/index.ts @@ -1,12 +1,10 @@ import addCredit from './addCredit' -import changeEmail from './changeEmail' import claimLogbooks from './claimLogbooks' import clearReadHistory from './clearReadHistory' import clearSearchHistory from './clearSearchHistory' import confirmVerificationCode from './confirmVerificationCode' import connectStripeAccount from './connectStripeAccount' import emailLogin from './emailLogin' -import generateLikerId from './generateLikerId' import generateSigningMessage from './generateSigningMessage' import migration from './migration' import payout from './payout' @@ -15,7 +13,6 @@ import putFeaturedTags from './putFeaturedTags' import refreshIPNSFeed from './refreshIPNSFeed' import resetLikerId from './resetLikerId' import resetPassword from './resetPassword' -import resetWallet from './resetWallet' import sendVerificationCode from './sendVerificationCode' import setCurrency from './setCurrency' import setEmail from './setEmail' @@ -32,9 +29,7 @@ import updateUserExtra from './updateUserExtra' import updateUserInfo from './updateUserInfo' import updateUserRole from './updateUserRole' import updateUserState from './updateUserState' -import userLogin from './userLogin' import userLogout from './userLogout' -import userRegister from './userRegister' import verifyEmail from './verifyEmail' import { walletLogin, addWalletLogin, removeWalletLogin } from './walletLogin' @@ -43,16 +38,11 @@ export default { sendVerificationCode, confirmVerificationCode, resetPassword, - changeEmail, - userRegister, - userLogin, emailLogin, userLogout, walletLogin, addWalletLogin, removeWalletLogin, - resetWallet, - generateLikerId, generateSigningMessage, resetLikerId, updateUserInfo, diff --git a/src/mutations/user/resetWallet.ts b/src/mutations/user/resetWallet.ts deleted file mode 100644 index ad8f4819b..000000000 --- a/src/mutations/user/resetWallet.ts +++ /dev/null @@ -1,33 +0,0 @@ -import type { GQLMutationResolvers } from 'definitions' - -import { ForbiddenError } from 'common/errors' -import { fromGlobalId } from 'common/utils' - -const resolver: GQLMutationResolvers['resetWallet'] = async ( - _, - { input: { id } }, - { dataSources: { atomService, userService } } -) => { - const { id: dbId } = fromGlobalId(id) - const user = await atomService.userIdLoader.load(dbId) - - if (!user || !user.ethAddress) { - throw new ForbiddenError("user doesn't exist or have a crypto wallet") - } - - if (!user.passwordHash) { - throw new ForbiddenError( - 'user registered with crypto wallet is not allowed' - ) - } - - const updatedUser = await atomService.update({ - table: 'user', - where: { id: user.id }, - data: { updatedAt: new Date(), ethAddress: null }, - }) - - return updatedUser -} - -export default resolver diff --git a/src/mutations/user/sendVerificationCode.ts b/src/mutations/user/sendVerificationCode.ts index af4ab7953..76e6a66c9 100644 --- a/src/mutations/user/sendVerificationCode.ts +++ b/src/mutations/user/sendVerificationCode.ts @@ -75,8 +75,6 @@ const resolver: GQLMutationResolvers['sendVerificationCode'] = async ( if ( type === VERIFICATION_CODE_TYPE.payment_password_reset || - type === VERIFICATION_CODE_TYPE.password_reset || - type === VERIFICATION_CODE_TYPE.email_reset || type === VERIFICATION_CODE_TYPE.email_verify ) { if (!user) { diff --git a/src/mutations/user/updateUserInfo.ts b/src/mutations/user/updateUserInfo.ts index 00d7c178c..4c3b5f7bd 100644 --- a/src/mutations/user/updateUserInfo.ts +++ b/src/mutations/user/updateUserInfo.ts @@ -8,9 +8,6 @@ import { AssetNotFoundError, AuthenticationError, DisplayNameInvalidError, - ForbiddenError, - NameExistsError, - NameInvalidError, PasswordInvalidError, UserInputError, } from 'common/errors' @@ -19,7 +16,6 @@ import { generatePasswordhash, isValidDisplayName, isValidPaymentPassword, - isValidUserName, setCookie, } from 'common/utils' import { cfsvc } from 'connectors' @@ -31,12 +27,7 @@ const resolver: GQLMutationResolvers['updateUserInfo'] = async ( { input }, { viewer, - dataSources: { - userService, - systemService, - notificationService, - atomService, - }, + dataSources: { systemService, notificationService, atomService }, req, res, } @@ -126,52 +117,6 @@ const resolver: GQLMutationResolvers['updateUserInfo'] = async ( updateParams.profileCover = null } - // check user name is editable - if (input.userName) { - const isUserNameEditable = await userService.isUserNameEditable(viewer.id) - if (!isUserNameEditable) { - auditLog({ - actorId: viewer.id, - action: AUDIT_LOG_ACTION.updateUsername, - oldValue: viewer.userName, - newValue: input.userName, - status: AUDIT_LOG_STATUS.failed, - remark: 'user name is not allow to edit', - }) - throw new ForbiddenError('userName is not allow to edit') - } - if (!isValidUserName(input.userName.toLowerCase())) { - auditLog({ - actorId: viewer.id, - action: AUDIT_LOG_ACTION.updateUsername, - oldValue: viewer.userName, - newValue: input.userName, - status: AUDIT_LOG_STATUS.failed, - remark: 'invalid user name', - }) - throw new NameInvalidError('invalid user name') - } - - // allows user to set the same userName - const isSameUserName = - viewer.userName.toLowerCase() === input.userName.toLowerCase() - const isUserNameExists = await userService.checkUserNameExists( - input.userName - ) - if (!isSameUserName && isUserNameExists) { - auditLog({ - actorId: viewer.id, - action: AUDIT_LOG_ACTION.updateUsername, - oldValue: viewer.userName, - newValue: input.userName, - status: AUDIT_LOG_STATUS.failed, - remark: 'user name already exists', - }) - throw new NameExistsError('user name already exists') - } - updateParams.userName = input.userName.toLowerCase() - } - // check user display name if (input.displayName) { if (!isValidDisplayName(input.displayName) && !viewer.hasRole('admin')) { @@ -233,24 +178,6 @@ const resolver: GQLMutationResolvers['updateUserInfo'] = async ( }) logger.info(`Updated id ${viewer.id} in "user"`) - // add user name edit history - if (input.userName) { - await atomService.create({ - table: 'username_edit_history', - data: { - userId: viewer.id, - previous: viewer.userName, - }, - }) - auditLog({ - actorId: viewer.id, - action: AUDIT_LOG_ACTION.updateUsername, - oldValue: viewer.userName, - newValue: input.userName, - status: AUDIT_LOG_STATUS.succeeded, - }) - } - if (input.displayName && viewer.displayName !== input.displayName) { auditLog({ actorId: viewer.id, diff --git a/src/mutations/user/userLogin.ts b/src/mutations/user/userLogin.ts deleted file mode 100644 index 86ad06cd2..000000000 --- a/src/mutations/user/userLogin.ts +++ /dev/null @@ -1,41 +0,0 @@ -import type { AuthMode, GQLMutationResolvers } from 'definitions' - -import { AUTH_RESULT_TYPE } from 'common/enums' -import { getViewerFromUser, setCookie } from 'common/utils' - -const resolver: GQLMutationResolvers['userLogin'] = async ( - _, - { input: { email: rawEmail, password } }, - context -) => { - const { - dataSources: { userService, systemService }, - req, - res, - } = context - - const email = rawEmail.toLowerCase() - const archivedCallback = async () => - systemService.saveAgentHash(context.viewer.agentHash || '', email) - const { token, user } = await userService.loginByEmail({ - email, - password, - archivedCallback, - }) - await userService.verifyPassword({ password, hash: user.passwordHash ?? '' }) - - setCookie({ req, res, token, user }) - - context.viewer = await getViewerFromUser(user) - context.viewer.authMode = user.role as AuthMode - context.viewer.scope = {} - - return { - token, - auth: true, - type: AUTH_RESULT_TYPE.Login, - user, - } -} - -export default resolver diff --git a/src/mutations/user/userRegister.ts b/src/mutations/user/userRegister.ts deleted file mode 100644 index e92de4a4d..000000000 --- a/src/mutations/user/userRegister.ts +++ /dev/null @@ -1,127 +0,0 @@ -import type { AuthMode, GQLMutationResolvers } from 'definitions' - -import { - VERIFICATION_CODE_STATUS, - VERIFICATION_CODE_TYPE, - AUTH_RESULT_TYPE, -} from 'common/enums' -import { - CodeExpiredError, - CodeInactiveError, - CodeInvalidError, - DisplayNameInvalidError, - EmailExistsError, - EmailInvalidError, - NameExistsError, - NameInvalidError, - PasswordInvalidError, -} from 'common/errors' -import { - getViewerFromUser, - isValidDisplayName, - isValidEmail, - isValidPassword, - isValidUserName, - setCookie, -} from 'common/utils' - -const resolver: GQLMutationResolvers['userRegister'] = async ( - _, - { input }, - context -) => { - const { - dataSources: { userService }, - req, - res, - } = context - const { email: rawEmail, userName, displayName, password, codeId } = input - const email = rawEmail.toLowerCase() - if (!isValidEmail(email, { allowPlusSign: false })) { - throw new EmailInvalidError('invalid email address format') - } - - // check verification code - const codes = await userService.findVerificationCodes({ - where: { - uuid: codeId, - email, - type: VERIFICATION_CODE_TYPE.register, - }, - }) - const code = codes?.length > 0 ? codes[0] : {} - - // check code - if (code.status === VERIFICATION_CODE_STATUS.expired) { - throw new CodeExpiredError('code is expired') - } - if (code.status === VERIFICATION_CODE_STATUS.inactive) { - throw new CodeInactiveError('code is retired') - } - if (code.status !== VERIFICATION_CODE_STATUS.verified) { - throw new CodeInvalidError('code does not exists') - } - - // check email - const otherUser = await userService.findByEmail(email) - if (otherUser) { - throw new EmailExistsError('email address has already been registered') - } - - // check display name - // Note: We will use "userName" to pre-fill "displayName" in step-1 of signUp flow on website - const shouldCheckDisplayName = displayName !== userName - if (shouldCheckDisplayName && !isValidDisplayName(displayName)) { - throw new DisplayNameInvalidError('invalid user display name') - } - - // check password - if (!isValidPassword(password)) { - throw new PasswordInvalidError('invalid user password') - } - - let newUserName - if (userName) { - if (!isValidUserName(userName.toLowerCase())) { - throw new NameInvalidError('invalid user name') - } - - if (await userService.checkUserNameExists(userName)) { - throw new NameExistsError('user name already exists') - } - - newUserName = userName - } else { - newUserName = await userService.generateUserName(email) - } - - const newUser = await userService.create({ - ...input, - email, - emailVerified: true, - userName: newUserName.toLowerCase(), - }) - // mark code status as used - await userService.markVerificationCodeAs({ - codeId: code.id, - status: VERIFICATION_CODE_STATUS.used, - }) - await userService.postRegister(newUser) - - const { token, user } = await userService.loginByEmail({ ...input, email }) - - setCookie({ req, res, token, user }) - - context.viewer = await getViewerFromUser(user) - context.viewer.authMode = user.role as AuthMode - context.viewer.scope = {} - - return { - token, - auth: true, - type: AUTH_RESULT_TYPE.Signup, - user, - } -} - -export default resolver diff --git a/src/mutations/user/walletLogin.ts b/src/mutations/user/walletLogin.ts index ef1d7d0e3..92fa53b88 100644 --- a/src/mutations/user/walletLogin.ts +++ b/src/mutations/user/walletLogin.ts @@ -8,21 +8,12 @@ import type { import { Hex } from 'viem' import { - VERIFICATION_CODE_STATUS, - VERIFICATION_CODE_TYPE, AUTH_RESULT_TYPE, SIGNING_MESSAGE_PURPOSE, AUDIT_LOG_ACTION, AUDIT_LOG_STATUS, } from 'common/enums' -import { - CodeExpiredError, - CodeInactiveError, - CodeInvalidError, - EmailExistsError, - EthAddressNotFoundError, - UserInputError, -} from 'common/errors' +import { EthAddressNotFoundError, UserInputError } from 'common/errors' import { auditLog } from 'common/logger' import { getViewerFromUser, setCookie } from 'common/utils' @@ -68,18 +59,7 @@ const _walletLogin: Exclude< undefined > = async ( _, - { - input: { - ethAddress, - nonce, - signedMessage, - signature, - email, - codeId, - language, - referralCode, - }, - }, + { input: { ethAddress, nonce, signedMessage, signature, language } }, context ) => { const { @@ -144,59 +124,11 @@ const _walletLogin: Exclude< } } else { // signup - if (email) { - if (!codeId) { - throw new UserInputError('email and codeId are required') - } - // check verification code - const codes = await userService.findVerificationCodes({ - where: { - uuid: codeId, - email, - type: VERIFICATION_CODE_TYPE.register, - }, - }) - const code = codes?.length > 0 ? codes[0] : {} - - // check code - if (code.status === VERIFICATION_CODE_STATUS.expired) { - throw new CodeExpiredError('code is expired') - } - if (code.status === VERIFICATION_CODE_STATUS.inactive) { - throw new CodeInactiveError('code is retired') - } - if (code.status !== VERIFICATION_CODE_STATUS.verified) { - throw new CodeInvalidError('code does not exists') - } - - // check email - const otherUser = await userService.findByEmail(email) - if (otherUser) { - throw new EmailExistsError('email address has already been registered') - } - - const userName = await userService.generateUserName(email) - user = await userService.create({ - email, - userName, - displayName: userName, - ethAddress: ethAddress.toLowerCase(), // save the lower case ones - language: language || viewer.language, - referralCode, - }) - // mark code status as used - await userService.postRegister(user) - await userService.markVerificationCodeAs({ - codeId: code.id, - status: VERIFICATION_CODE_STATUS.used, - }) - } else { - user = await userService.create({ - ethAddress: ethAddress.toLowerCase(), - language: language || viewer.language, - }) - await userService.postRegister(user) - } + user = await userService.create({ + ethAddress: ethAddress.toLowerCase(), + language: language || viewer.language, + }) + await userService.postRegister(user) } return tryLogin(AUTH_RESULT_TYPE.Signup, user) } diff --git a/src/queries/article/index.ts b/src/queries/article/index.ts index eac295715..1fabf1e4a 100644 --- a/src/queries/article/index.ts +++ b/src/queries/article/index.ts @@ -46,26 +46,17 @@ import sensitiveByAuthor from './sensitiveByAuthor' import shortHash from './shortHash' import slug from './slug' import state from './state' -import sticky from './sticky' import subscribed from './subscribed' import subscribers from './subscribers' import summary from './summary' import summaryCustomized from './summaryCustomized' import tagArticles from './tag/articles' import tagArticlesExcludeSpam from './tag/articlesExcludeSpam' -import tagCover from './tag/cover' -import tagCreator from './tag/creator' -import tagEditors from './tag/editors' -import tagFollowers from './tag/followers' import tagIsFollower from './tag/isFollower' -import tagIsOfficial from './tag/isOfficial' import tagNumArticles from './tag/numArticles' import tagNumAuthors from './tag/numAuthors' import * as tagOSS from './tag/oss' -import tagOwner from './tag/owner' -import tagParticipants from './tag/participants' import tagsRecommended from './tag/recommended' -import tagSelected from './tag/selected' import tags from './tags' import title from './title' import transactionsReceivedBy from './transactionsReceivedBy' @@ -112,7 +103,6 @@ const schema: GQLResolvers = { mediaHash, shortHash, state, - sticky, pinned, subscribed, subscribers, @@ -143,18 +133,10 @@ const schema: GQLResolvers = { id: ({ id }) => toGlobalId({ type: NODE_TYPES.Tag, id }), articles: tagArticles, articlesExcludeSpam: tagArticlesExcludeSpam, - selected: tagSelected, - creator: tagCreator, - editors: tagEditors, - owner: tagOwner, isFollower: tagIsFollower, - isOfficial: tagIsOfficial, numArticles: tagNumArticles, numAuthors: tagNumAuthors, - followers: tagFollowers, oss: (root) => root, - cover: tagCover, - participants: tagParticipants, recommended: tagsRecommended, }, ArticleVersion: { @@ -183,7 +165,6 @@ const schema: GQLResolvers = { TagOSS: { boost: tagOSS.boost, score: tagOSS.score, - selected: tagOSS.selected, }, } diff --git a/src/queries/article/sticky.ts b/src/queries/article/sticky.ts deleted file mode 100644 index d293b39da..000000000 --- a/src/queries/article/sticky.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type { GQLArticleResolvers } from 'definitions' - -const resolver: GQLArticleResolvers['sticky'] = async ({ pinned }) => pinned - -export default resolver diff --git a/src/queries/article/tag/cover.ts b/src/queries/article/tag/cover.ts deleted file mode 100644 index fbd56c8b3..000000000 --- a/src/queries/article/tag/cover.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type { GQLTagResolvers } from 'definitions' - -import _find from 'lodash/find' -import _isNil from 'lodash/isNil' - -const resolver: GQLTagResolvers['cover'] = async ( - { id, cover }, - _, - { dataSources: { systemService, tagService } } -) => { - let coverId = cover - - // fall back to first 10 article cover if tag has no cover - if (!coverId) { - const articleCover = _find( - await tagService.findArticleCovers({ id }), - (item) => !_isNil(item.cover) - ) - coverId = articleCover?.cover ?? null - } - return coverId ? systemService.findAssetUrl(coverId) : null -} - -export default resolver diff --git a/src/queries/article/tag/creator.ts b/src/queries/article/tag/creator.ts deleted file mode 100644 index 8f863a326..000000000 --- a/src/queries/article/tag/creator.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { GQLTagResolvers } from 'definitions' - -const resolver: GQLTagResolvers['creator'] = ( - { creator }, - _, - { dataSources: { atomService } } -) => { - if (!creator) { - return null - } - - return atomService.userIdLoader.load(creator) -} - -export default resolver diff --git a/src/queries/article/tag/editors.ts b/src/queries/article/tag/editors.ts deleted file mode 100644 index e6e8a2f1f..000000000 --- a/src/queries/article/tag/editors.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type { GQLTagResolvers } from 'definitions' - -import { environment } from 'common/environment' - -const resolver: GQLTagResolvers['editors'] = ( - { editors, owner }, - { input }, - { dataSources: { atomService } } -) => { - let ids = editors || [] - - if (input?.excludeAdmin === true) { - ids = ids.filter((editor: string) => editor !== environment.mattyId) - } - - if (input?.excludeOwner === true) { - ids = ids.filter((editor: string) => editor !== owner) - } - - return atomService.userIdLoader.loadMany(ids) -} - -export default resolver diff --git a/src/queries/article/tag/followers.ts b/src/queries/article/tag/followers.ts deleted file mode 100644 index 19b64c976..000000000 --- a/src/queries/article/tag/followers.ts +++ /dev/null @@ -1,39 +0,0 @@ -import type { GQLTagResolvers } from 'definitions' - -import { - connectionFromArray, - connectionFromArrayWithKeys, - cursorToKeys, - fromConnectionArgs, -} from 'common/utils' - -const resolver: GQLTagResolvers['followers'] = async ( - { id }, - { input }, - { dataSources: { tagService, atomService } } -) => { - if (!id) { - return connectionFromArray([], input) - } - - const { take } = fromConnectionArgs(input) - const keys = cursorToKeys(input.after) - const params = { targetId: id, skip: keys.idCursor, take } - const [count, actions] = await Promise.all([ - tagService.countFollowers(id), - tagService.findFollowers(params), - ]) - const cursors = actions.reduce( - (map, action) => ({ ...map, [action.userId]: action.id }), - {} - ) - - const users = await atomService.userIdLoader.loadMany( - actions.map(({ userId }: { userId: string }) => userId) - ) - const data = users.map((user) => ({ ...user, __cursor: cursors[user.id] })) - - return connectionFromArrayWithKeys(data, input, count) -} - -export default resolver diff --git a/src/queries/article/tag/isOfficial.ts b/src/queries/article/tag/isOfficial.ts deleted file mode 100644 index 6e902633f..000000000 --- a/src/queries/article/tag/isOfficial.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { GQLTagResolvers } from 'definitions' - -import { environment } from 'common/environment' - -const resolver: GQLTagResolvers['isOfficial'] = async ({ id }) => { - const { mattyChoiceTagId } = environment - return id === mattyChoiceTagId -} - -export default resolver diff --git a/src/queries/article/tag/oss.ts b/src/queries/article/tag/oss.ts index 36cff7ab7..245ca9aa4 100644 --- a/src/queries/article/tag/oss.ts +++ b/src/queries/article/tag/oss.ts @@ -11,21 +11,3 @@ export const score: GQLTagOssResolvers['score'] = ( _, { dataSources: { tagService } } ) => tagService.findScore(id) - -export const selected: GQLTagOssResolvers['selected'] = async ( - { id }, - _, - { - dataSources: { - connections: { knex }, - }, - } -) => { - const result = await knex - .from('matters_choice_tag') - .where({ tagId: id }) - .count() - .first() - - return parseInt(result ? (result.count as string) : '0', 10) > 0 -} diff --git a/src/queries/article/tag/owner.ts b/src/queries/article/tag/owner.ts deleted file mode 100644 index 8f607e578..000000000 --- a/src/queries/article/tag/owner.ts +++ /dev/null @@ -1,9 +0,0 @@ -import type { GQLTagResolvers } from 'definitions' - -const resolver: GQLTagResolvers['owner'] = ( - { owner }, - _, - { dataSources: { atomService } } -) => (owner ? atomService.userIdLoader.load(owner) : null) - -export default resolver diff --git a/src/queries/article/tag/participants.ts b/src/queries/article/tag/participants.ts deleted file mode 100644 index 2b651568b..000000000 --- a/src/queries/article/tag/participants.ts +++ /dev/null @@ -1,36 +0,0 @@ -import type { GQLTagResolvers } from 'definitions' - -import { - connectionFromArray, - connectionFromPromisedArray, - fromConnectionArgs, -} from 'common/utils' - -const resolver: GQLTagResolvers['participants'] = async ( - { id, owner }, - { input }, - { dataSources: { tagService, atomService } } -) => { - if (!id) { - return connectionFromArray([], input) - } - - const { take, skip } = fromConnectionArgs(input) - - const exclude = owner ? [owner] : [] - const totalCount = await tagService.countParticipants({ id, exclude }) - const userIds = await tagService.findParticipants({ - id, - skip, - take, - exclude, - }) - - return connectionFromPromisedArray( - atomService.userIdLoader.loadMany(userIds.map(({ authorId }) => authorId)), - input, - totalCount - ) -} - -export default resolver diff --git a/src/queries/article/tag/selected.ts b/src/queries/article/tag/selected.ts deleted file mode 100644 index 00ee67b7c..000000000 --- a/src/queries/article/tag/selected.ts +++ /dev/null @@ -1,30 +0,0 @@ -import type { GQLTagResolvers } from 'definitions' - -import { ArticleNotFoundError } from 'common/errors' -import { fromGlobalId } from 'common/utils' - -const resolver: GQLTagResolvers['selected'] = async ( - { id }, - { input }, - { dataSources: { tagService, articleService } } -) => { - let articleId: string | undefined - - if (input.id) { - articleId = fromGlobalId(input.id).id - } else if (input.mediaHash) { - const node = await articleService.findVersionByMediaHash(input.mediaHash) - articleId = node.articleId - } - - if (!articleId) { - throw new ArticleNotFoundError('Cannot find article by a given input') - } - - return tagService.isArticleSelected({ - articleId, - tagId: id, - }) -} - -export default resolver diff --git a/src/queries/draft/article/drafts.ts b/src/queries/draft/article/drafts.ts deleted file mode 100644 index 8b1812bdf..000000000 --- a/src/queries/draft/article/drafts.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type { GQLArticleResolvers } from 'definitions' - -const resolver: GQLArticleResolvers['drafts'] = async ( - { id: articleId }, - _, - { dataSources: { atomService } } -) => - atomService.findMany({ - table: 'draft', - where: { articleId }, - orderBy: [{ column: 'created_at', order: 'desc' }], - }) - -export default resolver diff --git a/src/queries/draft/article/newestPublishedDraft.ts b/src/queries/draft/article/newestPublishedDraft.ts deleted file mode 100644 index 72b4e35b1..000000000 --- a/src/queries/draft/article/newestPublishedDraft.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { GQLArticleResolvers } from 'definitions' - -import { PUBLISH_STATE } from 'common/enums' - -const resolver: GQLArticleResolvers['newestPublishedDraft'] = async ( - { id: articleId }, - _, - { dataSources: { atomService } } -) => { - const draft = await atomService.findFirst({ - table: 'draft', - where: { articleId, publishState: PUBLISH_STATE.published }, - orderBy: [{ column: 'created_at', order: 'desc' }], - }) - return draft -} - -export default resolver diff --git a/src/queries/draft/article/newestUnpublishedDraft.ts b/src/queries/draft/article/newestUnpublishedDraft.ts deleted file mode 100644 index 4c1e31445..000000000 --- a/src/queries/draft/article/newestUnpublishedDraft.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type { GQLArticleResolvers } from 'definitions' - -import { PUBLISH_STATE } from 'common/enums' - -const resolver: GQLArticleResolvers['newestUnpublishedDraft'] = async ( - { id: articleId }, - _, - { dataSources: { atomService } } -) => { - const draft = await atomService.findFirst({ - table: 'draft', - where: { articleId }, - whereIn: [ - 'publish_state', - [PUBLISH_STATE.unpublished, PUBLISH_STATE.pending, PUBLISH_STATE.error], - ], - orderBy: [{ column: 'created_at', order: 'desc' }], - }) - - return draft -} - -export default resolver diff --git a/src/queries/draft/index.ts b/src/queries/draft/index.ts index 72004b364..d79fbe55a 100644 --- a/src/queries/draft/index.ts +++ b/src/queries/draft/index.ts @@ -7,9 +7,6 @@ import { NODE_TYPES } from 'common/enums' import { countWords, toGlobalId } from 'common/utils' import * as draftAccess from './access' -import articleDrafts from './article/drafts' -import articleNewestPublishedDraft from './article/newestPublishedDraft' -import articleNewestUnpublishedDraft from './article/newestUnpublishedDraft' import assets from './assets' import campaigns from './campaigns' import collection from './collection' @@ -18,11 +15,6 @@ import draftCover from './cover' import drafts from './drafts' const schema: GQLResolvers = { - Article: { - drafts: articleDrafts, - newestUnpublishedDraft: articleNewestUnpublishedDraft, - newestPublishedDraft: articleNewestPublishedDraft, - }, User: { drafts, }, diff --git a/src/queries/user/liker/index.ts b/src/queries/user/liker/index.ts index 1d391f408..0c24b27b6 100644 --- a/src/queries/user/liker/index.ts +++ b/src/queries/user/liker/index.ts @@ -1,11 +1,9 @@ import civicLiker from './civicLiker' import likerId from './likerId' -import rateUSD from './rateUSD' import total from './total' export default { likerId, civicLiker, total, - rateUSD, } diff --git a/src/queries/user/liker/rateUSD.ts b/src/queries/user/liker/rateUSD.ts deleted file mode 100644 index 099fe1381..000000000 --- a/src/queries/user/liker/rateUSD.ts +++ /dev/null @@ -1,22 +0,0 @@ -import type { GQLLikerResolvers } from 'definitions' - -import { getLogger } from 'common/logger' - -const logger = getLogger('query-rate-usd') - -const resolver: GQLLikerResolvers['rateUSD'] = async ( - _, - __: any, - { dataSources: { userService } } -) => { - try { - const price = await userService.likecoin.rate() - - return price - } catch (e) { - logger.error(e) - return null - } -} - -export default resolver diff --git a/src/queries/user/recommendation/index.ts b/src/queries/user/recommendation/index.ts index 4b506446a..9d6a7fc43 100644 --- a/src/queries/user/recommendation/index.ts +++ b/src/queries/user/recommendation/index.ts @@ -9,14 +9,12 @@ import { icymi } from './icymi' import { icymiTopic } from './icymiTopic' import { newest, newestExcludeSpam } from './newest' import newestCircles from './newestCircles' -import readTagsArticles from './readTagsArticles' import { selectedTags } from './selectedTags' import { tags } from './tags' const resolvers: GQLRecommendationResolvers = { authors, following, - readTagsArticles, hottest, hottestExcludeSpam, icymi, diff --git a/src/queries/user/recommendation/readTagsArticles.ts b/src/queries/user/recommendation/readTagsArticles.ts deleted file mode 100644 index 96391171a..000000000 --- a/src/queries/user/recommendation/readTagsArticles.ts +++ /dev/null @@ -1,40 +0,0 @@ -import type { GQLRecommendationResolvers } from 'definitions' - -import { - connectionFromArray, - connectionFromPromisedArray, - fromConnectionArgs, -} from 'common/utils' - -const resolver: GQLRecommendationResolvers['readTagsArticles'] = async ( - { id: userId }, - { input }, - { dataSources: { atomService } } -) => { - if (!userId) { - return connectionFromArray([], input) - } - - const { take, skip } = fromConnectionArgs(input) - - const [totalCount, tagArticles] = await Promise.all([ - atomService.count({ - table: 'recommended_articles_from_read_tags_materialized', - where: { userId }, - }), - atomService.findMany({ - table: 'recommended_articles_from_read_tags_materialized', - where: { userId }, - take, - skip, - }), - ]) - - return connectionFromPromisedArray( - atomService.articleIdLoader.loadMany(tagArticles.map((d) => d.articleId)), - input, - totalCount - ) -} - -export default resolver diff --git a/src/types/__test__/1/article.test.ts b/src/types/__test__/1/article.test.ts index 0e47d73c1..1d613c6f1 100644 --- a/src/types/__test__/1/article.test.ts +++ b/src/types/__test__/1/article.test.ts @@ -106,28 +106,6 @@ const GET_ARTICLE_TAGS = /* GraphQL */ ` } ` -const GET_ARTICLE_DRAFTS = /* GraphQL */ ` - query ($input: NodeInput!) { - node(input: $input) { - ... on Article { - id - drafts { - id - publishState - } - newestPublishedDraft { - id - publishState - } - newestUnpublishedDraft { - id - publishState - } - } - } - } -` - const TOGGLE_SUBSCRIBE_ARTICLE = /* GraphQL */ ` mutation ($input: ToggleItemInput!) { toggleSubscribeArticle(input: $input) { @@ -247,30 +225,6 @@ describe('query tag on article', () => { }) }) -describe('query drafts on article', () => { - test('query drafts on article', async () => { - const id = toGlobalId({ type: NODE_TYPES.Article, id: 4 }) - const server = await testClient({ connections }) - const { data } = await server.executeOperation({ - query: GET_ARTICLE_DRAFTS, - variables: { input: { id } }, - }) - - // drafts - const drafts = data && data.node && data.node.drafts - expect(drafts[0].publishState).toEqual(PUBLISH_STATE.published) - - // unpublishedDraft - const unpublishedDraft = - data && data.node && data.node.newestUnpublishedDraft - expect(unpublishedDraft).toBeNull() - - // publishedDraft - const publishedDraft = data && data.node && data.node.newestPublishedDraft - expect(publishedDraft.publishState).toEqual(PUBLISH_STATE.published) - }) -}) - describe('publish article', () => { test('user w/o username can not publish', async () => { const draft = { diff --git a/src/types/__test__/1/editArticle.test.ts b/src/types/__test__/1/editArticle.test.ts index 90f59d6c8..9c9272162 100644 --- a/src/types/__test__/1/editArticle.test.ts +++ b/src/types/__test__/1/editArticle.test.ts @@ -128,7 +128,7 @@ const EDIT_ARTICLE = /* GraphQL */ ` id content } - sticky + pinned state license requestForDonation @@ -691,7 +691,7 @@ describe('edit article', () => { globalThis.mockEnums.MAX_ARTICLE_REVISION_COUNT = originalCheckRevisionCount }) - test('toggle article sticky', async () => { + test('toggle article pinned', async () => { const server = await testClient({ isAuth: true, connections, @@ -702,22 +702,22 @@ describe('edit article', () => { variables: { input: { id: articleGlobalId, - sticky: true, + pinned: true, }, }, }) - expect(_get(enableResult, 'data.editArticle.sticky')).toBe(true) + expect(_get(enableResult, 'data.editArticle.pinned')).toBe(true) const disableResult = await server.executeOperation({ query: EDIT_ARTICLE, variables: { input: { id: articleGlobalId, - sticky: false, + pinned: false, }, }, }) - expect(_get(disableResult, 'data.editArticle.sticky')).toBe(false) + expect(_get(disableResult, 'data.editArticle.pinned')).toBe(false) }) test('edit license', async () => { diff --git a/src/types/__test__/1/tag.test.ts b/src/types/__test__/1/tag.test.ts index bca5e08f2..f23399e8e 100644 --- a/src/types/__test__/1/tag.test.ts +++ b/src/types/__test__/1/tag.test.ts @@ -1,26 +1,13 @@ -import type { - GQLPutTagInput, - GQLUpdateTagSettingInput, - Connections, -} from 'definitions' +import type { Connections } from 'definitions' import _difference from 'lodash/difference' import _get from 'lodash/get' -import { - NODE_TYPES, - FEATURE_FLAG, - FEATURE_NAME, - UPDATE_TAG_SETTING_TYPE, -} from 'common/enums' +import { NODE_TYPES } from 'common/enums' import { toGlobalId } from 'common/utils' -import { - setFeature, - testClient, - genConnections, - closeConnections, -} from '../utils' +import { testClient, genConnections, closeConnections } from '../utils' +import { TagService } from 'connectors' declare global { // eslint-disable-next-line no-var @@ -43,7 +30,6 @@ const QUERY_TAG = /* GraphQL */ ` ... on Tag { id content - description recommended(input: {}) { edges { node { @@ -58,37 +44,6 @@ const QUERY_TAG = /* GraphQL */ ` } ` -const PUT_TAG = /* GraphQL */ ` - mutation ($input: PutTagInput!) { - putTag(input: $input) { - id - content - description - editors { - id - } - owner { - id - } - } - } -` - -const UPDATE_TAG_SETTING = /* GraphQL */ ` - mutation ($input: UpdateTagSettingInput!) { - updateTagSetting(input: $input) { - id - content - editors { - id - } - owner { - id - } - } - } -` - const RENAME_TAG = /* GraphQL */ ` mutation ($input: RenameTagInput!) { renameTag(input: $input) { @@ -104,9 +59,6 @@ const MERGE_TAG = /* GraphQL */ ` ... on Tag { id content - owner { - id - } } } } @@ -118,151 +70,14 @@ const DELETE_TAG = /* GraphQL */ ` } ` -const ADD_ARTICLES_TAGS = /* GraphQL */ ` - mutation ($input: AddArticlesTagsInput!) { - addArticlesTags(input: $input) { - id - articles(input: { after: null, first: null, oss: true }) { - edges { - node { - ... on Article { - id - } - } - } - } - } - } -` - -const UPDATE_ARTICLES_TAGS = /* GraphQL */ ` - mutation ($input: UpdateArticlesTagsInput!) { - updateArticlesTags(input: $input) { - id - articles(input: { after: null, first: null, oss: true }) { - edges { - node { - ... on Article { - id - } - } - } - } - } - } -` - -const DELETE_ARTICLES_TAGS = /* GraphQL */ ` - mutation ($input: DeleteArticlesTagsInput!) { - deleteArticlesTags(input: $input) { - id - articles(input: { after: null, first: null, oss: true }) { - edges { - node { - ... on Article { - id - } - } - } - } - } - } -` - -interface BaseInput { - isAdmin?: boolean - isAuth?: boolean - isMatty?: boolean -} - -type PutTagInput = { tag: GQLPutTagInput } & BaseInput - -export const putTag = async ({ - isAdmin = true, - isAuth = true, - isMatty = true, - tag, -}: PutTagInput) => { - const server = await testClient({ isAdmin, isAuth, isMatty, connections }) - const result = await server.executeOperation({ - query: PUT_TAG, - variables: { input: tag }, - }) - const data = result?.data?.putTag - return data -} - -type UpdateTagSettingInput = GQLUpdateTagSettingInput & BaseInput - -export const updateTagSetting = async ({ - isAdmin = false, - isAuth = false, - isMatty = false, - id, - type, - editors, -}: UpdateTagSettingInput) => { - const server = await testClient({ isAdmin, isAuth, isMatty, connections }) - const result = await server.executeOperation({ - query: UPDATE_TAG_SETTING, - variables: { input: { id, type, editors } }, - }) - - if (!result.data) { - return result - } - const data = result?.data?.updateTagSetting - return data -} - -describe('put tag', () => { - test('create, query and update tag', async () => { - const content = 'Test tag #1' - const expected = 'Test tag1' - const description = 'This is a tag description' - - // create - const createResult = await putTag({ tag: { content, description } }) - const createTagId = createResult?.id - expect(createTagId).toBeDefined() - - // query - const server = await testClient({ - isAuth: true, - isAdmin: true, - isMatty: true, - connections, - }) - const { data, errors } = await server.executeOperation({ - query: QUERY_TAG, - variables: { input: { id: createTagId } }, - }) - console.log(errors) - expect(data.node.content).toBe(expected) - expect(data.node.description).toBe(description) - - // update - const updateContent = 'Update tag #1' - const updateExpected = 'Update tag1' - const updateDescription = 'Update description' - const updateResult = await putTag({ - tag: { - id: createTagId, - content: updateContent, - description: updateDescription, - }, - }) - expect(updateResult?.content).toBe(updateExpected) - expect(updateResult?.description).toBe(updateDescription) - }) -}) - describe('manage tag', () => { test('rename and delete tag', async () => { - // create - const createResult = await putTag({ tag: { content: 'Test tag #1' } }) - const createTagId = createResult?.id - expect(createTagId).toBeDefined() + const tagService = new TagService(connections) + const tag = await tagService.create({ + content: 'Test tag #1', + creator: '0', + }) + const createTagId = toGlobalId({ type: NODE_TYPES.Tag, id: tag?.id }) const server = await testClient({ isAuth: true, @@ -270,6 +85,7 @@ describe('manage tag', () => { isMatty: true, connections, }) + // rename const renameContent = 'Rename tag' const renameResult = await server.executeOperation({ @@ -286,9 +102,6 @@ describe('manage tag', () => { }) const mergeTagId = mergeResult?.data?.mergeTags?.id expect(mergeResult?.data?.mergeTags?.content).toBe(mergeContent) - expect(mergeResult?.data?.mergeTags?.owner?.id).toBe( - toGlobalId({ type: NODE_TYPES.User, id: 6 }) - ) // delete const deleteResult = await server.executeOperation({ @@ -299,383 +112,6 @@ describe('manage tag', () => { }) }) -describe('manage article tag', () => { - test('users w/o username can not add tags', async () => { - const server = await testClient({ noUserName: true, connections }) - const { errors } = await server.executeOperation({ - query: PUT_TAG, - variables: { input: { content: 'faketag' } }, - }) - expect(errors?.[0].extensions.code).toBe('FORBIDDEN') - }) - test('add and delete article tag', async () => { - // create - const createResult = await putTag({ tag: { content: 'Test tag #1' } }) - const createTagId = createResult?.id - expect(createTagId).toBeDefined() - - const server = await testClient({ - isAuth: true, - isAdmin: true, - isMatty: true, - connections, - }) - - const articleIds = [ - toGlobalId({ type: NODE_TYPES.Article, id: 1 }), - toGlobalId({ type: NODE_TYPES.Article, id: 2 }), - ] - - // add - const addResult = await server.executeOperation({ - query: ADD_ARTICLES_TAGS, - variables: { - input: { - id: createTagId, - articles: articleIds, - }, - }, - }) - expect(addResult?.data?.addArticlesTags?.articles?.edges.length).toBe(2) - - // update - const updateResult = await server.executeOperation({ - query: UPDATE_ARTICLES_TAGS, - variables: { - input: { - id: createTagId, - articles: articleIds, - isSelected: true, - }, - }, - }) - expect(updateResult?.data?.updateArticlesTags?.articles?.edges.length).toBe( - 2 - ) - - // remove - const deleteResult = await server.executeOperation({ - query: DELETE_ARTICLES_TAGS, - variables: { - input: { - id: createTagId, - articles: articleIds, - }, - }, - }) - expect(deleteResult?.data?.deleteArticlesTags?.articles?.edges.length).toBe( - 0 - ) - }) -}) - -describe('manage settings of a tag', () => { - const errorPath = 'errors.0.extensions.code' - const editorFilter = (editor: any) => editor?.id - - test('adopt and leave tag', async () => { - const authedId = toGlobalId({ type: NODE_TYPES.User, id: 1 }) - const mattyId = toGlobalId({ type: NODE_TYPES.User, id: 6 }) - - // matty enable user can adopt tag - await setFeature( - { - isAdmin: true, - isMatty: true, - input: { - name: FEATURE_NAME.tag_adoption, - flag: FEATURE_FLAG.on, - }, - }, - connections - ) - - // matty create tag - const tag = await putTag({ tag: { content: 'Tag adoption #1' } }) - const editors = (tag?.editors || []).map(editorFilter) - expect(editors.includes(mattyId)).toBeTruthy() - expect(tag?.owner?.id).toBe(mattyId) - - // authed user try adopt matty's tag - const adoptMattyTagData = await updateTagSetting({ - isAuth: true, - id: tag.id, - type: UPDATE_TAG_SETTING_TYPE.adopt, - }) - expect(_get(adoptMattyTagData, errorPath)).toBe('FORBIDDEN') - - // authed user try to leave matty's tag - const leaveMattyTagData = await updateTagSetting({ - isAuth: true, - id: tag.id, - type: UPDATE_TAG_SETTING_TYPE.leave, - }) - expect(_get(leaveMattyTagData, errorPath)).toBe('FORBIDDEN') - - // matty leave tag - const mattyLeaveTagData = await updateTagSetting({ - isAuth: true, - isMatty: true, - id: tag.id, - type: UPDATE_TAG_SETTING_TYPE.leave, - }) - const mattyLeaveTagDataEditors = (mattyLeaveTagData?.editors || []).map( - editorFilter - ) - expect(mattyLeaveTagDataEditors.includes(mattyId)).toBeTruthy() - expect(mattyLeaveTagData?.owner).toBe(null) - - // authed user adopt tag - const adoptData = await updateTagSetting({ - isAuth: true, - id: tag.id, - type: UPDATE_TAG_SETTING_TYPE.adopt, - }) - const adoptDataEditors = (adoptData?.editors || []).map(editorFilter) - expect(adoptDataEditors.includes(authedId)).toBeTruthy() - expect(adoptData?.owner?.id).toBe(authedId) - - // authed user leave tag - const leaveData = await updateTagSetting({ - isAuth: true, - id: tag.id, - type: UPDATE_TAG_SETTING_TYPE.leave, - }) - const leaveDataEditors = (leaveData?.editors || []).map(editorFilter) - expect(leaveDataEditors.includes(authedId)).toBeFalsy() - expect(leaveDataEditors.includes(mattyId)).toBeTruthy() - expect(leaveData?.owner).toBe(null) - }) - - test('add and remove editor to a tag', async () => { - const user1Id = toGlobalId({ type: NODE_TYPES.User, id: 1 }) - const user2Id = toGlobalId({ type: NODE_TYPES.User, id: 2 }) - const user3Id = toGlobalId({ type: NODE_TYPES.User, id: 3 }) - const user4Id = toGlobalId({ type: NODE_TYPES.User, id: 4 }) - const user7Id = toGlobalId({ type: NODE_TYPES.User, id: 7 }) - const mattyId = toGlobalId({ type: NODE_TYPES.User, id: 6 }) - const user9Id = toGlobalId({ type: NODE_TYPES.User, id: 9 }) - - // matty create tag - const tag = await putTag({ tag: { content: 'Tag editor #1' } }) - const editors = (tag?.editors || []).map(editorFilter) - expect(editors.includes(mattyId)).toBeTruthy() - expect(tag?.owner?.id).toBe(mattyId) - - // other try add editor - const otherAddEditorData = await updateTagSetting({ - isAuth: true, - id: tag.id, - type: UPDATE_TAG_SETTING_TYPE.add_editor, - editors: [user2Id], - }) - expect(_get(otherAddEditorData, errorPath)).toBe('FORBIDDEN') - - // other try remove editor - const otherRemoveEditorData = await updateTagSetting({ - isAuth: true, - id: tag.id, - type: UPDATE_TAG_SETTING_TYPE.remove_editor, - editors: [user2Id], - }) - expect(_get(otherRemoveEditorData, errorPath)).toBe('FORBIDDEN') - - // owner add self into edtor - const addSelfData = await updateTagSetting({ - isAuth: true, - isMatty: true, - id: tag.id, - type: UPDATE_TAG_SETTING_TYPE.add_editor, - editors: [mattyId], - }) - const addSelfDataEditors = (addSelfData?.editors || []).map(editorFilter) - expect(addSelfDataEditors.includes(mattyId)).toBeTruthy() - expect(addSelfData?.owner?.id).toBe(mattyId) - - // owner remove self from editor - const rmSelfData = await updateTagSetting({ - isAuth: true, - isMatty: true, - id: tag.id, - type: UPDATE_TAG_SETTING_TYPE.remove_editor, - editors: [mattyId], - }) - const rmSelfDataEditors = (rmSelfData?.editors || []).map(editorFilter) - expect(rmSelfDataEditors.includes(mattyId)).toBeTruthy() - expect(rmSelfData?.owner?.id).toBe(mattyId) - - // add other users - const addData1 = await updateTagSetting({ - isAuth: true, - isMatty: true, - id: tag.id, - type: UPDATE_TAG_SETTING_TYPE.add_editor, - editors: [user1Id], - }) - const addData1Editors = (addData1?.editors || []).map(editorFilter) - expect(_difference(addData1Editors, [mattyId, user1Id]).length).toBe(0) - expect(addData1?.editors.length).toBe(2) - - const addData2 = await updateTagSetting({ - isAuth: true, - isMatty: true, - id: tag.id, - type: UPDATE_TAG_SETTING_TYPE.add_editor, - editors: [user2Id, user3Id, user4Id], - }) - const addData2Editors = (addData2?.editors || []).map(editorFilter) - expect( - _difference(addData2Editors, [ - mattyId, - user1Id, - user2Id, - user3Id, - user4Id, - ]).length - ).toBe(0) - expect(addData2?.editors.length).toBe(5) - - const addData3 = await updateTagSetting({ - isAuth: true, - isMatty: true, - id: tag.id, - type: UPDATE_TAG_SETTING_TYPE.add_editor, - editors: [user7Id, user9Id], - }) - expect(_get(addData3, errorPath)).toBe('TAG_EDITORS_REACH_LIMIT') - - // remove other users - const rmData1 = await updateTagSetting({ - isAuth: true, - isMatty: true, - id: tag.id, - type: UPDATE_TAG_SETTING_TYPE.remove_editor, - editors: [user4Id], - }) - const rmData1Editors = (rmData1?.editors || []).map(editorFilter) - expect(rmData1Editors.includes(mattyId)).toBeTruthy() - expect(rmData1Editors.includes(user1Id)).toBeTruthy() - expect(rmData1Editors.includes(user2Id)).toBeTruthy() - expect(rmData1Editors.includes(user3Id)).toBeTruthy() - expect(rmData1Editors.includes(user4Id)).toBeFalsy() - expect(rmData1?.editors.length).toBe(4) - - const rmData2 = await updateTagSetting({ - isAuth: true, - isMatty: true, - id: tag.id, - type: UPDATE_TAG_SETTING_TYPE.remove_editor, - editors: [user2Id, user3Id], - }) - const rmData2Editors = (rmData2?.editors || []).map(editorFilter) - expect(rmData2Editors.includes(mattyId)).toBeTruthy() - expect(rmData2Editors.includes(user1Id)).toBeTruthy() - expect(rmData2Editors.includes(user2Id)).toBeFalsy() - expect(rmData2Editors.includes(user3Id)).toBeFalsy() - expect(rmData2?.editors.length).toBe(2) - - const rmData3 = await updateTagSetting({ - isAuth: true, - isMatty: true, - id: tag.id, - type: UPDATE_TAG_SETTING_TYPE.remove_editor, - editors: [user1Id], - }) - const rmData3Editors = (rmData3?.editors || []).map(editorFilter) - expect(rmData3Editors.includes(mattyId)).toBeTruthy() - expect(rmData3Editors.includes(user1Id)).toBeFalsy() - expect(rmData3?.editors.length).toBe(1) - - // authed user create tag - const authedUserTag = await putTag({ - isAdmin: false, - isMatty: false, - tag: { content: 'Tag editor #2' }, - }) - const authedUserTagEditors = (authedUserTag?.editors || []).map( - editorFilter - ) - expect(_difference(authedUserTagEditors, [mattyId, user1Id]).length).toBe(0) - expect(authedUserTag?.owner?.id).toBe(user1Id) - expect(authedUserTag?.editors.length).toBe(2) - - const authedUserAddData1 = await updateTagSetting({ - isAuth: true, - id: authedUserTag.id, - type: UPDATE_TAG_SETTING_TYPE.remove_editor, - editors: [mattyId], - }) - const authedUserAddData1Editors = (authedUserAddData1?.editors || []).map( - editorFilter - ) - expect( - _difference(authedUserAddData1Editors, [mattyId, user1Id]).length - ).toBe(0) - expect(authedUserAddData1?.editors.length).toBe(2) - - const authedUserAddData2 = await updateTagSetting({ - isAuth: true, - id: authedUserTag.id, - type: UPDATE_TAG_SETTING_TYPE.add_editor, - editors: [user1Id, user2Id], - }) - const authedUserAddData2Editors = (authedUserAddData2?.editors || []).map( - editorFilter - ) - expect( - _difference(authedUserAddData2Editors, [mattyId, user1Id, user2Id]).length - ).toBe(0) - expect(authedUserAddData2?.editors.length).toBe(3) - - const authedUserRmData1 = await updateTagSetting({ - isAuth: true, - id: authedUserTag.id, - type: UPDATE_TAG_SETTING_TYPE.remove_editor, - editors: [mattyId], - }) - const authedUserRmData1Editors = (authedUserRmData1?.editors || []).map( - editorFilter - ) - expect(authedUserRmData1Editors.includes(mattyId)).toBeTruthy() - expect(authedUserRmData1?.editors.length).toBe(3) - }) - - test('leave editor from a tag', async () => { - const user1Id = toGlobalId({ type: NODE_TYPES.User, id: 1 }) - // const user2Id = toGlobalId({ type: NODE_TYPES.User, id: 2 }) - const mattyId = toGlobalId({ type: NODE_TYPES.User, id: 6 }) - - // matty create tag - const tag = await putTag({ tag: { content: 'Tag editor #3' } }) - const editors = (tag?.editors || []).map(editorFilter) - expect(editors.includes(mattyId)).toBeTruthy() - expect(tag?.owner?.id).toBe(mattyId) - - // add editor - const addData = await updateTagSetting({ - isAuth: true, - isMatty: true, - id: tag.id, - type: UPDATE_TAG_SETTING_TYPE.add_editor, - editors: [user1Id], - }) - const addDataEditors = (addData?.editors || []).map(editorFilter) - expect(_difference(addDataEditors, [mattyId, user1Id]).length).toBe(0) - expect(addData?.editors.length).toBe(2) - - // user leave from editors - const leaveData = await updateTagSetting({ - isAuth: true, - id: tag.id, - type: UPDATE_TAG_SETTING_TYPE.leave_editor, - editors: [user1Id], - }) - const leaveDataEditors = (leaveData?.editors || []).map(editorFilter) - expect(_difference(leaveDataEditors, [mattyId]).length).toBe(0) - expect(leaveData?.editors.length).toBe(1) - }) -}) - describe('query tag', () => { test('tag recommended', async () => { const server = await testClient({ connections }) diff --git a/src/types/__test__/2/system.test.ts b/src/types/__test__/2/system.test.ts index 16a244aeb..42c44a562 100644 --- a/src/types/__test__/2/system.test.ts +++ b/src/types/__test__/2/system.test.ts @@ -33,9 +33,6 @@ const userDescription = `test-${Math.floor(Math.random() * 100)}` const user = { email: `test-${Math.floor(Math.random() * 100)}@matters.news`, - displayName: 'testUser', - password: '12345678', - codeId: '123', } let connections: Connections @@ -44,7 +41,7 @@ beforeAll(async () => { connections = await genConnections() const { id } = await putDraft({ draft }, connections) await publishArticle({ id }, connections) - await registerUser(user, connections) + await registerUser(user.email, connections) await updateUserDescription( { email: user.email, diff --git a/src/types/__test__/2/user.test.ts b/src/types/__test__/2/user.test.ts index ba089f496..3b1ec3325 100644 --- a/src/types/__test__/2/user.test.ts +++ b/src/types/__test__/2/user.test.ts @@ -29,11 +29,11 @@ import { import { defaultTestUser, getUserContext, - registerUser, testClient, updateUserState, genConnections, closeConnections, + registerUser, } from '../utils' let connections: Connections @@ -54,9 +54,9 @@ afterAll(async () => { await closeConnections(connections) }) -const USER_LOGIN = /* GraphQL */ ` - mutation UserLogin($input: UserLoginInput!) { - userLogin(input: $input) { +const EMAIL_LOGIN = /* GraphQL */ ` + mutation EmailLogin($input: EmailLoginInput!) { + emailLogin(input: $input) { auth token } @@ -410,17 +410,6 @@ const RESET_USER_LIKER_ID = /* GraphQL */ ` } ` -const RESET_USER_WALLET = /* GraphQL */ ` - mutation ResetWallet($input: ResetWalletInput!) { - resetWallet(input: $input) { - id - info { - ethAddress - } - } - } -` - describe('register and login functionarlities', () => { test('register user and retrieve info', async () => { const email = `test-${Math.floor(Math.random() * 100)}@matters.news` @@ -434,12 +423,10 @@ describe('register and login functionarlities', () => { }) const user = { email, - displayName: 'testUser', - password: 'Abcd1234', - codeId: code.uuid, + passwordOrCode: code.code, } - const registerResult = await registerUser(user, connections) - expect(_get(registerResult, 'data.userRegister.token')).toBeTruthy() + const registerResult = await registerUser(user.email, connections) + expect(_get(registerResult, 'data.emailLogin.token')).toBeTruthy() const context = await getUserContext({ email: user.email }, connections) const server = await testClient({ @@ -449,9 +436,7 @@ describe('register and login functionarlities', () => { const newUserResult = await server.executeOperation({ query: GET_VIEWER_INFO, }) - const displayName = _get(newUserResult, 'data.viewer.displayName') const info = newUserResult!.data!.viewer.info - expect(displayName).toBe(user.displayName) expect(info.email).toBe(user.email) const status = newUserResult!.data!.viewer.status @@ -464,8 +449,8 @@ describe('register and login functionarlities', () => { const server = await testClient({ connections }) const result = await server.executeOperation({ - query: USER_LOGIN, - variables: { input: { email, password } }, + query: EMAIL_LOGIN, + variables: { input: { email, passwordOrCode: password } }, }) expect(_get(result, 'errors.0.extensions.code')).toBe( 'USER_PASSWORD_INVALID' @@ -478,10 +463,10 @@ describe('register and login functionarlities', () => { const server = await testClient({ connections }) const result = await server.executeOperation({ - query: USER_LOGIN, - variables: { input: { email, password } }, + query: EMAIL_LOGIN, + variables: { input: { email, passwordOrCode: password } }, }) - expect(_get(result, 'data.userLogin.auth')).toBe(true) + expect(_get(result, 'data.emailLogin.auth')).toBe(true) }) test('retrive user info after login', async () => { @@ -904,19 +889,6 @@ describe('mutations on User object', () => { expect(adminReservedNameDisplayName).toEqual(RESERVED_NAMES[0]) }) - test('updateUserInfoUserName', async () => { - const server = await testClient({ isAuth: true, connections }) - - const userName2 = 'UPPERTest' - const { data } = await server.executeOperation({ - query: UPDATE_USER_INFO, - variables: { input: { userName: userName2 } }, - }) - expect(_get(data, 'updateUserInfo.userName')).toEqual( - userName2.toLowerCase() - ) - }) - test('updateUserInfoDescription', async () => { const description = 'foo bar' const server = await testClient({ @@ -1337,51 +1309,6 @@ describe('likecoin', () => { }) }) -describe('crypto wallet', () => { - test('reset wallet', async () => { - const server = await testClient({ - isAuth: true, - isAdmin: true, - connections, - }) - - // check if exists - const { data } = await server.executeOperation({ - query: GET_USER_BY_USERNAME, - variables: { input: { userName: 'test2' } }, - }) - - // reset - const resetResult = await server.executeOperation({ - query: RESET_USER_WALLET, - variables: { input: { id: _get(data, 'user.id') } }, - }) - expect(_get(resetResult, 'data.resetWallet.id')).toBe(_get(data, 'user.id')) - expect(_get(resetResult, 'data.resetWallet.info.ethAddress')).toBeFalsy() - }) - - test('reset wallet forbidden', async () => { - const server = await testClient({ - isAuth: true, - isAdmin: true, - connections, - }) - - // check if exists - const { data } = await server.executeOperation({ - query: GET_USER_BY_USERNAME, - variables: { input: { userName: 'test10' } }, - }) - - // reset - const resetResult = await server.executeOperation({ - query: RESET_USER_WALLET, - variables: { input: { id: _get(data, 'user.id') } }, - }) - expect(_get(resetResult, 'data.resetWallet.id')).toBeFalsy() - }) -}) - describe('update user state', () => { // archive user const id = toGlobalId({ type: NODE_TYPES.User, id: '1' }) diff --git a/src/types/__test__/utils.ts b/src/types/__test__/utils.ts index dfaa85823..4ce6ee4cb 100644 --- a/src/types/__test__/utils.ts +++ b/src/types/__test__/utils.ts @@ -2,7 +2,6 @@ import type { GQLPublishArticleInput, GQLPutDraftInput, GQLSetFeatureInput, - GQLUserRegisterInput, User, Connections, DataSources, @@ -42,6 +41,7 @@ import { import { genConnections, closeConnections } from 'connectors/__test__/utils' import schema from '../../schema' +import { VERIFICATION_CODE_STATUS } from 'common/enums' export { genConnections, closeConnections } @@ -326,23 +326,30 @@ export const putDraft = async ( return putDraftResult } -export const registerUser = async ( - user: GQLUserRegisterInput, - connections: Connections -) => { - const USER_REGISTER = ` - mutation UserRegister($input: UserRegisterInput!) { - userRegister(input: $input) { +export const registerUser = async (email: string, connections: Connections) => { + const EMAIL_REGISTER = ` + mutation EmailRegister($input: EmailLoginInput!) { + emailLogin(input: $input) { auth token } } ` + const userService = new UserService(connections) + const code = await userService.createVerificationCode({ + type: 'register', + email, + }) + await userService.markVerificationCodeAs({ + codeId: code.id, + status: VERIFICATION_CODE_STATUS.verified, + }) + const server = await testClient({ connections }) return server.executeOperation({ - query: USER_REGISTER, - variables: { input: user }, + query: EMAIL_REGISTER, + variables: { input: { email, passwordOrCode: code.code } }, }) } diff --git a/src/types/article.ts b/src/types/article.ts index 8f8eb5859..fc3d63550 100644 --- a/src/types/article.ts +++ b/src/types/article.ts @@ -34,21 +34,6 @@ export default /* GraphQL */ ` "Follow or unfollow tag." toggleFollowTag(input: ToggleItemInput!): Tag! @auth(mode: "${AUTH_MODE.oauth}", group: "${SCOPE_GROUP.level1}") @purgeCache(type: "${NODE_TYPES.Tag}") - "Create or update tag." - putTag(input: PutTagInput!): Tag! @auth(mode: "${AUTH_MODE.oauth}", group: "${SCOPE_GROUP.level1}") @purgeCache(type: "${NODE_TYPES.Tag}") - - "Update member, permission and othters of a tag." - updateTagSetting(input: UpdateTagSettingInput!): Tag! @auth(mode: "${AUTH_MODE.oauth}", group: "${SCOPE_GROUP.level1}") @purgeCache(type: "${NODE_TYPES.Tag}") - - "Add one tag to articles." - addArticlesTags(input: AddArticlesTagsInput!): Tag! @auth(mode: "${AUTH_MODE.oauth}", group: "${SCOPE_GROUP.level1}") @purgeCache(type: "${NODE_TYPES.Tag}") - - "Update articles' tag." - updateArticlesTags(input: UpdateArticlesTagsInput!): Tag! @auth(mode: "${AUTH_MODE.oauth}", group: "${SCOPE_GROUP.level1}") @purgeCache(type: "${NODE_TYPES.Tag}") - - "Delete one tag from articles" - deleteArticlesTags(input: DeleteArticlesTagsInput!): Tag! @auth(mode: "${AUTH_MODE.oauth}", group: "${SCOPE_GROUP.level1}") @purgeCache(type: "${NODE_TYPES.Tag}") - ############## # OSS # @@ -57,7 +42,6 @@ export default /* GraphQL */ ` updateArticleState(input: UpdateArticleStateInput!): Article! @auth(mode: "${AUTH_MODE.admin}") @purgeCache(type: "${NODE_TYPES.Article}") updateArticleSensitive(input: UpdateArticleSensitiveInput!): Article! @auth(mode: "${AUTH_MODE.admin}") @purgeCache(type: "${NODE_TYPES.Article}") - toggleTagRecommend(input: ToggleRecommendInput!): Tag! @auth(mode: "${AUTH_MODE.admin}") @purgeCache(type: "${NODE_TYPES.Tag}") deleteTags(input: DeleteTagsInput!): Boolean @complexity(value: 10, multipliers: ["input.ids"]) @auth(mode: "${AUTH_MODE.admin}") renameTag(input: RenameTagInput!): Tag! @auth(mode: "${AUTH_MODE.admin}") @purgeCache(type: "${NODE_TYPES.Tag}") mergeTags(input: MergeTagsInput!): Tag! @complexity(value: 10, multipliers: ["input.ids"]) @auth(mode: "${AUTH_MODE.admin}") @purgeCache(type: "${NODE_TYPES.Tag}") @@ -173,7 +157,6 @@ export default /* GraphQL */ ` subscribed: Boolean! "This value determines if this article is an author selected article or not." - sticky: Boolean! @deprecated(reason: "Use pinned instead") pinned: Boolean! "Translation of article title and content." @@ -191,15 +174,6 @@ export default /* GraphQL */ ` "Cumulative reading time in seconds" readTime: Float! - "Drafts linked to this article." - drafts: [Draft!] @logCache(type: "${NODE_TYPES.Draft}") @deprecated(reason: "Use Article.newestUnpublishedDraft or Article.newestPublishedDraft instead") - - "Newest unpublished draft linked to this article." - newestUnpublishedDraft: Draft @logCache(type: "${NODE_TYPES.Draft}") - - "Newest published draft linked to this article." - newestPublishedDraft: Draft! @logCache(type: "${NODE_TYPES.Draft}") - "Revision Count" revisionCount: Int! @@ -293,48 +267,18 @@ export default /* GraphQL */ ` articlesExcludeSpam(input: TagArticlesInput!): ArticleConnection! @complexity(multipliers: ["input.first"], value: 1) - "This value determines if this article is selected by this tag or not." - selected(input: TagSelectedInput!): Boolean! - "Time of this tag was created." createdAt: DateTime! - "Tag's cover link." - cover: String - - "Description of this tag." - description: String - - "Editors of this tag." - editors(input: TagEditorsInput): [User!] @logCache(type: "${NODE_TYPES.User}") - - "Creator of this tag." - creator: User @logCache(type: "${NODE_TYPES.User}") - - "Owner of this tag." - owner: User - "This value determines if current viewer is following or not." isFollower: Boolean - "Followers of this tag." - followers(input: ConnectionArgs!): UserConnection! @complexity(multipliers: ["input.first"], value: 1) - - "Participants of this tag." - participants(input: ConnectionArgs!): UserConnection! @complexity(multipliers: ["input.first"], value: 1) - "Tags recommended based on relations to current tag." recommended(input: ConnectionArgs!): TagConnection! @complexity(multipliers: ["input.first"], value: 1) - "This value determines if it is official." - isOfficial: Boolean - "Counts of this tag." numArticles: Int! @objectCache(maxAge: ${CACHE_TTL.MEDIUM}) ## cache for 1 hour numAuthors: Int! @objectCache(maxAge: ${CACHE_TTL.MEDIUM}) ## cache for 1 hour - ## numArticlesR3m: Int - ## numAuthorsR3m: Int - ############## # OSS # @@ -386,7 +330,6 @@ export default /* GraphQL */ ` type TagOSS @cacheControl(maxAge: ${CACHE_TTL.INSTANT}) { boost: Float! score: Float! - selected: Boolean! } type ArticleConnection implements Connection { @@ -442,8 +385,6 @@ export default /* GraphQL */ ` input EditArticleInput { id: ID! state: ArticleState - "deprecated, use pinned instead" - sticky: Boolean pinned: Boolean title: String summary: String @@ -489,6 +430,7 @@ export default /* GraphQL */ ` id: ID! } + input ToggleRecommendInput { id: ID! enabled: Boolean! @@ -519,36 +461,6 @@ export default /* GraphQL */ ` content: String! } - input PutTagInput { - id: ID - content: String - cover: ID - description: String - } - - input UpdateTagSettingInput { - id: ID! - type: UpdateTagSettingType! - editors: [ID!] - } - - input AddArticlesTagsInput { - id: ID! - articles: [ID!] - selected: Boolean - } - - input UpdateArticlesTagsInput { - id: ID! - articles: [ID!] - isSelected: Boolean! - } - - input DeleteArticlesTagsInput { - id: ID! - articles: [ID!] - } - enum TagArticlesSortBy { byHottestDesc byCreatedAtDesc @@ -562,16 +474,6 @@ export default /* GraphQL */ ` sortBy: TagArticlesSortBy = byCreatedAtDesc } - input TagSelectedInput { - id: ID - mediaHash: String - } - - input TagEditorsInput { - excludeAdmin: Boolean - excludeOwner: Boolean - } - input TransactionsReceivedByArgs { after: String first: Int @constraint(min: 0) @@ -620,12 +522,4 @@ export default /* GraphQL */ ` newest search } - - enum UpdateTagSettingType { - adopt - leave - add_editor - remove_editor - leave_editor - } ` diff --git a/src/types/user.ts b/src/types/user.ts index 8a3f5d5d5..a80130424 100644 --- a/src/types/user.ts +++ b/src/types/user.ts @@ -17,9 +17,6 @@ export default /* GraphQL */ ` "Reset user or payment password." resetPassword(input: ResetPasswordInput!): Boolean - "Change user email." - changeEmail(input: ChangeEmailInput!): User! @auth(mode: "${AUTH_MODE.oauth}", group: "${SCOPE_GROUP.level3}") @purgeCache(type: "${NODE_TYPES.User}") @deprecated(reason: "use 'setEmail' instead") - "Set user email." setEmail(input: SetEmailInput!): User! @auth(mode: "oauth") @purgeCache(type: "${NODE_TYPES.User}") @@ -29,12 +26,7 @@ export default /* GraphQL */ ` "Set user currency preference." setCurrency(input: SetCurrencyInput!): User! @auth(mode: "${AUTH_MODE.oauth}", group: "${SCOPE_GROUP.level1}") @purgeCache(type: "${NODE_TYPES.User}") - "Register user, can only be used on matters.{town,news} website." - userRegister(input: UserRegisterInput!): AuthResult! @deprecated(reason: "use 'emailLogin' instead") @rateLimit(limit:10, period:86400) - "Login user." - userLogin(input: UserLoginInput!): AuthResult! @deprecated(reason: "use 'emailLogin' instead") - emailLogin(input: EmailLoginInput!): AuthResult! "Get signing message." @@ -58,15 +50,9 @@ export default /* GraphQL */ ` "Remove a social login from current user." removeSocialLogin(input: RemoveSocialLoginInput!): User! @auth(mode: "oauth") @purgeCache(type: "${NODE_TYPES.User}") - "Reset crypto wallet." - resetWallet(input: ResetWalletInput!): User! @auth(mode: "${AUTH_MODE.admin}") @purgeCache(type: "${NODE_TYPES.User}") @deprecated(reason: "use 'removeWalletLogin' instead") - "Logout user." userLogout: Boolean! - "Generate or claim a Liker ID through LikeCoin" - generateLikerId: User! @auth(mode: "${AUTH_MODE.oauth}", group: "${SCOPE_GROUP.level3}") @purgeCache(type: "${NODE_TYPES.User}") @deprecated(reason: "No longer in use") - "Reset Liker ID" resetLikerId(input: ResetLikerIdInput!): User! @auth(mode: "${AUTH_MODE.admin}") @purgeCache(type: "${NODE_TYPES.User}") @@ -223,9 +209,6 @@ export default /* GraphQL */ ` "Activities based on user's following, sort by creation time." following(input: RecommendationFollowingInput!): FollowingActivityConnection! @complexity(multipliers: ["input.first"], value: 1) - "Articles recommended based on recently read article tags." - readTagsArticles(input: ConnectionArgs!): ArticleConnection! @complexity(multipliers: ["input.first"], value: 1) @deprecated(reason: "Merged into following") - "Global articles sort by publish time." newest(input: ConnectionArgs!): ArticleConnection! @complexity(multipliers: ["input.first"], value: 1) @cacheControl(maxAge: ${CACHE_TTL.PUBLIC_FEED_ARTICLE}) @@ -432,9 +415,6 @@ export default /* GraphQL */ ` "Total LIKE left in wallet." total: Float! @auth(mode: "${AUTH_MODE.oauth}") - - "Rate of LikeCoin/USD" - rateUSD: Float @objectCache(maxAge: ${CACHE_TTL.LONG}) @deprecated(reason: "No longer in use") } type UserOSS @cacheControl(maxAge: ${CACHE_TTL.INSTANT}) { @@ -764,10 +744,6 @@ export default /* GraphQL */ ` referralCode: String } - input UserLoginInput { - email: String! @constraint(format: "email") - password: String! - } input GenerateSigningMessageInput { address: String! @@ -786,12 +762,6 @@ export default /* GraphQL */ ` "nonce from generateSigningMessage" nonce: String! - "required for wallet register" - email: String @constraint(format: "email") @deprecated(reason: "No longer in use") - - "email verification code, required for wallet register" - codeId: ID @deprecated(reason: "No longer in use") - "used in register" language: UserLanguage @@ -813,7 +783,6 @@ export default /* GraphQL */ ` input UpdateUserInfoInput { displayName: String - userName: String @deprecated(reason: "use 'setUserName' instead") avatar: ID description: String language: UserLanguage @@ -911,9 +880,6 @@ export default /* GraphQL */ ` register email_verify email_otp - email_reset @deprecated(reason: "No longer in use") - email_reset_confirm @deprecated(reason: "No longer in use") - password_reset @deprecated(reason: "No longer in use") payment_password_reset }