Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/app/api/tasks/public/public.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export const PublicTaskDtoSchema = z.object({
clientId: z.string().uuid().nullable(),
companyId: z.string().uuid().nullable(),
association: AssociationsSchema,
viewers: AssociationsSchema,
attachments: z.array(PublicAttachmentDtoSchema),
isShared: z.boolean().optional(),
})
Expand Down
1 change: 1 addition & 0 deletions src/app/api/tasks/public/public.serializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ export class PublicTaskSerializer {
clientId: task.clientId,
companyId: task.companyId,
association: AssociationsSchema.parse(task.associations),
viewers: task.isShared ? AssociationsSchema.parse(task.associations) : [],
attachments: await PublicAttachmentSerializer.serializeAttachments({
attachments: task.attachments,
uploadedByUserType: 'internalUser', // task creator is always IU
Expand Down
10 changes: 7 additions & 3 deletions src/app/api/tasks/public/public.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -332,9 +332,12 @@ export class PublicTasksService extends TasksSharedService {
companyId: validatedIds?.companyId ?? null,
})

const associations: Associations = await this.getValidatedAssociations({
prevAssociations: prevTask.associations,
associationsResetCondition: shouldUpdateUserIds ? !!clientId || !!companyId : !prevTask.internalUserId,
const associations = await this.resolveAssociations({
prevTask,
data,
shouldUpdateUserIds,
clientId,
companyId,
})

const userAssignmentFields = shouldUpdateUserIds
Expand Down Expand Up @@ -383,6 +386,7 @@ export class PublicTasksService extends TasksSharedService {
completedBy,
completedByUserType,
associations,
isShared: this.validateTaskShare(prevTask, data),
...userAssignmentFields,
...(await getTaskTimestamps('update', this.user, data, prevTask)),
},
Expand Down
39 changes: 7 additions & 32 deletions src/app/api/tasks/tasks.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -313,24 +313,6 @@ export class TasksService extends TasksSharedService {
}
}

private validateTaskShare(prevTask: Task, data: UpdateTaskRequest): boolean | undefined {
const isTaskShared = data.isShared

if (isTaskShared === undefined) return undefined

if (isTaskShared) {
const isEligibleForShare = !!(
(prevTask.associations.length && prevTask.internalUserId) ||
(data.associations?.length && data.internalUserId)
)
if (!isEligibleForShare) {
throw new APIError(httpStatus.BAD_REQUEST, 'Cannot share task with assocations')
}
return true
}
return false
}

async updateOneTask(id: string, data: UpdateTaskRequest) {
const policyGate = new PoliciesService(this.user)
policyGate.authorize(UserAction.Update, Resource.Tasks)
Expand Down Expand Up @@ -365,20 +347,13 @@ export class TasksService extends TasksSharedService {
companyId: validatedIds?.companyId ?? null,
})

let associations: Associations = AssociationsSchema.parse(prevTask.associations)

// check if current or previous assignee is a client or company
const associationsResetCondition = shouldUpdateUserIds
? !!clientId || !!companyId
: prevTask.clientId || prevTask.companyId
if (data.associations) {
// only update of associations attribute is available. No associations in payload attribute means the data remains as it is in DB.
if (associationsResetCondition || !data.associations?.length) {
associations = [] // reset associations to [] if task is not reassigned to IU.
} else if (data.associations?.length) {
associations = await this.validateAssociations(data.associations)
}
}
const associations = await this.resolveAssociations({
prevTask,
data,
shouldUpdateUserIds,
clientId,
companyId,
})

const userAssignmentFields = shouldUpdateUserIds
? {
Expand Down
73 changes: 72 additions & 1 deletion src/app/api/tasks/tasksShared.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@ import { maxSubTaskDepth } from '@/constants/tasks'
import { MAX_FETCH_ASSIGNEE_COUNT } from '@/constants/users'
import { InternalUsers, TempClientFilter, Uuid } from '@/types/common'
import { CreateAttachmentRequestSchema } from '@/types/dto/attachments.dto'
import { CreateTaskRequest, CreateTaskRequestSchema, Associations } from '@/types/dto/tasks.dto'
import {
CreateTaskRequest,
CreateTaskRequestSchema,
Associations,
UpdateTaskRequest,
AssociationsSchema,
} from '@/types/dto/tasks.dto'
import { getFileNameFromPath } from '@/utils/attachmentUtils'
import { buildLtree, buildLtreeNodeString } from '@/utils/ltree'
import { getFilePathFromUrl } from '@/utils/signedUrlReplacer'
Expand Down Expand Up @@ -536,4 +542,69 @@ export abstract class TasksSharedService extends BaseService {
)
}
}

protected validateTaskShare(prevTask: Task, data: UpdateTaskRequest): boolean | undefined {
const finalIsShared = data.isShared !== undefined ? data.isShared : prevTask.isShared

const finalInternalUser = data.internalUserId !== undefined ? data.internalUserId : prevTask.internalUserId

const finalAssociations = data.associations !== undefined ? data.associations : prevTask.associations

if (!finalIsShared) return false

const hasInternalUser = !!finalInternalUser
const hasAssociations = !!finalAssociations?.length

if (!hasInternalUser || !hasAssociations) {
throw new APIError(
httpStatus.BAD_REQUEST,
'Cannot share task. A task must have an internal user and at least one association to be shared.',
)
}

return true
}

protected async resolveAssociations(params: {
prevTask: Task
data: UpdateTaskRequest
shouldUpdateUserIds: boolean
clientId?: string | null
companyId?: string | null
}): Promise<Associations> {
const { prevTask, data, shouldUpdateUserIds, clientId, companyId } = params
if (!data.associations) {
return AssociationsSchema.parse(prevTask.associations)
}

const shouldReset = this.shouldResetAssociations({
shouldUpdateUserIds,
prevTask,
clientId,
companyId,
})

if (shouldReset) return []

const parsed = AssociationsSchema.parse(data.associations)

if (!parsed?.length) return []

return this.validateAssociations(parsed)
}

private shouldResetAssociations(params: {
shouldUpdateUserIds: boolean
prevTask: Task
clientId?: string | null
companyId?: string | null
}): boolean {
const { shouldUpdateUserIds, prevTask, clientId, companyId } = params

if (shouldUpdateUserIds) {
return !!clientId || !!companyId
}

return !!prevTask.clientId || !!prevTask.companyId
}
}
Loading