diff --git a/src/utils/db.ts b/src/utils/db.ts index 4586b3c60..3e379cd95 100644 --- a/src/utils/db.ts +++ b/src/utils/db.ts @@ -1,5 +1,5 @@ import { nanoid } from 'nanoid'; -import { GoalHistory, Comment, Activity, User, Goal } from '@prisma/client'; +import { GoalHistory, Comment, Activity, User, Goal, Role } from '@prisma/client'; import { TRPCError } from '@trpc/server'; import { GoalCommon, GoalUpdate, dependencyKind } from '../schema/goal'; @@ -50,7 +50,7 @@ export const findOrCreateEstimate = async ( * @param input goal FormData * @returns new goal id */ -export const createGoal = async (activityId: string, input: GoalCommon) => { +export const createGoal = async (input: GoalCommon, activityId: string, role: Role) => { const id = nanoid(); await prisma.$executeRaw` @@ -106,7 +106,7 @@ export const createGoal = async (activityId: string, input: GoalCommon) => { return { ...goal, - ...addCalclulatedGoalsFields(goal, activityId), + ...addCalclulatedGoalsFields(goal, activityId, role), }; }; diff --git a/trpc/queries/goals.ts b/trpc/queries/goals.ts index 07e8e92e4..f2d69b184 100644 --- a/trpc/queries/goals.ts +++ b/trpc/queries/goals.ts @@ -1,4 +1,4 @@ -import { Estimate, EstimateToGoal, Goal, GoalAchieveCriteria, Prisma, State, StateType } from '@prisma/client'; +import { Estimate, EstimateToGoal, Goal, GoalAchieveCriteria, Prisma, Role, State, StateType } from '@prisma/client'; import { QueryWithFilters } from '../../src/schema/common'; @@ -482,7 +482,7 @@ export const calcAchievedWeight = ( ); }; -export const addCalclulatedGoalsFields = (goal: any, activityId: string) => { +export const addCalclulatedGoalsFields = (goal: any, activityId: string, role: Role) => { const _isOwner = goal.ownerId === activityId; const _isParticipant = goal.participants?.some((participant: any) => participant?.id === activityId); const _isWatching = goal.watchers?.some((watcher: any) => watcher?.id === activityId); @@ -510,7 +510,7 @@ export const addCalclulatedGoalsFields = (goal: any, activityId: string) => { } checkParent(goal.project); - const _isEditable = _isOwner || _isIssuer || parentOwner; + const _isEditable = _isOwner || _isIssuer || parentOwner || role === 'ADMIN'; return { _isOwner, diff --git a/trpc/router/goal.ts b/trpc/router/goal.ts index ed87f670a..483115836 100644 --- a/trpc/router/goal.ts +++ b/trpc/router/goal.ts @@ -110,12 +110,14 @@ export const goal = router({ }), ) .query(async ({ ctx, input: { query, limit, skip, cursor } }) => { + const { activityId, role } = ctx.session.user; + const [items, count] = await Promise.all([ prisma.goal.findMany({ take: limit + 1, skip, cursor: cursor ? { id: cursor } : undefined, - ...(query ? goalsFilter(query, ctx.session.user.activityId) : {}), + ...(query ? goalsFilter(query, activityId) : {}), orderBy: { id: 'asc', }, @@ -136,9 +138,9 @@ export const goal = router({ return { items: items.map((g) => ({ ...g, - ...addCalclulatedGoalsFields(g, ctx.session.user.activityId), + ...addCalclulatedGoalsFields(g, activityId, role), _estimate: getEstimateListFormJoin(g), - _project: g.project ? addCalculatedProjectFields(g.project, ctx.session.user.activityId) : null, + _project: g.project ? addCalculatedProjectFields(g.project, activityId, role) : null, })), nextCursor, meta: { @@ -157,6 +159,7 @@ export const goal = router({ getById: protectedProcedure.input(z.string()).query(async ({ ctx, input }) => { // try to recognize shot id like: FRNTND-23 const [projectId, scopeIdStr] = input.split('-'); + const { activityId, role } = ctx.session.user; if (!projectId) return null; @@ -225,8 +228,8 @@ export const goal = router({ return { ...goal, - ...addCalclulatedGoalsFields(goal, ctx.session.user.activityId), - _project: goal.project ? addCalculatedProjectFields(goal.project, ctx.session.user.activityId) : null, + ...addCalclulatedGoalsFields(goal, activityId, role), + _project: goal.project ? addCalculatedProjectFields(goal.project, activityId, role) : null, _estimate: getEstimateListFormJoin(goal), _activityFeed: mixHistoryWithComments(history, goal.comments), _relations: makeGoalRelationMap({ @@ -243,7 +246,7 @@ export const goal = router({ if (!input.owner.id) return null; if (!input.parent.id) return null; - const { activityId } = ctx.session.user; + const { activityId, role } = ctx.session.user; const actualProject = await prisma.project.findUnique({ where: { id: input.parent.id }, @@ -259,7 +262,7 @@ export const goal = router({ } try { - const newGoal = await createGoal(activityId, input); + const newGoal = await createGoal(input, activityId, role); const recipients = Array.from( new Set( @@ -299,7 +302,7 @@ export const goal = router({ if (!actualGoal) return null; - const { activityId } = ctx.session.user; + const { activityId, role } = ctx.session.user; try { await changeGoalProject(input.id, input.projectId); @@ -327,13 +330,15 @@ export const goal = router({ return { ...goal, - ...addCalclulatedGoalsFields(goal, activityId), + ...addCalclulatedGoalsFields(goal, activityId, role), }; } catch (error: any) { throw new TRPCError({ code: 'INTERNAL_SERVER_ERROR', message: String(error.message), cause: error }); } }), update: protectedProcedure.input(goalUpdateSchema).mutation(async ({ ctx, input }) => { + const { activityId, role } = ctx.session.user; + const actualGoal = await prisma.goal.findUnique({ where: { id: input.id }, include: { @@ -352,7 +357,7 @@ export const goal = router({ if (!actualGoal) return null; - const { _isEditable, _shortId } = addCalclulatedGoalsFields(actualGoal, ctx.session.user.activityId); + const { _isEditable, _shortId } = addCalclulatedGoalsFields(actualGoal, activityId, role); if (!_isEditable) { return null; @@ -423,7 +428,7 @@ export const goal = router({ }); } - const correctEstimate = await findOrCreateEstimate(input.estimate, ctx.session.user.activityId, actualGoal.id); + const correctEstimate = await findOrCreateEstimate(input.estimate, activityId, actualGoal.id); const previousEstimate = actualGoal.estimate.length ? actualGoal.estimate[actualGoal.estimate.length - 1] : null; @@ -473,7 +478,7 @@ export const goal = router({ }, history: { createMany: { - data: history.map((record) => ({ ...record, activityId: ctx.session.user.activityId })), + data: history.map((record) => ({ ...record, activityId })), }, }, goalAsCriteria: actualGoal.goalAsCriteria @@ -530,8 +535,8 @@ export const goal = router({ return { ...goal, - ...addCalclulatedGoalsFields(goal, ctx.session.user.activityId), - _project: goal.project ? addCalculatedProjectFields(goal.project, ctx.session.user.activityId) : null, + ...addCalclulatedGoalsFields(goal, activityId, role), + _project: goal.project ? addCalculatedProjectFields(goal.project, activityId, role) : null, _estimate: getEstimateListFormJoin(goal), _activityFeed: [], }; @@ -572,6 +577,8 @@ export const goal = router({ toggleArchive: protectedProcedure .input(toggleGoalArchiveSchema) .mutation(async ({ input: { id, archived }, ctx }) => { + const { activityId, role } = ctx.session.user; + const actualGoal = await prisma.goal.findFirst({ where: { id, @@ -590,7 +597,7 @@ export const goal = router({ return null; } - const { _isEditable, _shortId } = addCalclulatedGoalsFields(actualGoal, ctx.session.user.activityId); + const { _isEditable, _shortId } = addCalclulatedGoalsFields(actualGoal, activityId, role); if (!_isEditable) { return null; @@ -605,7 +612,7 @@ export const goal = router({ create: { subject: 'state', action: 'archive', - activityId: ctx.session.user.activityId, + activityId, nextValue: archived ? 'move to archive' : 'move from archive', }, }, @@ -638,6 +645,8 @@ export const goal = router({ } }), switchState: protectedProcedure.input(goalStateChangeSchema).mutation(async ({ input, ctx }) => { + const { activityId, role } = ctx.session.user; + const actualGoal = await prisma.goal.findFirst({ where: { id: input.id, @@ -657,7 +666,7 @@ export const goal = router({ return null; } - const { _isEditable, _shortId } = addCalclulatedGoalsFields(actualGoal, ctx.session.user.activityId); + const { _isEditable, _shortId } = addCalclulatedGoalsFields(actualGoal, activityId, role); if (!_isEditable) { return null; @@ -677,7 +686,7 @@ export const goal = router({ action: 'change', previousValue: actualGoal.stateId, nextValue: input.state.id, - activityId: ctx.session.user.activityId, + activityId, }, }, goalAsCriteria: actualGoal.goalAsCriteria @@ -708,7 +717,7 @@ export const goal = router({ subject: 'criteria', action: nowIsCompleted ? 'complete' : 'uncomplete', nextValue: actualGoal.goalAsCriteria.id, - activityId: ctx.session.user.activityId, + activityId, }, }), ); @@ -748,6 +757,8 @@ export const goal = router({ createComment: protectedProcedure.input(goalCommentCreateSchema).mutation(async ({ ctx, input }) => { if (!input.goalId) return null; + const { activityId, role } = ctx.session.user; + const [commentAuthor, actualGoal, pushState] = await Promise.all([ prisma.activity.findUnique({ where: { id: ctx.session.user.activityId }, @@ -771,7 +782,7 @@ export const goal = router({ if (!commentAuthor) return null; if (!actualGoal) return null; - const { _isEditable, _shortId } = addCalclulatedGoalsFields(actualGoal, ctx.session.user.activityId); + const { _isEditable, _shortId } = addCalclulatedGoalsFields(actualGoal, activityId, role); try { // We want to see state changes record and comment next in activity feed. @@ -797,7 +808,7 @@ export const goal = router({ action: 'change', previousValue: actualGoal.stateId, nextValue: input.stateId, - activityId: ctx.session.user.activityId, + activityId, }, } : undefined, diff --git a/trpc/router/project.ts b/trpc/router/project.ts index ed4955675..0579b1d74 100644 --- a/trpc/router/project.ts +++ b/trpc/router/project.ts @@ -1,4 +1,4 @@ -import { Activity, Prisma, Project, User } from '@prisma/client'; +import { Activity, Prisma, Project, Role, User } from '@prisma/client'; import z from 'zod'; import { TRPCError } from '@trpc/server'; @@ -31,6 +31,7 @@ export const addCalculatedProjectFields = < >( project: T, activityId: string, + role: Role, ): T & { _isWatching?: boolean; _isStarred?: boolean; @@ -38,7 +39,7 @@ export const addCalculatedProjectFields = < } => { const _isWatching = project.watchers?.some((watcher: any) => watcher.id === activityId); const _isStarred = project.stargizers?.some((stargizer: any) => stargizer.id === activityId); - const _isOwner = project.activityId === activityId; + const _isOwner = project.activityId === activityId || role === 'ADMIN'; return { ...project, @@ -112,7 +113,7 @@ export const project = router({ return Promise.all(requests).then(([suggest, included = []]) => [...included, ...suggest]); }), getUserProjectsWithGoals: protectedProcedure.input(queryWithFiltersSchema).query(async ({ ctx, input = {} }) => { - const { activityId } = ctx.session.user; + const { activityId, role } = ctx.session.user; const projectsSchema = getProjectSchema({ activityId, @@ -200,7 +201,7 @@ export const project = router({ const goals = project.goals.map((goal) => { return { ...goal, - ...addCalclulatedGoalsFields(goal, activityId), + ...addCalclulatedGoalsFields(goal, activityId, role), _estimate: getEstimateListFormJoin(goal), }; }); @@ -209,7 +210,7 @@ export const project = router({ return { goals, - project: addCalculatedProjectFields(rest, activityId), + project: addCalculatedProjectFields(rest, activityId, role), }; }), totalGoalsCount: res.reduce((acc, cur) => { @@ -230,7 +231,7 @@ export const project = router({ .optional(), ) .query(async ({ ctx, input: { goalsQuery } = {} }) => { - const { activityId } = ctx.session.user; + const { activityId, role } = ctx.session.user; const sqlFilters = sqlGoalsFilter(activityId, goalsQuery); const [projects, watchers, stargizers, projectsChildrenParent] = await Promise.all([ @@ -326,7 +327,7 @@ export const project = router({ fillProject(projectsDict, { watcher, stargizer, project }); } - return Object.values(projectsDict)?.map((project) => addCalculatedProjectFields(project, activityId)); + return Object.values(projectsDict)?.map((project) => addCalculatedProjectFields(project, activityId, role)); }), getTop: protectedProcedure .input( @@ -338,18 +339,20 @@ export const project = router({ .optional(), ) .query(async ({ ctx, input: { firstLevel, goalsQuery } = {} }) => { + const { activityId, role } = ctx.session.user; + const allProjects = await prisma.project .findMany({ orderBy: { createdAt: 'asc', }, ...getProjectSchema({ - activityId: ctx.session.user.activityId, + activityId, goalsQuery, firstLevel, }), }) - .then((res) => res.map((project) => addCalculatedProjectFields(project, ctx.session.user.activityId))); + .then((res) => res.map((project) => addCalculatedProjectFields(project, activityId, role))); // FIX: it is hack! return allProjects.filter((p) => p._count.parent === 0); @@ -362,9 +365,11 @@ export const project = router({ }), ) .query(async ({ ctx, input: { id, goalsQuery } }) => { + const { activityId, role } = ctx.session.user; + const project = await prisma.project.findUnique({ ...getProjectSchema({ - activityId: ctx.session.user.activityId, + activityId, goalsQuery, }), where: { @@ -374,7 +379,7 @@ export const project = router({ if (!project) return null; - return addCalculatedProjectFields(project, ctx.session.user.activityId); + return addCalculatedProjectFields(project, activityId, role); }), getByIds: protectedProcedure .input( @@ -384,6 +389,8 @@ export const project = router({ }), ) .query(async ({ ctx, input: { ids, goalsQuery } }) => { + const { activityId, role } = ctx.session.user; + const projects = await prisma.project.findMany({ where: { id: { @@ -391,12 +398,12 @@ export const project = router({ }, }, ...getProjectSchema({ - activityId: ctx.session.user.activityId, + activityId, goalsQuery, }), }); - return projects.map((project) => addCalculatedProjectFields(project, ctx.session.user.activityId)); + return projects.map((project) => addCalculatedProjectFields(project, activityId, role)); }), getDeepInfo: protectedProcedure .input( @@ -406,6 +413,8 @@ export const project = router({ }), ) .query(async ({ ctx, input: { id, goalsQuery } }) => { + const { activityId, role } = ctx.session.user; + const [allProjectGoals, filtredProjectGoals] = await Promise.all([ prisma.goal.findMany({ ...goalsFilter( @@ -418,7 +427,7 @@ export const project = router({ project: [], query: '', }, - ctx.session.user.activityId, + activityId, { projectId: id }, ), include: { @@ -434,7 +443,7 @@ export const project = router({ }, }), prisma.goal.findMany({ - ...goalsFilter(goalsQuery, ctx.session.user.activityId, { projectId: id }), + ...goalsFilter(goalsQuery, activityId, { projectId: id }), include: { ...goalDeepQuery, estimate: { @@ -452,8 +461,8 @@ export const project = router({ return { goals: filtredProjectGoals.map((g) => ({ ...g, - _project: g.project ? addCalculatedProjectFields(g.project, ctx.session.user.activityId) : null, - ...addCalclulatedGoalsFields(g, ctx.session.user.activityId), + _project: g.project ? addCalculatedProjectFields(g.project, activityId, role) : null, + ...addCalclulatedGoalsFields(g, activityId, role), _estimate: getEstimateListFormJoin(g), })), meta: calcGoalsMeta(allProjectGoals), @@ -462,16 +471,18 @@ export const project = router({ create: protectedProcedure .input(projectCreateSchema) .mutation(async ({ ctx, input: { id, title, description, flow } }) => { + const { activityId } = ctx.session.user; + try { return prisma.project.create({ data: { id, title, description, - activityId: ctx.session.user.activityId, + activityId, flowId: flow.id, watchers: { - connect: [ctx.session.user.activityId].map((id) => ({ id })), + connect: [activityId].map((id) => ({ id })), }, }, }); diff --git a/trpc/router/search.ts b/trpc/router/search.ts index 842665697..fe5d70ca7 100644 --- a/trpc/router/search.ts +++ b/trpc/router/search.ts @@ -6,6 +6,8 @@ import { addCalclulatedGoalsFields, goalDeepQuery } from '../queries/goals'; export const search = router({ global: protectedProcedure.input(z.string()).query(async ({ ctx, input }) => { + const { activityId, role } = ctx.session.user; + const [goals, projects] = await Promise.all([ prisma.goal.findMany({ take: 5, @@ -75,7 +77,7 @@ export const search = router({ return { goals: goals.map((g) => ({ ...g, - ...addCalclulatedGoalsFields(g, ctx.session.user.activityId), + ...addCalclulatedGoalsFields(g, activityId, role), })), projects, };