diff --git a/packages/commonwealth/server/controllers/server_threads_controller.ts b/packages/commonwealth/server/controllers/server_threads_controller.ts index dda95d81e96..c7655283e15 100644 --- a/packages/commonwealth/server/controllers/server_threads_controller.ts +++ b/packages/commonwealth/server/controllers/server_threads_controller.ts @@ -47,11 +47,6 @@ import { GetBulkThreadsResult, __getBulkThreads, } from './server_threads_methods/get_bulk_threads'; -import { - ArchiveOrUnarchiveThreadOptions, - ArchiveOrUnarchiveThreadResult, - __archiveOrUnarchiveThread, -} from './server_threads_methods/archive_or_unarchive_thread'; /** * Implements methods related to threads @@ -121,10 +116,4 @@ export class ServerThreadsController { ): Promise { return __getBulkThreads.call(this, options); } - - async archiveOrUnarchiveThread( - options: ArchiveOrUnarchiveThreadOptions - ): Promise { - return __archiveOrUnarchiveThread.call(this, options); - } } diff --git a/packages/commonwealth/server/controllers/server_threads_methods/archive_or_unarchive_thread.ts b/packages/commonwealth/server/controllers/server_threads_methods/archive_or_unarchive_thread.ts deleted file mode 100644 index 7943675ebf9..00000000000 --- a/packages/commonwealth/server/controllers/server_threads_methods/archive_or_unarchive_thread.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { UserInstance } from '../../models/user'; -import { ServerThreadsController } from '../server_threads_controller'; -import { ThreadAttributes } from '../../models/thread'; -import { validateOwner } from '../../util/validateOwner'; -import { AppError } from '../../../../common-common/src/errors'; - -const Errors = { - ThreadNotFound: 'Thread not found', - InvalidPermissions: 'Invalid permissions', -}; - -export type ArchiveOrUnarchiveThreadOptions = { - user: UserInstance; - threadId: string; - shouldArchive: boolean; -}; - -export type ArchiveOrUnarchiveThreadResult = ThreadAttributes; - -export async function __archiveOrUnarchiveThread( - this: ServerThreadsController, - { user, threadId, shouldArchive }: ArchiveOrUnarchiveThreadOptions -): Promise { - const thread = await this.models.Thread.findOne({ - where: { - id: threadId, - }, - }); - if (!thread) { - throw new AppError(Errors.ThreadNotFound); - } - - const isOwner = await validateOwner({ - models: this.models, - user, - chainId: thread.chain, - entity: thread, - allowMod: true, - allowAdmin: true, - allowGodMode: true, - }); - - if (!isOwner) { - throw new AppError(Errors.InvalidPermissions); - } - - await thread.update({ - archived_at: shouldArchive - ? this.models.Sequelize.literal('CURRENT_TIMESTAMP') - : null, - }); - - // get thread with updated timestamp - const updatedThread = await this.models.Thread.findOne({ - where: { - id: thread.id, - }, - }); - - return updatedThread; -} diff --git a/packages/commonwealth/server/controllers/server_threads_methods/update_thread.ts b/packages/commonwealth/server/controllers/server_threads_methods/update_thread.ts index 7f503bc4494..8111346d82c 100644 --- a/packages/commonwealth/server/controllers/server_threads_methods/update_thread.ts +++ b/packages/commonwealth/server/controllers/server_threads_methods/update_thread.ts @@ -560,9 +560,12 @@ async function setThreadStage( // fetch available stages let customStages = []; try { - customStages = Array.from(JSON.parse(chain.custom_stages)) - .map((s) => s.toString()) - .filter((s) => s); + const chainStages = JSON.parse(chain.custom_stages); + if (Array.isArray(chainStages)) { + customStages = Array.from(chainStages) + .map((s) => s.toString()) + .filter((s) => s); + } if (customStages.length === 0) { customStages = [ 'discussion', @@ -658,8 +661,6 @@ async function updateThreadCollaborators( } } - const toUpdate: Partial = {}; - // add collaborators if (toAddUnique.length > 0) { const collaboratorAddresses = await models.Address.findAll({ @@ -698,9 +699,5 @@ async function updateThreadCollaborators( transaction, }); } - - if (Object.keys(toUpdate).length > 0) { - await thread.update(toUpdate, { transaction }); - } } } diff --git a/packages/commonwealth/server/routes/addEditors.ts b/packages/commonwealth/server/routes/addEditors.ts deleted file mode 100644 index 3e11172c882..00000000000 --- a/packages/commonwealth/server/routes/addEditors.ts +++ /dev/null @@ -1,169 +0,0 @@ -import { AppError } from 'common-common/src/errors'; -import { NotificationCategories, ProposalType } from 'common-common/src/types'; -import type { NextFunction, Request, Response } from 'express'; -import { body, validationResult } from 'express-validator'; -import { IThreadCollaborator } from 'models/Thread'; -import { Op } from 'sequelize'; -import { getThreadUrl } from '../../shared/utils'; -import type { DB } from '../models'; -import { failure } from '../types'; -import emitNotifications from '../util/emitNotifications'; - -export const Errors = { - InvalidThread: 'Must provide a valid thread_id', - InvalidEditor: 'Must provide valid addresses of community members', - InvalidEditorFormat: 'Editors attribute improperly formatted', - IncorrectOwner: 'Not owned by this user', -}; - -export const addEditorValidation = [ - body('thread_id').isNumeric().withMessage(Errors.InvalidThread), - body('editors').toArray(), -]; - -export interface AddEditorsBody { - thread_id: number; - editors: IThreadCollaborator[]; -} - -const addEditors = async ( - models: DB, - req: Request, - res: Response, - next: NextFunction -) => { - const errors = validationResult(req).array(); - if (errors.length !== 0) { - return failure(res.status(400), errors); - } - - const { thread_id, editors } = req.body as AddEditorsBody; - const editorChains = new Set(editors.map((e) => e.chain)); - const editorAddresses = new Set(editors.map((e) => e.address)); - - const chain = req.chain; - const author = req.address; - - const thread = await models.Thread.findOne({ - where: { - id: thread_id, - }, - }); - - if (!thread) return next(new AppError(Errors.InvalidThread)); - - const collaborators = await models.Address.findAll({ - where: { - chain: { - [Op.in]: Array.from(editorChains), - }, - address: { - [Op.in]: Array.from(editorAddresses), - }, - }, - include: [models.User], - }); - - if (!collaborators) { - return next(new AppError(Errors.InvalidEditor)); - } - - // Make sure that we query every collaborator provided. - if ( - collaborators.length !== Math.max(editorChains.size, editorAddresses.size) - ) { - return next(new AppError(Errors.InvalidEditor)); - } - - await Promise.all( - collaborators.map(async (collaborator) => { - const isMember = collaborator.chain === chain.id; - - if (!isMember) throw new AppError(Errors.InvalidEditor); - - await models.Collaboration.findOrCreate({ - where: { - thread_id: thread.id, - address_id: collaborator.id, - }, - }); - // auto-subscribe collaborator to comments & reactions - // findOrCreate to avoid duplicate subscriptions being created e.g. for - // same-account collaborators - await models.Subscription.findOrCreate({ - where: { - subscriber_id: collaborator.User.id, - category_id: NotificationCategories.NewComment, - object_id: String(thread.id), - thread_id: thread.id, - chain_id: thread.chain, - is_active: true, - }, - }); - await models.Subscription.findOrCreate({ - where: { - subscriber_id: collaborator.User.id, - category_id: NotificationCategories.NewReaction, - object_id: String(thread.id), - thread_id: thread.id, - chain_id: thread.chain, - is_active: true, - }, - }); - }) - ).catch((e) => { - return next(new AppError(e)); - }); - - await thread.save(); - - collaborators.forEach((collaborator) => { - if (!collaborator.User) return; // some Addresses may be missing users, e.g. if the user removed the address - - emitNotifications( - models, - NotificationCategories.NewCollaboration, - `user-${collaborator.User.id}`, - { - created_at: new Date(), - thread_id: +thread.id, - root_type: ProposalType.Thread, - root_title: thread.title, - comment_text: thread.body, - chain_id: thread.chain, - author_address: author.address, - author_chain: author.chain, - }, - { - user: author.address, - url: getThreadUrl(thread), - title: req.body.title, - bodyUrl: req.body.url, - chain: thread.chain, - body: thread.body, - }, - [author.address] - ); - }); - - const finalCollaborations = await models.Collaboration.findAll({ - where: { thread_id: thread.id }, - include: [ - { - model: models.Address, - as: 'Address', - }, - ], - }); - - return res.json({ - status: 'Success', - result: { - collaborators: finalCollaborations - .map((c) => c.toJSON()) - .map((c) => c.Address), - }, - }); -}; - -export default addEditors; diff --git a/packages/commonwealth/server/routes/deleteEditors.ts b/packages/commonwealth/server/routes/deleteEditors.ts deleted file mode 100644 index af687880d7e..00000000000 --- a/packages/commonwealth/server/routes/deleteEditors.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { IThreadCollaborator } from 'models/Thread'; -import { AppError } from 'common-common/src/errors'; -import type { NextFunction, Request, Response } from 'express'; -import { Op } from 'sequelize'; -import type { DB } from '../models'; - -export const Errors = { - InvalidThread: 'Must provide a valid thread_id', - InvalidEditor: 'Must provide valid addresses of existing editor', - InvalidEditorFormat: 'Editors attribute improperly formatted.', - IncorrectOwner: 'Not owned by this user', - InvalidAddress: 'Must provide editor address and chain', -}; - -interface DeleteEditorsBody { - thread_id: string; - editors: IThreadCollaborator[]; -} - -const deleteEditors = async ( - models: DB, - req: Request, - res: Response, - next: NextFunction -) => { - if (!req.body.thread_id) { - return next(new AppError(Errors.InvalidThread)); - } - - const { thread_id, editors } = req.body as DeleteEditorsBody; - - // Ensure editors is an array - if (!Array.isArray(editors)) { - return next(new AppError(Errors.InvalidEditorFormat)); - } - - const userOwnedAddressIds = (await req.user.getAddresses()) - .filter((addr) => !!addr.verified) - .map((addr) => addr.id); - const thread = await models.Thread.findOne({ - where: { - id: thread_id, - address_id: { [Op.in]: userOwnedAddressIds }, - }, - }); - if (!thread) return next(new AppError(Errors.InvalidThread)); - - await Promise.all( - editors.map(async (editor) => { - const address = await models.Address.findOne({ - where: { - chain: editor.chain, - address: editor.address, - }, - }); - const collaboration = await models.Collaboration.findOne({ - where: { - thread_id: thread.id, - address_id: address.id, - }, - }); - if (collaboration) { - await collaboration.destroy(); - } - }) - ); - - const finalCollaborations = await models.Collaboration.findAll({ - where: { thread_id: thread.id }, - include: [ - { - model: models.Address, - as: 'Address', - }, - ], - }); - - return res.json({ - status: 'Success', - result: { - collaborators: finalCollaborations - .map((c) => c.toJSON()) - .map((c) => c.Address), - }, - }); -}; - -export default deleteEditors; diff --git a/packages/commonwealth/server/routes/spam/markThreadAsSpam.ts b/packages/commonwealth/server/routes/spam/markThreadAsSpam.ts deleted file mode 100644 index 480f2e0cd8f..00000000000 --- a/packages/commonwealth/server/routes/spam/markThreadAsSpam.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { AppError } from 'common-common/src/errors'; -import type { Request, Response } from 'express'; -import type { DB } from '../../models'; -import { Op, Sequelize } from 'sequelize'; -import { success } from '../../types'; -import { findAllRoles } from '../../util/roles'; - -export const Errors = { - InvalidThreadId: 'Thread ID invalid', - NotLoggedIn: 'Not logged in', - ThreadNotFound: 'Could not find Thread', - NotAdmin: 'Not an admin', -}; - -export default async (models: DB, req: Request, res: Response) => { - const threadId = req.params.id; - if (!threadId) { - throw new AppError(Errors.InvalidThreadId); - } - - if (!req.user) { - throw new AppError(Errors.NotLoggedIn); - } - - const thread = await models.Thread.findOne({ - where: { - id: threadId, - }, - }); - if (!thread) { - throw new AppError(Errors.ThreadNotFound); - } - const userOwnedAddressIds = (await req.user.getAddresses()) - .filter((addr) => !!addr.verified) - .map((addr) => addr.id); - if (!userOwnedAddressIds.includes(thread.address_id) && !req.user.isAdmin) { - // is not author or site admin - const roles = await findAllRoles( - models, - { where: { address_id: { [Op.in]: userOwnedAddressIds } } }, - thread.chain, - ['admin', 'moderator'] - ); - const role = roles.find((r) => { - return r.chain_id === thread.chain; - }); - if (!role) { - throw new AppError(Errors.NotAdmin); - } - } - - await thread.update({ - marked_as_spam_at: Sequelize.literal('CURRENT_TIMESTAMP'), - }); - - // get thread with updated timestamp - const updatedThread = await models.Thread.findOne({ - where: { - id: thread.id, - }, - }); - - return success(res, updatedThread); -}; diff --git a/packages/commonwealth/server/routes/spam/unmarkThreadAsSpam.ts b/packages/commonwealth/server/routes/spam/unmarkThreadAsSpam.ts deleted file mode 100644 index fcc63c8855c..00000000000 --- a/packages/commonwealth/server/routes/spam/unmarkThreadAsSpam.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { AppError } from 'common-common/src/errors'; -import type { Request, Response } from 'express'; -import type { DB } from '../../models'; -import { Op } from 'sequelize'; -import { success } from '../../types'; -import { findAllRoles } from '../../util/roles'; - -export const Errors = { - InvalidThreadId: 'Thread ID invalid', - NotLoggedIn: 'Not logged in', - ThreadNotFound: 'Could not find Thread', - NotAdmin: 'Not an admin', -}; - -export default async (models: DB, req: Request, res: Response) => { - const threadId = req.params.id; - if (!threadId) { - throw new AppError(Errors.InvalidThreadId); - } - - if (!req.user) { - throw new AppError(Errors.NotLoggedIn); - } - - const thread = await models.Thread.findOne({ - where: { - id: threadId, - }, - }); - if (!thread) { - throw new AppError(Errors.ThreadNotFound); - } - const userOwnedAddressIds = (await req.user.getAddresses()) - .filter((addr) => !!addr.verified) - .map((addr) => addr.id); - if (!userOwnedAddressIds.includes(thread.address_id) && !req.user.isAdmin) { - // is not author or site admin - const roles = await findAllRoles( - models, - { where: { address_id: { [Op.in]: userOwnedAddressIds } } }, - thread.chain, - ['admin', 'moderator'] - ); - const role = roles.find((r) => { - return r.chain_id === thread.chain; - }); - if (!role) { - throw new AppError(Errors.NotAdmin); - } - } - - await thread.update({ - marked_as_spam_at: null, - }); - - // get thread with updated timestamp - const updatedThread = await models.Thread.findOne({ - where: { - id: thread.id, - }, - }); - - return success(res, updatedThread); -}; diff --git a/packages/commonwealth/server/routes/threads/archive_thread_handler.ts b/packages/commonwealth/server/routes/threads/archive_thread_handler.ts deleted file mode 100644 index 99407f2c99e..00000000000 --- a/packages/commonwealth/server/routes/threads/archive_thread_handler.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { AppError } from 'common-common/src/errors'; -import type { Request, Response } from 'express'; -import { success } from '../../types'; -import { ServerControllers } from 'server/routing/router'; - -export const Errors = { - InvalidThreadId: 'Thread ID invalid', - NotLoggedIn: 'Not logged in', -}; - -export const archiveThreadHandler = async ( - controllers: ServerControllers, - req: Request, - res: Response -) => { - const threadId = req.params.id; - if (!threadId) { - throw new AppError(Errors.InvalidThreadId); - } - if (!req.user) { - throw new AppError(Errors.NotLoggedIn); - } - - const updatedThread = await controllers.threads.archiveOrUnarchiveThread({ - user: req.user, - threadId, - shouldArchive: true, - }); - - return success(res, updatedThread); -}; diff --git a/packages/commonwealth/server/routes/threads/unarchive_thread_handler.ts b/packages/commonwealth/server/routes/threads/unarchive_thread_handler.ts deleted file mode 100644 index e3129e6adcf..00000000000 --- a/packages/commonwealth/server/routes/threads/unarchive_thread_handler.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { AppError } from 'common-common/src/errors'; -import type { Request, Response } from 'express'; -import { success } from '../../types'; -import { ServerControllers } from 'server/routing/router'; - -export const Errors = { - InvalidThreadId: 'Thread ID invalid', - NotLoggedIn: 'Not logged in', -}; - -export const unarchiveThreadHandler = async ( - controllers: ServerControllers, - req: Request, - res: Response -) => { - const threadId = req.params.id; - if (!threadId) { - throw new AppError(Errors.InvalidThreadId); - } - if (!req.user) { - throw new AppError(Errors.NotLoggedIn); - } - - const updatedThread = await controllers.threads.archiveOrUnarchiveThread({ - user: req.user, - threadId, - shouldArchive: false, - }); - - return success(res, updatedThread); -}; diff --git a/packages/commonwealth/server/routes/updateThreadPinned.ts b/packages/commonwealth/server/routes/updateThreadPinned.ts deleted file mode 100644 index 34a60482faa..00000000000 --- a/packages/commonwealth/server/routes/updateThreadPinned.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { AppError, ServerError } from 'common-common/src/errors'; -import type { NextFunction, Request, Response } from 'express'; -import { Op } from 'sequelize'; -import type { DB } from '../models'; -import { findAllRoles } from '../util/roles'; - -export const Errors = { - NotAdmin: 'Not an admin', - NoThread: 'Cannot find thread', -}; - -const updateThreadPinned = async ( - models: DB, - req: Request, - res: Response, - next: NextFunction -) => { - const { thread_id } = req.body; - if (!thread_id) return next(new AppError(Errors.NoThread)); - - try { - const thread = await models.Thread.findOne({ - where: { - id: thread_id, - }, - }); - const userOwnedAddressIds = (await req.user.getAddresses()) - .filter((addr) => !!addr.verified) - .map((addr) => addr.id); - - // only community mods and admin can pin - const roles = await findAllRoles( - models, - { where: { address_id: { [Op.in]: userOwnedAddressIds } } }, - thread.chain, - ['admin', 'moderator'] - ); - const role = roles.find((r) => { - return r.chain_id === thread.chain; - }); - if (!role) return next(new AppError(Errors.NotAdmin)); - - await thread.update({ pinned: !thread.pinned }); - - const finalThread = await models.Thread.findOne({ - where: { id: thread.id }, - include: [ - { - model: models.Address, - as: 'Address', - }, - { - model: models.Address, - // through: models.Collaboration, - as: 'collaborators', - }, - { - model: models.Topic, - as: 'topic', - }, - ], - }); - - return res.json({ status: 'Success', result: finalThread.toJSON() }); - } catch (e) { - return next(new ServerError(e)); - } -}; - -export default updateThreadPinned; diff --git a/packages/commonwealth/server/routes/updateThreadPrivacy.ts b/packages/commonwealth/server/routes/updateThreadPrivacy.ts deleted file mode 100644 index 009e57863bb..00000000000 --- a/packages/commonwealth/server/routes/updateThreadPrivacy.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { AppError, ServerError } from 'common-common/src/errors'; -import type { NextFunction, Request, Response } from 'express'; -import { Op, Sequelize } from 'sequelize'; -import type { DB } from '../models'; -import { findAllRoles } from '../util/roles'; - -export const Errors = { - NoThreadId: 'Must provide thread_id', - NoReadOnly: 'Must pass in read_only', - NoThread: 'Cannot find thread', - NotAdmin: 'Not an admin', -}; - -const updateThreadPrivacy = async ( - models: DB, - req: Request, - res: Response, - next: NextFunction -) => { - const { thread_id, read_only } = req.body; - if (!thread_id) return next(new AppError(Errors.NoThreadId)); - if (read_only == undefined) return next(new AppError(Errors.NoReadOnly)); - - try { - const thread = await models.Thread.findOne({ - where: { - id: thread_id, - }, - }); - if (!thread) return next(new AppError(Errors.NoThread)); - - const userOwnedAddressIds = (await req.user.getAddresses()) - .filter((addr) => !!addr.verified) - .map((addr) => addr.id); - - if (!userOwnedAddressIds.includes(thread.address_id)) { - // is not author - const roles = await findAllRoles( - models, - { where: { address_id: { [Op.in]: userOwnedAddressIds } } }, - thread.chain, - ['admin', 'moderator'] - ); - - const role = roles.find((r) => { - return r.chain_id === thread.chain; - }); - if (!role) return next(new AppError(Errors.NotAdmin)); - } - - const lockedAt = read_only ? Sequelize.literal('CURRENT_TIMESTAMP') : null; - await thread.update({ read_only, locked_at: lockedAt }); - - const finalThread = await models.Thread.findOne({ - where: { id: thread_id }, - include: [ - { - model: models.Address, - as: 'Address', - }, - { - model: models.Address, - // through: models.Collaboration, - as: 'collaborators', - }, - { - model: models.Topic, - as: 'topic', - }, - ], - }); - - return res.json({ status: 'Success', result: finalThread.toJSON() }); - } catch (e) { - return next(new ServerError(e)); - } -}; - -export default updateThreadPrivacy; diff --git a/packages/commonwealth/server/routes/updateThreadStage.ts b/packages/commonwealth/server/routes/updateThreadStage.ts deleted file mode 100644 index b00f97a5817..00000000000 --- a/packages/commonwealth/server/routes/updateThreadStage.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { AppError, ServerError } from 'common-common/src/errors'; -import type { NextFunction, Request, Response } from 'express'; -import { Op } from 'sequelize'; -import type { DB } from '../models'; -import { findAllRoles } from '../util/roles'; -import { MixpanelCommunityInteractionEvent } from '../../shared/analytics/types'; -import { ServerAnalyticsController } from '../controllers/server_analytics_controller'; - -export const Errors = { - NoThreadId: 'Must provide thread_id', - NoStage: 'Must pass in stage', - NoThread: 'Cannot find thread', - NotAdminOrOwner: 'Not an admin or owner of this thread', - InvalidStage: 'Please Select a Stage', - FailedToParse: 'Failed to parse custom stages', -}; - -const updateThreadStage = async ( - models: DB, - req: Request, - res: Response, - next: NextFunction -) => { - const { thread_id, stage } = req.body; - if (!thread_id) return next(new AppError(Errors.NoThreadId)); - if (!stage) return next(new AppError(Errors.NoStage)); - if (!req.user) return next(new AppError(Errors.NotAdminOrOwner)); - - try { - const thread = await models.Thread.findOne({ - where: { - id: thread_id, - }, - }); - if (!thread) return next(new AppError(Errors.NoThread)); - const userOwnedAddressIds = (await req.user.getAddresses()) - .filter((addr) => !!addr.verified) - .map((addr) => addr.id); - if (!userOwnedAddressIds.includes(thread.address_id) && !req.user.isAdmin) { - // is not author - const roles = await findAllRoles( - models, - { where: { address_id: { [Op.in]: userOwnedAddressIds } } }, - thread.chain, - ['admin', 'moderator'] - ); - const role = roles.find((r) => { - return r.chain_id === thread.chain; - }); - if (!role) return next(new AppError(Errors.NotAdminOrOwner)); - } - - // fetch available stages - let custom_stages = []; - const entity = await models.Chain.findOne({ where: { id: thread.chain } }); - try { - custom_stages = Array.from(JSON.parse(entity.custom_stages)) - .map((s) => s.toString()) - .filter((s) => s); - } catch (e) { - throw new AppError(Errors.FailedToParse); - } - - // validate stage - const availableStages = - custom_stages.length === 0 - ? ['discussion', 'proposal_in_review', 'voting', 'passed', 'failed'] - : custom_stages; - - if (availableStages.indexOf(stage) === -1) { - return next(new AppError(Errors.InvalidStage)); - } - - await thread.update({ stage }); - - const finalThread = await models.Thread.findOne({ - where: { id: thread_id }, - include: [ - { - model: models.Address, - as: 'Address', - }, - { - model: models.Address, - // through: models.Collaboration, - as: 'collaborators', - }, - { - model: models.Topic, - as: 'topic', - }, - ], - }); - - const serverAnalyticsController = new ServerAnalyticsController(); - serverAnalyticsController.track( - { - event: MixpanelCommunityInteractionEvent.UPDATE_STAGE, - }, - req - ); - - return res.json({ status: 'Success', result: finalThread.toJSON() }); - } catch (e) { - return next(new ServerError(e)); - } -}; - -export default updateThreadStage; diff --git a/packages/commonwealth/server/routes/updateThreadTopic.ts b/packages/commonwealth/server/routes/updateThreadTopic.ts deleted file mode 100644 index 8925bc9a07b..00000000000 --- a/packages/commonwealth/server/routes/updateThreadTopic.ts +++ /dev/null @@ -1,88 +0,0 @@ -/* eslint-disable quotes */ -import { AppError } from 'common-common/src/errors'; -import type { NextFunction, Response } from 'express'; -import { Op } from 'sequelize'; -import type { DB } from '../models'; -import { findAllRoles } from '../util/roles'; - -export enum UpdateTopicErrors { - NoUser = 'Not logged in', - NoThread = 'Must provide thread_id', - NoAddr = 'Must provide address', - NoTopic = 'Must provide topic_name or topic_id', - InvalidAddr = 'Invalid address', - NoPermission = `You do not have permission to edit post topic`, -} - -const updateThreadTopic = async ( - models: DB, - req, - res: Response, - next: NextFunction -) => { - if (!req.user) return next(new AppError(UpdateTopicErrors.NoUser)); - if (!req.body.thread_id) - return next(new AppError(UpdateTopicErrors.NoThread)); - if (!req.body.address) return next(new AppError(UpdateTopicErrors.NoAddr)); - if (!req.body.topic_name && !req.body.topic_id) - return next(new AppError(UpdateTopicErrors.NoTopic)); - - const userAddresses = await req.user.getAddresses(); - const userAddressIds = userAddresses - .filter((a) => !!a.verified) - .map((a) => a.id); - - if (userAddressIds.length === 0) { - return next(new AppError(UpdateTopicErrors.InvalidAddr)); - } - - const thread = await models.Thread.findOne({ - where: { - id: req.body.thread_id, - }, - }); - - const roles: any[] = await findAllRoles( - models, - { where: { address_id: { [Op.in]: userAddressIds } } }, - thread.chain, - ['admin', 'moderator'] - ); - - const isAdminOrMod = roles.length > 0; - - if (!isAdminOrMod) { - const isAuthor = await models.Thread.findOne({ - where: { - id: req.body.thread_id, - address_id: { [Op.in]: userAddressIds }, - }, - }); - - if (!isAuthor) { - return next(new AppError(UpdateTopicErrors.NoPermission)); - } - } - - // remove deleted topics - let newTopic; - if (req.body.topic_id) { - thread.topic_id = req.body.topic_id; - await thread.save(); - newTopic = await models.Topic.findOne({ - where: { id: req.body.topic_id }, - }); - } else { - [newTopic] = await models.Topic.findOrCreate({ - where: { - name: req.body.topic_name, - chain_id: thread.chain, - }, - }); - thread.topic_id = newTopic.id; - await thread.save(); - } - return res.json({ status: 'Success', result: newTopic.toJSON() }); -}; - -export default updateThreadTopic; diff --git a/packages/commonwealth/server/routing/router.ts b/packages/commonwealth/server/routing/router.ts index 9ea256f237e..232cc312958 100644 --- a/packages/commonwealth/server/routing/router.ts +++ b/packages/commonwealth/server/routing/router.ts @@ -72,13 +72,8 @@ import getUploadSignature from '../routes/getUploadSignature'; import createPoll from '../routes/createPoll'; import getPolls from '../routes/getPolls'; import deletePoll from '../routes/deletePoll'; -import updateThreadStage from '../routes/updateThreadStage'; -import updateThreadPrivacy from '../routes/updateThreadPrivacy'; -import updateThreadPinned from '../routes/updateThreadPinned'; import updateVote from '../routes/updateVote'; import viewVotes from '../routes/viewVotes'; -import addEditors, { addEditorValidation } from '../routes/addEditors'; -import deleteEditors from '../routes/deleteEditors'; import deleteChain from '../routes/deleteChain'; import updateChain from '../routes/updateChain'; import updateProfileNew from '../routes/updateNewProfile'; @@ -86,7 +81,6 @@ import writeUserSetting from '../routes/writeUserSetting'; import sendFeedback from '../routes/sendFeedback'; import logout from '../routes/logout'; import createTopic from '../routes/createTopic'; -import updateThreadTopic from '../routes/updateThreadTopic'; import updateTopic from '../routes/topics/updateTopic'; import orderTopics from '../routes/orderTopics'; import editTopic from '../routes/editTopic'; @@ -155,9 +149,7 @@ import * as controllers from '../controller'; import addThreadLink from '../routes/linking/addThreadLinks'; import deleteThreadLinks from '../routes/linking/deleteThreadLinks'; import getLinks from '../routes/linking/getLinks'; -import markThreadAsSpam from '../routes/spam/markThreadAsSpam'; import markCommentAsSpam from '../routes/spam/markCommentAsSpam'; -import unmarkThreadAsSpam from '../routes/spam/unmarkThreadAsSpam'; import unmarkCommentAsSpam from '../routes/spam/unmarkCommentAsSpam'; import { ServerThreadsController } from '../controllers/server_threads_controller'; @@ -176,8 +168,6 @@ import { createThreadCommentHandler } from '../routes/threads/create_thread_comm import { updateCommentHandler } from '../routes/comments/update_comment_handler'; import { deleteCommentHandler } from '../routes/comments/delete_comment_handler'; import { getThreadsHandler } from '../routes/threads/get_threads_handler'; -import { archiveThreadHandler } from '../routes/threads/archive_thread_handler'; -import { unarchiveThreadHandler } from '../routes/threads/unarchive_thread_handler'; import { deleteThreadHandler } from '../routes/threads/delete_thread_handler'; import { deleteBotThreadHandler } from '../routes/threads/delete_thread_bot_handler'; import { deleteBotCommentHandler } from '../routes/comments/delete_comment_bot_handler'; @@ -477,27 +467,6 @@ function setupRouter( passport.authenticate('jwt', { session: false }), deletePoll.bind(this, models) ); - registerRoute( - router, - 'post', - '/updateThreadStage', - passport.authenticate('jwt', { session: false }), - updateThreadStage.bind(this, models) - ); - registerRoute( - router, - 'post', - '/updateThreadPrivacy', - passport.authenticate('jwt', { session: false }), - updateThreadPrivacy.bind(this, models) - ); - registerRoute( - router, - 'post', - '/updateThreadPinned', - passport.authenticate('jwt', { session: false }), - updateThreadPinned.bind(this, models) - ); registerRoute( router, 'post', @@ -597,25 +566,6 @@ function setupRouter( deleteCommunityContractTemplateMetadata.bind(this, models) ); - registerRoute( - router, - 'post', - '/addEditors', - passport.authenticate('jwt', { session: false }), - databaseValidationService.validateAuthor, - databaseValidationService.validateChain, - addEditorValidation, - addEditors.bind(this, models) - ); - registerRoute( - router, - 'post', - '/deleteEditors', - passport.authenticate('jwt', { session: false }), - databaseValidationService.validateAuthor, - databaseValidationService.validateChain, - deleteEditors.bind(this, models) - ); registerRoute( router, 'delete', @@ -732,14 +682,6 @@ function setupRouter( databaseValidationService.validateChain, createTopic.bind(this, models) ); - - registerRoute( - router, - 'post', - '/updateThreadTopic', - passport.authenticate('jwt', { session: false }), - updateThreadTopic.bind(this, models) - ); registerRoute( router, 'post', @@ -1178,20 +1120,6 @@ function setupRouter( ); // thread spam - registerRoute( - router, - 'put', - '/threads/:id/spam', - passport.authenticate('jwt', { session: false }), - markThreadAsSpam.bind(this, models) - ); - registerRoute( - router, - 'delete', - '/threads/:id/spam', - passport.authenticate('jwt', { session: false }), - unmarkThreadAsSpam.bind(this, models) - ); registerRoute( router, 'put', @@ -1207,22 +1135,6 @@ function setupRouter( unmarkCommentAsSpam.bind(this, models) ); - // thread archive - registerRoute( - router, - 'put', - '/threads/:id/archive', - passport.authenticate('jwt', { session: false }), - archiveThreadHandler.bind(this, serverControllers) - ); - registerRoute( - router, - 'delete', - '/threads/:id/archive', - passport.authenticate('jwt', { session: false }), - unarchiveThreadHandler.bind(this, serverControllers) - ); - // login registerRoute(router, 'post', '/login', startEmailLogin.bind(this, models)); registerRoute( diff --git a/packages/commonwealth/test/integration/api/addEditors.spec.ts b/packages/commonwealth/test/integration/api/addEditors.spec.ts deleted file mode 100644 index 4235717125f..00000000000 --- a/packages/commonwealth/test/integration/api/addEditors.spec.ts +++ /dev/null @@ -1,90 +0,0 @@ -import chai from 'chai'; -import chaiHttp from 'chai-http'; -import jwt from 'jsonwebtoken'; -import app from '../../../server-test'; -import { JWT_SECRET } from '../../../server/config'; -import { Errors } from '../../../server/routes/addEditors'; -import { post } from './external/appHook.spec'; -import { - testAddresses, - testThreads, - testUsers, -} from './external/dbEntityHooks.spec'; - -chai.use(chaiHttp); -const { expect } = chai; - -describe('addEditors Integration Tests', () => { - let jwtToken; - - beforeEach(() => { - jwtToken = jwt.sign( - { id: testUsers[0].id, email: testUsers[0].email }, - JWT_SECRET - ); - }); - - it('should return an error response if there are validation errors', async () => { - const invalidRequest = { - jwt: jwtToken, - author_chain: testAddresses[0].chain, - address: testAddresses[0].address, - chain: testAddresses[0].chain, - thread_id: -1, - editors: [{ chain: 'invalid', address: testAddresses[0].address }], - }; - - const response = await post('/api/addEditors', invalidRequest, true, app); - - response.should.have.status(400); - chai.assert.equal(response.error, Errors.InvalidEditor); - }); - - it('should return an error response if the thread does not exist', async () => { - const nonExistentThreadId = '-12345678'; - - const response = await post( - '/api/addEditors', - { - jwt: jwtToken, - author_chain: testAddresses[0].chain, - address: testAddresses[0].address, - chain: testAddresses[0].chain, - thread_id: nonExistentThreadId, - editors: [ - { chain: testAddresses[0].chain, address: testAddresses[0].address }, - ], - }, - true, - app - ); - - response.should.have.status(400); - chai.assert.equal(response.error, Errors.InvalidThread); - }); - - it('should add editors and return a success response', async () => { - const testAddress = testAddresses.filter( - (a) => a.id === testThreads[0].address_id - )[0]; - const validRequest = { - address: testAddresses[0].address, - author_chain: testAddresses[0].chain, - chain: testAddresses[0].chain, - editors: [{ address: testAddress.address, chain: testAddress.chain }], - jwt: jwtToken, - thread_id: testThreads[0].id, - }; - const response = await post('/api/addEditors', validRequest, true, app); - - expect(response.status).to.equal('Success'); - expect(response.result.collaborators).to.haveOwnProperty('length'); - expect(response.result.collaborators.length).equal(2); - - const findCollab = (id) => { - return response.result.collaborators.find((c) => c.id == id); - }; - expect(findCollab(-1)).to.exist; - expect(findCollab(-2)).to.exist; - }); -}); diff --git a/packages/commonwealth/test/integration/api/thread.update.spec.ts b/packages/commonwealth/test/integration/api/thread.update.spec.ts new file mode 100644 index 00000000000..18d500bad97 --- /dev/null +++ b/packages/commonwealth/test/integration/api/thread.update.spec.ts @@ -0,0 +1,197 @@ +import chai from 'chai'; +import chaiHttp from 'chai-http'; +import 'chai/register-should'; +import jwt from 'jsonwebtoken'; +import { JWT_SECRET } from 'server/config'; +import app, { resetDatabase } from '../../../server-test'; +import * as modelUtils from 'test/util/modelUtils'; + +chai.use(chaiHttp); +const { expect } = chai; + +describe('Thread Patch Update', () => { + const chain = 'ethereum'; + + let adminJWT; + let adminUserId; + let adminAddress; + let adminAddressId; + + let userJWT; + let userId; + let userAddress; + let userAddressId; + + before(async () => { + await resetDatabase(); + const adminRes = await modelUtils.createAndVerifyAddress({ chain }); + { + adminAddress = adminRes.address; + adminUserId = adminRes.user_id; + adminAddressId = adminRes.address_id; + adminJWT = jwt.sign( + { id: adminRes.user_id, email: adminRes.email }, + JWT_SECRET + ); + const isAdmin = await modelUtils.updateRole({ + address_id: adminRes.address_id, + chainOrCommObj: { chain_id: chain }, + role: 'admin', + }); + expect(adminAddress).to.not.be.null; + expect(adminJWT).to.not.be.null; + expect(isAdmin).to.not.be.null; + } + + const userRes = await modelUtils.createAndVerifyAddress({ chain }); + { + userAddress = userRes.address; + userId = userRes.user_id; + userAddressId = userRes.address_id; + userJWT = jwt.sign( + { id: userRes.user_id, email: userRes.email }, + JWT_SECRET + ); + expect(userAddress).to.not.be.null; + expect(userJWT).to.not.be.null; + } + }); + + describe('update thread', () => { + it('should update thread attributes as owner', async () => { + const { result: thread } = await modelUtils.createThread({ + chainId: 'ethereum', + address: userAddress, + jwt: userJWT, + title: 'test1', + body: 'body1', + kind: 'discussion', + stage: 'discussion', + topicName: 't1', + topicId: undefined, + }); + + const res = await chai.request + .agent(app) + .patch(`/api/threads/${thread.id}`) + .set('Accept', 'application/json') + .send({ + author_chain: thread.chain, + chain: thread.chain, + address: userAddress, + jwt: userJWT, + title: 'newTitle', + body: 'newBody', + stage: 'voting', + locked: true, + archived: true, + topicName: 'newTopic', + }); + + expect(res.status).to.equal(200); + expect(res.body.result).to.contain({ + id: thread.id, + chain: 'ethereum', + title: 'newTitle', + body: 'newBody', + stage: 'voting', + }); + expect(res.body.result.topic.name).to.equal('newTopic'); + expect(res.body.result.locked).to.not.be.null; + expect(res.body.result.archived).to.not.be.null; + }); + + it('should not allow non-admin to set pinned or spam', async () => { + const { result: thread } = await modelUtils.createThread({ + chainId: 'ethereum', + address: userAddress, + jwt: userJWT, + title: 'test2', + body: 'body2', + kind: 'discussion', + stage: 'discussion', + topicName: 't2', + topicId: undefined, + }); + + { + const res = await chai.request + .agent(app) + .patch(`/api/threads/${thread.id}`) + .set('Accept', 'application/json') + .send({ + author_chain: thread.chain, + chain: thread.chain, + address: userAddress, + jwt: userJWT, + pinned: true, + }); + expect(res.status).to.equal(400); + } + + { + const res = await chai.request + .agent(app) + .patch(`/api/threads/${thread.id}`) + .set('Accept', 'application/json') + .send({ + author_chain: thread.chain, + chain: thread.chain, + address: userAddress, + jwt: userJWT, + spam: true, + }); + expect(res.status).to.equal(400); + } + }); + + it('should allow admin to set pinned or spam', async () => { + // non-admin creates thread + const { result: thread } = await modelUtils.createThread({ + chainId: 'ethereum', + address: userAddress, + jwt: userJWT, + title: 'test2', + body: 'body2', + kind: 'discussion', + stage: 'discussion', + topicName: 't2', + topicId: undefined, + }); + + // admin sets thread as pinned + { + const res = await chai.request + .agent(app) + .patch(`/api/threads/${thread.id}`) + .set('Accept', 'application/json') + .send({ + author_chain: thread.chain, + chain: thread.chain, + address: adminAddress, + jwt: adminJWT, + pinned: true, + }); + expect(res.status).to.equal(200); + expect(res.body.result.pinned).to.be.true; + } + + // admin sets thread as spam + { + const res = await chai.request + .agent(app) + .patch(`/api/threads/${thread.id}`) + .set('Accept', 'application/json') + .send({ + author_chain: thread.chain, + chain: thread.chain, + address: adminAddress, + jwt: adminJWT, + spam: true, + }); + expect(res.status).to.equal(200); + expect(!!res.body.result.marked_as_spam_at).to.be.true; + } + }); + }); +}); diff --git a/packages/commonwealth/test/integration/api/threads.spec.ts b/packages/commonwealth/test/integration/api/threads.spec.ts index 96d932d8c51..cc51a81839b 100644 --- a/packages/commonwealth/test/integration/api/threads.spec.ts +++ b/packages/commonwealth/test/integration/api/threads.spec.ts @@ -10,8 +10,8 @@ import { Errors as CreateCommentErrors } from 'server/routes/threads/create_thre import { Errors as CreateThreadErrors } from 'server/controllers/server_threads_methods/create_thread'; import { Errors as EditThreadErrors } from 'server/controllers/server_threads_methods/update_thread'; import { Errors as EditThreadHandlerErrors } from 'server/routes/threads/update_thread_handler'; -import { Errors as updateThreadPinnedErrors } from 'server/routes/updateThreadPinned'; -import { Errors as updateThreadPrivacyErrors } from 'server/routes/updateThreadPrivacy'; +import { Errors as updateThreadPinnedErrors } from 'server/routes/threads/update_thread_handler'; +import { Errors as updateThreadPrivacyErrors } from 'server/routes/threads/update_thread_handler'; import { Errors as ViewCountErrors } from 'server/routes/viewCount'; import sleep from 'sleep-promise'; import app, { resetDatabase } from '../../../server-test'; @@ -663,70 +663,6 @@ describe('Thread Tests', () => { expect(res.status).to.be.equal(200); expect(res.body.result.read_only).to.be.false; }); - - it('should fail without read_only', async () => { - const res = await chai - .request(app) - .post('/api/updateThreadPrivacy') - .set('Accept', 'application/json') - .send({ - thread_id: tempThread.id, - jwt: adminJWT, - }); - expect(res.status).to.be.equal(400); - expect(res.body.error).to.be.equal( - updateThreadPrivacyErrors.NoReadOnly - ); - }); - - it('should fail without thread_id', async () => { - const res = await chai - .request(app) - .post('/api/updateThreadPrivacy') - .set('Accept', 'application/json') - .send({ - read_only: 'true', - jwt: adminJWT, - }); - expect(res.status).to.be.equal(400); - expect(res.body.error).to.be.equal( - updateThreadPrivacyErrors.NoThreadId - ); - }); - - it('should fail with an invalid thread_id', async () => { - const res = await chai - .request(app) - .post('/api/updateThreadPrivacy') - .set('Accept', 'application/json') - .send({ - thread_id: 123458, - read_only: 'true', - jwt: adminJWT, - }); - expect(res.status).to.be.equal(400); - expect(res.body.error).to.be.equal(updateThreadPrivacyErrors.NoThread); - }); - - it('should fail if not an admin or author', async () => { - // create new user + jwt - const res = await modelUtils.createAndVerifyAddress({ chain }); - const newUserJWT = jwt.sign( - { id: res.user_id, email: res.email }, - JWT_SECRET - ); - const res2 = await chai - .request(app) - .post('/api/updateThreadPrivacy') - .set('Accept', 'application/json') - .send({ - thread_id: tempThread.id, - read_only: 'true', - jwt: newUserJWT, - }); - expect(res2.status).to.be.equal(400); - expect(res2.body.error).to.be.equal(updateThreadPrivacyErrors.NotAdmin); - }); }); describe('/comments/:id', () => { @@ -926,24 +862,6 @@ describe('Thread Tests', () => { expect(res2.body.status).to.be.equal('Success'); expect(res2.body.result.pinned).to.be.false; }); - - it('admin fails to toggle without thread', async () => { - const res2 = await chai - .request(app) - .post('/api/updateThreadPinned') - .set('Accept', 'application/json') - .send({ jwt: adminJWT }); - expect(res2.body.error).to.be.equal(updateThreadPinnedErrors.NoThread); - }); - - it('user fails to toggle pin', async () => { - const res2 = await chai - .request(app) - .post('/api/updateThreadPinned') - .set('Accept', 'application/json') - .send({ thread_id: pinThread, jwt: userJWT }); - expect(res2.body.error).to.be.equal(updateThreadPinnedErrors.NotAdmin); - }); }); }); }); diff --git a/packages/commonwealth/test/integration/api/topics.spec.ts b/packages/commonwealth/test/integration/api/topics.spec.ts index 00837526a9e..b836124b147 100644 --- a/packages/commonwealth/test/integration/api/topics.spec.ts +++ b/packages/commonwealth/test/integration/api/topics.spec.ts @@ -95,90 +95,4 @@ describe('Topic Tests', () => { expect(res.body.result.length).to.be.equal(1); }); }); - - describe('Update Topics', () => { - let thread; - - before(async () => { - const res = await modelUtils.createAndVerifyAddress({ chain }); - adminAddress = res.address; - adminJWT = jwt.sign({ id: res.user_id, email: res.email }, JWT_SECRET); - const isAdmin = await modelUtils.updateRole({ - address_id: res.address_id, - chainOrCommObj: { chain_id: chain }, - role: 'admin', - }); - expect(adminAddress).to.not.be.null; - expect(adminJWT).to.not.be.null; - expect(isAdmin).to.not.be.null; - - const res2 = await modelUtils.createAndVerifyAddress({ chain }); - userAddress = res2.address; - userJWT = jwt.sign({ id: res2.user_id, email: res2.email }, JWT_SECRET); - expect(userAddress).to.not.be.null; - expect(userJWT).to.not.be.null; - - const res3 = await modelUtils.createThread({ - chainId: chain, - address: adminAddress, - jwt: adminJWT, - title, - body, - topicName, - topicId, - kind, - stage, - }); - thread = res3.result; - }); - - it('Should fail to update thread without a topic name', async () => { - const res = await chai - .request(app) - .post('/api/updateThreadTopic') - .set('Accept', 'application/json') - .send({ - jwt: adminJWT, - thread_id: thread.id, - address: adminAddress, - topic_name: undefined, - }); - expect(res.body).to.not.be.null; - expect(res.body.status).to.not.be.equal('Success'); - expect(res.body.result).to.not.be.null; - }); - - it('Should successfully add topic to thread with admin account', async () => { - const res = await chai - .request(app) - .post('/api/updateThreadTopic') - .set('Accept', 'application/json') - .send({ - jwt: adminJWT, - thread_id: thread.id, - address: adminAddress, - topic_name: topicName, - }); - expect(res.body).to.not.be.null; - expect(res.body.status).to.be.equal('Success'); - expect(res.body.result).to.not.be.null; - expect(res.body.result.name).to.be.equal(topicName); - }); - - it('Should fail to add topic to thread with non-admin account', async () => { - const res = await chai - .request(app) - .post('/api/updateThreadTopic') - .set('Accept', 'application/json') - .send({ - jwt: userJWT, - thread_id: thread.id, - address: userAddress, - topic_name: topicName, - }); - expect(res.body).to.not.be.null; - expect(res.body.status).to.not.be.equal('Success'); - expect(res.body.result).to.not.be.null; - }); - }); }); diff --git a/packages/commonwealth/test/integration/api/updateThreadPrivacy.spec.ts b/packages/commonwealth/test/integration/api/updateThreadPrivacy.spec.ts deleted file mode 100644 index 1b749dd0621..00000000000 --- a/packages/commonwealth/test/integration/api/updateThreadPrivacy.spec.ts +++ /dev/null @@ -1,137 +0,0 @@ -import chai from 'chai'; -import chaiHttp from 'chai-http'; -import jwt from 'jsonwebtoken'; -import app from '../../../server-test'; -import { JWT_SECRET } from '../../../server/config'; -import models from '../../../server/database'; -import { Errors } from '../../../server/routes/updateThreadPrivacy'; -import { post } from './external/appHook.spec'; -import { - testAddresses, - testThreads, - testUsers, -} from './external/dbEntityHooks.spec'; - -chai.use(chaiHttp); - -describe('updateThreadPrivacy Integration Tests', () => { - let jwtTokenUser1; - - beforeEach(() => { - jwtTokenUser1 = jwt.sign( - { id: testUsers[0].id, email: testUsers[0].email }, - JWT_SECRET - ); - }); - - it('should return an error response if there is an invalid threadId specified', async () => { - const invalidRequest = { - jwt: jwtTokenUser1, - author_chain: testAddresses[0].chain, - chain: testAddresses[0].chain, - thread_id: false, - read_only: false, - }; - - const response = await post( - '/api/updateThreadPrivacy', - invalidRequest, - true, - app - ); - - response.should.have.status(400); - chai.assert.equal(response.error, Errors.NoThreadId); - }); - - it('should return an error response if an invalid read only is specified', async () => { - const invalidRequest = { - jwt: jwtTokenUser1, - author_chain: testAddresses[0].chain, - chain: testAddresses[0].chain, - thread_id: testThreads[0].id, - }; - - const response = await post( - '/api/updateThreadPrivacy', - invalidRequest, - true, - app - ); - - response.should.have.status(400); - chai.assert.equal(response.error, Errors.NoReadOnly); - }); - - it('should return an error response if the user does not own the thread or is not an admin', async () => { - const invalidRequest = { - jwt: jwtTokenUser1, - author_chain: testAddresses[0].chain, - chain: testAddresses[0].chain, - thread_id: testThreads[3].id, - read_only: false, - }; - - const response = await post( - '/api/updateThreadPrivacy', - invalidRequest, - true, - app - ); - - response.should.have.status(400); - chai.assert.equal(response.error, Errors.NotAdmin); - }); - - it('should lock a thread', async () => { - const validRequest = { - jwt: jwtTokenUser1, - author_chain: testAddresses[0].chain, - chain: testAddresses[0].chain, - thread_id: testThreads[0].id, - read_only: true, - }; - - chai.assert.equal(testThreads[0].read_only, false); - - const response = await post( - '/api/updateThreadPrivacy', - validRequest, - false, - app - ); - - chai.assert.equal(response.status, 'Success'); - const thread = await models.Thread.findOne({ - where: { id: testThreads[0].id }, - }); - - chai.assert.equal(thread.read_only, true); - }); - - it('should unlock a thread', async () => { - const validRequest = { - jwt: jwtTokenUser1, - author_chain: testAddresses[0].chain, - chain: testAddresses[0].chain, - thread_id: testThreads[0].id, - read_only: false, - }; - - const response = await post( - '/api/updateThreadPrivacy', - validRequest, - false, - app - ); - - chai.assert.equal(response.status, 'Success'); - const thread = await models.Thread.findOne({ - where: { - id: testThreads[0].id, - }, - }); - - chai.assert.equal(thread.read_only, false); - }); -}); diff --git a/packages/commonwealth/test/integration/api/updateThreadStage.spec.ts b/packages/commonwealth/test/integration/api/updateThreadStage.spec.ts deleted file mode 100644 index f8fcbc618c8..00000000000 --- a/packages/commonwealth/test/integration/api/updateThreadStage.spec.ts +++ /dev/null @@ -1,153 +0,0 @@ -import chai from 'chai'; -import chaiHttp from 'chai-http'; -import jwt from 'jsonwebtoken'; -import app, { resetDatabase } from '../../../server-test'; -import { JWT_SECRET } from '../../../server/config'; -import models from '../../../server/database'; -import { Errors } from '../../../server/routes/updateThreadStage'; -import { post } from './external/appHook.spec'; -import { - testAddresses, - testThreads, - testUsers, -} from './external/dbEntityHooks.spec'; - -chai.use(chaiHttp); - -describe('updateThreadStage Integration Tests', () => { - let jwtTokenUser1; - - beforeEach(() => { - jwtTokenUser1 = jwt.sign( - { id: testUsers[0].id, email: testUsers[0].email }, - JWT_SECRET - ); - }); - - it('should return an error response if there is an invalid threadId specified', async () => { - const invalidRequest = { - jwt: jwtTokenUser1, - author_chain: testAddresses[0].chain, - chain: testAddresses[0].chain, - thread_id: false, - stage: testThreads[0].stage, - }; - - const response = await post( - '/api/updateThreadStage', - invalidRequest, - true, - app - ); - - response.should.have.status(400); - chai.assert.equal(response.error, Errors.NoThreadId); - }); - - it('should return an error response if an invalid stage is specified', async () => { - const invalidRequest = { - jwt: jwtTokenUser1, - author_chain: testAddresses[0].chain, - chain: testAddresses[0].chain, - thread_id: testThreads[0].id, - stage: false, - }; - - const response = await post( - '/api/updateThreadStage', - invalidRequest, - true, - app - ); - - response.should.have.status(400); - chai.assert.equal(response.error, Errors.NoStage); - }); - - it('should return an error response if it cannot find thead specified', async () => { - const invalidRequest = { - jwt: jwtTokenUser1, - author_chain: testAddresses[0].chain, - chain: testAddresses[0].chain, - thread_id: -123456, - stage: 'discussion', - }; - - const response = await post( - '/api/updateThreadStage', - invalidRequest, - true, - app - ); - - response.should.have.status(400); - chai.assert.equal(response.error, Errors.NoThread); - }); - - it('should return an error response if not enough permission to update stage (not admin or owner)', async () => { - await models.Address.update( - { - role: 'member', - }, - { - where: { - id: testAddresses[0].id, - }, - } - ); - await models.User.update( - { - isAdmin: false, - }, - { - where: { - id: testUsers[0].id, - }, - } - ); - - const invalidRequest = { - jwt: jwtTokenUser1, - author_chain: testAddresses[0].chain, - chain: testAddresses[0].chain, - thread_id: testThreads[3].id, - stage: 'discussion', - }; - - const response = await post( - '/api/updateThreadStage', - invalidRequest, - true, - app - ); - - response.should.have.status(400); - chai.assert.equal(response.error, Errors.NotAdminOrOwner); - }); - - it('should update thread stage and return a success response', async () => { - const validRequest = { - jwt: jwtTokenUser1, - author_chain: testAddresses[0].chain, - chain: testAddresses[0].chain, - thread_id: testThreads[0].id, - stage: 'proposal_in_review', - }; - - chai.assert.notEqual(testThreads[0].stage, 'proposal_in_review'); - - const response = await post( - '/api/updateThreadStage', - validRequest, - true, - app - ); - - chai.assert.equal(response.status, 'Success'); - const thread = await models.Thread.findOne({ - where: { id: testThreads[0].id }, - }); - - chai.assert.equal(thread.stage, 'proposal_in_review'); - }); -}); diff --git a/packages/commonwealth/test/integration/api/updateTopic.spec.ts b/packages/commonwealth/test/integration/api/updateTopic.spec.ts deleted file mode 100644 index ecda33cccc9..00000000000 --- a/packages/commonwealth/test/integration/api/updateTopic.spec.ts +++ /dev/null @@ -1,139 +0,0 @@ -import chai from 'chai'; -import chaiHttp from 'chai-http'; -import jwt from 'jsonwebtoken'; -import app from '../../../server-test'; -import { JWT_SECRET } from '../../../server/config'; -import models from '../../../server/database'; -import { UpdateTopicErrors } from '../../../server/routes/updateThreadTopic'; -import { post } from './external/appHook.spec'; -import { - testAddresses, - testThreads, - testTopics, - testUsers, -} from './external/dbEntityHooks.spec'; - -chai.use(chaiHttp); - -describe('updateTopic Integration Tests', () => { - let jwtTokenUser1; - - beforeEach(() => { - jwtTokenUser1 = jwt.sign( - { id: testUsers[0].id, email: testUsers[0].email }, - JWT_SECRET - ); - }); - - it('should return an error response if there is an invalid threadId specified', async () => { - const invalidRequest = { - jwt: jwtTokenUser1, - author_chain: testAddresses[0].chain, - chain: testAddresses[0].chain, - thread_id: false, - topic_name: testTopics[0].name, - address: testAddresses[0].address, - }; - - const response = await post( - '/api/updateThreadTopic', - invalidRequest, - true, - app - ); - - response.should.have.status(400); - chai.assert.equal(response.error, UpdateTopicErrors.NoThread); - }); - - it('should return an error response if an invalid address is specified', async () => { - const invalidRequest = { - jwt: jwtTokenUser1, - author_chain: testAddresses[0].chain, - chain: testAddresses[0].chain, - thread_id: testThreads[0].id, - topic_name: testTopics[0].name, - address: false, - }; - - const response = await post( - '/api/updateThreadTopic', - invalidRequest, - true, - app - ); - - response.should.have.status(400); - chai.assert.equal(response.error, UpdateTopicErrors.NoAddr); - }); - - it('should return an error response if an invalid topic is specified', async () => { - const invalidRequest = { - jwt: jwtTokenUser1, - author_chain: testAddresses[0].chain, - chain: testAddresses[0].chain, - thread_id: testThreads[0].id, - topic_name: false, - address: testAddresses[0].address, - }; - - const response = await post( - '/api/updateThreadTopic', - invalidRequest, - true, - app - ); - - response.should.have.status(400); - chai.assert.equal(response.error, UpdateTopicErrors.NoTopic); - }); - - it('should return an error response if not enough permission to update topic', async () => { - const invalidRequest = { - jwt: jwtTokenUser1, - author_chain: testAddresses[0].chain, - chain: testAddresses[0].chain, - thread_id: testThreads[3].id, - topic_name: 'test topic', - address: testAddresses[3].address, - }; - - const response = await post( - '/api/updateThreadTopic', - invalidRequest, - true, - app - ); - - response.should.have.status(400); - chai.assert.equal(response.error, UpdateTopicErrors.NoPermission); - }); - - it('should update topic and return a success response', async () => { - const validRequest = { - jwt: jwtTokenUser1, - author_chain: testAddresses[0].chain, - chain: testAddresses[0].chain, - thread_id: testThreads[0].id, - topic_name: testTopics[1].name, - address: testAddresses[0].address, - }; - - chai.assert.notEqual(testTopics[0].id, testTopics[1].id); - chai.assert.equal(testThreads[0].topic_id, testTopics[0].id); - - const response = await post( - '/api/updateThreadTopic', - validRequest, - true, - app - ); - - chai.assert.equal(response.status, 'Success'); - const thread = await models.Thread.findOne({ - where: { id: testThreads[0].id }, - }); - - chai.assert.equal(thread.topic_id, testTopics[1].id); - }); -});