{"record":{"id":"955cf7765d772a70","repo":"immich-app/immich","slug":"partner-already-exists","errorCode":null,"errorMessage":"Partner already exists","messagePattern":"Partner already exists","errorType":"http","errorClass":"BadRequestException","httpStatus":400,"severity":"warning","filePath":"server/src/services/partner.service.ts","lineNumber":16,"sourceCode":"import { BadRequestException, Injectable } from '@nestjs/common';\nimport { Partner } from 'src/database';\nimport { AuthDto } from 'src/dtos/auth.dto';\nimport { PartnerCreateDto, PartnerResponseDto, PartnerSearchDto, PartnerUpdateDto } from 'src/dtos/partner.dto';\nimport { mapUser } from 'src/dtos/user.dto';\nimport { Permission } from 'src/enum';\nimport { PartnerDirection, PartnerIds } from 'src/repositories/partner.repository';\nimport { BaseService } from 'src/services/base.service';\n\n@Injectable()\nexport class PartnerService extends BaseService {\n  async create(auth: AuthDto, { sharedWithId }: PartnerCreateDto): Promise<PartnerResponseDto> {\n    const partnerId: PartnerIds = { sharedById: auth.user.id, sharedWithId };\n    const exists = await this.partnerRepository.get(partnerId);\n    if (exists) {\n      throw new BadRequestException(`Partner already exists`);\n    }\n\n    const user = await this.userRepository.get(sharedWithId, {});\n    if (!user) {\n      this.logger.debug('Partner creation failed: user not found');\n      throw new BadRequestException('Invalid user');\n    }\n\n    const partner = await this.partnerRepository.create(partnerId);\n    return this.mapPartner(partner, PartnerDirection.SharedBy);\n  }\n\n  async remove(auth: AuthDto, sharedWithId: string): Promise<void> {\n    const partnerId: PartnerIds = { sharedById: auth.user.id, sharedWithId };\n    const partner = await this.partnerRepository.get(partnerId);\n    if (!partner) {\n      throw new BadRequestException('Partner not found');\n    }","sourceCodeStart":1,"sourceCodeEnd":34,"githubUrl":"https://github.com/immich-app/immich/blob/199723261c6ffa897fec8ccdaea6359e39c37cc3/server/src/services/partner.service.ts#L1-L34","documentation":"Thrown by PartnerService.create when partnerRepository.get({ sharedById, sharedWithId }) already returns a row. Represents an idempotency violation: the authenticated user is already sharing their library with the requested sharedWithId. BadRequestException -> HTTP 400.","triggerScenarios":"POST /partner with a sharedWithId that the caller already shares with; double submission of the same partner-create form; a UI that re-issues the request on retry.","commonSituations":"User clicks 'Share' twice; client retries after a timeout even though the first call succeeded; race condition where two parallel requests both pass the exists-check before either inserts.","solutions":["Treat 400 'Partner already exists' as success if the goal is to ensure the share exists (idempotent create).","Guard the UI: fetch the existing partner list first and disable already-shared users.","Catch the 400 client-side and refresh the partner list instead of retrying the POST."],"exampleFix":"// before\nconst exists = await this.partnerRepository.get(partnerId);\nif (exists) {\n  throw new BadRequestException(`Partner already exists`);\n}\n\n// after (return the existing row so the endpoint is idempotent)\nconst exists = await this.partnerRepository.get(partnerId);\nif (exists) {\n  return this.mapPartner(exists, PartnerDirection.SharedBy);\n}","handlingStrategy":"validation","validationCode":"// Before creating, check the existing partnership for this caller.\nconst existing = await partnerService.search(auth, { direction: 'shared-by' });\nif (existing.some((p) => p.id === sharedWithId)) {\n  // already shared; treat as success, do not POST /partner again\n  return existing.find((p) => p.id === sharedWithId)!;\n}","typeGuard":"const isPartner = (p: PartnerResponseDto | null | undefined): p is PartnerResponseDto =>\n  !!p && typeof p.id === 'string';","tryCatchPattern":"try {\n  await partnerService.create(auth, { sharedWithId });\n} catch (e) {\n  if (e instanceof BadRequestException && /already exists/i.test(e.message)) {\n    // idempotent success\n    return;\n  }\n  throw e;\n}","preventionTips":["Fetch the current partner list before showing the share UI.","Make create idempotent on the client by treating 'already exists' as success.","Disable the share button for users already in the partner list."],"tags":["partner","sharing","idempotency","nestjs","validation"],"backgroundTag":null,"analyzedSha":"199723261c6ffa897fec8ccdaea6359e39c37cc3","analyzedAt":"2026-08-12T04:54:27.085Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}