From c9dbee806d23f3e3447c8d9c582353c74507459b Mon Sep 17 00:00:00 2001 From: jue-henry Date: Thu, 10 Oct 2024 13:02:46 -0700 Subject: [PATCH 01/23] preliminary implementation --- src/@types/graphql.ts | 4 ++++ src/api/db/schemas/Task.ts | 1 + src/api/type-defs/inputs/DeleteCameraInput.ts | 5 +++++ src/task/camera.ts | 4 ++++ 4 files changed, 14 insertions(+) create mode 100644 src/api/type-defs/inputs/DeleteCameraInput.ts diff --git a/src/@types/graphql.ts b/src/@types/graphql.ts index 8518cb8f..0c8cf8ad 100644 --- a/src/@types/graphql.ts +++ b/src/@types/graphql.ts @@ -256,6 +256,10 @@ export type CreateViewPayload = { view?: Maybe; }; +export type DeleteCameraInput = { + cameraId: Scalars['ID']['input']; +}; + export type DeleteDeploymentInput = { cameraId: Scalars['ID']['input']; deploymentId: Scalars['ID']['input']; diff --git a/src/api/db/schemas/Task.ts b/src/api/db/schemas/Task.ts index 4a0bb6d6..83fd69f9 100644 --- a/src/api/db/schemas/Task.ts +++ b/src/api/db/schemas/Task.ts @@ -20,6 +20,7 @@ const TaskSchema = new Schema({ 'UpdateSerialNumber', 'DeleteImages', 'DeleteImagesByFilter', + 'DeleteCamera', ], }, status: { diff --git a/src/api/type-defs/inputs/DeleteCameraInput.ts b/src/api/type-defs/inputs/DeleteCameraInput.ts new file mode 100644 index 00000000..4d690ff5 --- /dev/null +++ b/src/api/type-defs/inputs/DeleteCameraInput.ts @@ -0,0 +1,5 @@ +export default /* GraphQL */ ` + input DeleteCameraInput { + cameraId: ID! + } +`; diff --git a/src/task/camera.ts b/src/task/camera.ts index 13211533..b050ffe5 100644 --- a/src/task/camera.ts +++ b/src/task/camera.ts @@ -7,3 +7,7 @@ export async function UpdateSerialNumber(task: TaskInput) { + console.log('DeleteCamera task:', task); +} From 7a97ddd075c8d240a38b46a958d417646200aaa4 Mon Sep 17 00:00:00 2001 From: jue-henry Date: Thu, 10 Oct 2024 17:12:27 -0700 Subject: [PATCH 02/23] adding the delete camera mutation --- src/api/auth/roles.ts | 2 ++ src/api/db/models/Camera.ts | 26 ++++++++++++++++++++++++++ src/api/resolvers/Mutation.ts | 8 ++++++++ src/task/camera.ts | 1 + src/task/handler.ts | 4 +++- 5 files changed, 40 insertions(+), 1 deletion(-) diff --git a/src/api/auth/roles.ts b/src/api/auth/roles.ts index 78c455c9..2e17759f 100644 --- a/src/api/auth/roles.ts +++ b/src/api/auth/roles.ts @@ -15,6 +15,7 @@ const WRITE_DEPLOYMENTS_ROLES = [MANAGER]; const WRITE_AUTOMATION_RULES_ROLES = [MANAGER]; const WRITE_CAMERA_REGISTRATION_ROLES = [MANAGER]; const WRITE_CAMERA_SERIAL_NUMBER_ROLES = [MANAGER]; +const WRITE_DELETE_CAMERA_ROLES = [MANAGER]; const WRITE_TAGS_ROLES = [MANAGER, MEMBER]; export { @@ -31,5 +32,6 @@ export { WRITE_AUTOMATION_RULES_ROLES, WRITE_CAMERA_REGISTRATION_ROLES, WRITE_CAMERA_SERIAL_NUMBER_ROLES, + WRITE_DELETE_CAMERA_ROLES, WRITE_TAGS_ROLES, }; diff --git a/src/api/db/models/Camera.ts b/src/api/db/models/Camera.ts index 3c63a6b5..ed656c00 100644 --- a/src/api/db/models/Camera.ts +++ b/src/api/db/models/Camera.ts @@ -6,6 +6,7 @@ import retry from 'async-retry'; import { WRITE_CAMERA_REGISTRATION_ROLES, WRITE_CAMERA_SERIAL_NUMBER_ROLES, + WRITE_DELETE_CAMERA_ROLES, } from '../../auth/roles.js'; import { ProjectModel } from './Project.js'; import { BaseAuthedModel, MethodParams, roleCheck, idMatch } from './utils.js'; @@ -256,6 +257,26 @@ export class CameraModel { } } + static async deleteCameraTask( + input: gql.DeleteCameraInput, + context: Pick, + ): Promise> { + try { + return await TaskModel.create( + { + type: 'DeleteCamera', + projectId: context.user['curr_project'], + user: context.user.sub, + config: input, + }, + context, + ); + } catch (err) { + if (err instanceof GraphQLError) throw err; + throw new InternalServerError(err as string); + } + } + // NOTE: this method is called by the async task handler static async updateSerialNumber( input: gql.UpdateCameraSerialNumberInput, @@ -405,6 +426,11 @@ export default class AuthedCameraModel extends BaseAuthedModel { async updateSerialNumber(...args: MethodParams) { return await CameraModel.updateSerialNumberTask(...args); } + + @roleCheck(WRITE_DELETE_CAMERA_ROLES) + async deleteCamera(...args: MethodParams) { + return await CameraModel.deleteCameraTask(...args); + } } interface OperationMetadata { diff --git a/src/api/resolvers/Mutation.ts b/src/api/resolvers/Mutation.ts index 5f2c980d..3f6d5982 100644 --- a/src/api/resolvers/Mutation.ts +++ b/src/api/resolvers/Mutation.ts @@ -188,6 +188,14 @@ export default { return context.models.Camera.updateSerialNumber(input, context); }, + deleteCamera: async ( + _: unknown, + { input }: gql.MutationDeleteCameraArgs, + context: Context, + ): Promise => { + return context.models.Camera.deleteCamera(input, context); + }, + createProject: async ( _: unknown, { input }: gql.MutationCreateProjectArgs, diff --git a/src/task/camera.ts b/src/task/camera.ts index b050ffe5..f76ee695 100644 --- a/src/task/camera.ts +++ b/src/task/camera.ts @@ -10,4 +10,5 @@ export async function UpdateSerialNumber(task: TaskInput) { console.log('DeleteCamera task:', task); + return { isOk: true }; } diff --git a/src/task/handler.ts b/src/task/handler.ts index 84ea05be..d9060e43 100644 --- a/src/task/handler.ts +++ b/src/task/handler.ts @@ -4,7 +4,7 @@ import { connectToDatabase } from '../api/db/connect.js'; import { TaskModel } from '../api/db/models/Task.js'; import GetStats from './stats.js'; import { CreateDeployment, UpdateDeployment, DeleteDeployment } from './deployment.js'; -import { UpdateSerialNumber } from './camera.js'; +import { DeleteCamera, UpdateSerialNumber } from './camera.js'; import ImageErrorExport from './image-errors.js'; import AnnotationsExport from './annotations.js'; import { parseMessage } from './utils.js'; @@ -47,6 +47,8 @@ async function handler(event: SQSEvent) { output = await DeleteImages(task); } else if (task.type === 'DeleteImagesByFilter') { output = await DeleteImagesByFilter(task); + } else if (task.type === 'DeleteCamera') { + output = await DeleteCamera(task); } else { throw new Error(`Unknown Task: ${JSON.stringify(task)}`); } From f259ac21f79f10d44ba6f4b90bba22b4fbc5f14a Mon Sep 17 00:00:00 2001 From: jue-henry Date: Fri, 11 Oct 2024 13:54:05 -0700 Subject: [PATCH 03/23] log messages and additional types --- src/@types/graphql.ts | 6 ++++++ src/api/db/models/Camera.ts | 1 + src/api/db/models/Task.ts | 2 +- src/api/resolvers/Mutation.ts | 1 + src/api/type-defs/root/Mutation.ts | 1 + src/task/handler.ts | 2 +- src/task/stats.ts | 2 +- 7 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/@types/graphql.ts b/src/@types/graphql.ts index 0c8cf8ad..9872270f 100644 --- a/src/@types/graphql.ts +++ b/src/@types/graphql.ts @@ -606,6 +606,7 @@ export type Mutation = { createUpload?: Maybe; createUser?: Maybe; createView?: Maybe; + deleteCamera?: Maybe; deleteDeployment?: Maybe; deleteImageComment?: Maybe; deleteImageTag?: Maybe; @@ -726,6 +727,11 @@ export type MutationCreateViewArgs = { }; +export type MutationDeleteCameraArgs = { + input: DeleteCameraInput; +}; + + export type MutationDeleteDeploymentArgs = { input: DeleteDeploymentInput; }; diff --git a/src/api/db/models/Camera.ts b/src/api/db/models/Camera.ts index ed656c00..4dc04954 100644 --- a/src/api/db/models/Camera.ts +++ b/src/api/db/models/Camera.ts @@ -262,6 +262,7 @@ export class CameraModel { context: Pick, ): Promise> { try { + console.log('CameraModel.deleteCameraTask - input: ', input); return await TaskModel.create( { type: 'DeleteCamera', diff --git a/src/api/db/models/Task.ts b/src/api/db/models/Task.ts index d3a58b8b..52152856 100644 --- a/src/api/db/models/Task.ts +++ b/src/api/db/models/Task.ts @@ -66,7 +66,7 @@ export class TaskModel { projectId: input.projectId, type: input.type, }); - + console.log('TaskModel.create task:', task); const sqs = new SQS.SQSClient({ region: process.env.AWS_DEFAULT_REGION }); await task.save(); diff --git a/src/api/resolvers/Mutation.ts b/src/api/resolvers/Mutation.ts index 3f6d5982..4d272599 100644 --- a/src/api/resolvers/Mutation.ts +++ b/src/api/resolvers/Mutation.ts @@ -193,6 +193,7 @@ export default { { input }: gql.MutationDeleteCameraArgs, context: Context, ): Promise => { + console.log('Mutation.deleteCamera input:', input); return context.models.Camera.deleteCamera(input, context); }, diff --git a/src/api/type-defs/root/Mutation.ts b/src/api/type-defs/root/Mutation.ts index a19b9517..c908e752 100644 --- a/src/api/type-defs/root/Mutation.ts +++ b/src/api/type-defs/root/Mutation.ts @@ -41,6 +41,7 @@ export default /* GraphQL */ ` registerCamera(input: RegisterCameraInput!): RegisterCameraPayload unregisterCamera(input: UnregisterCameraInput!): UnregisterCameraPayload updateCameraSerialNumber(input: UpdateCameraSerialNumberInput!): Task + deleteCamera(input: DeleteCameraInput!): Task createView(input: CreateViewInput!): CreateViewPayload updateView(input: UpdateViewInput!): UpdateViewPayload diff --git a/src/task/handler.ts b/src/task/handler.ts index d9060e43..ebb5a71b 100644 --- a/src/task/handler.ts +++ b/src/task/handler.ts @@ -27,7 +27,7 @@ async function handler(event: SQSEvent) { { _id: task._id, status: 'RUNNING' }, { user: { curr_project: task.projectId } as User }, ); - + console.log('TaskModel task.type:', task.type); try { if (task.type === 'GetStats') { output = await GetStats(task); diff --git a/src/task/stats.ts b/src/task/stats.ts index b16eabe7..d74d7bbb 100644 --- a/src/task/stats.ts +++ b/src/task/stats.ts @@ -18,7 +18,7 @@ export default async function (task: TaskInput<{ filters: FiltersSchema }>) { const project = await ProjectModel.queryById(context.user['curr_project']); const pipeline = buildPipeline(task.config.filters, context.user['curr_project']); - + console.log('GetStats pipeline:', pipeline); // stream in images from MongoDB for await (const img of Image.aggregate(pipeline)) { // increment imageCount From 5cd81717620b342496c963d030ffd6632b223fbe Mon Sep 17 00:00:00 2001 From: jue-henry Date: Thu, 17 Oct 2024 16:07:33 -0700 Subject: [PATCH 04/23] removing camera id from camera configs --- src/api/db/models/Camera.ts | 59 +++++++++++++++++++++++------------- src/api/db/models/Project.ts | 42 +++++++++++++++++++++++-- src/task/camera.ts | 4 +-- 3 files changed, 80 insertions(+), 25 deletions(-) diff --git a/src/api/db/models/Camera.ts b/src/api/db/models/Camera.ts index 4dc04954..961bd67d 100644 --- a/src/api/db/models/Camera.ts +++ b/src/api/db/models/Camera.ts @@ -257,27 +257,6 @@ export class CameraModel { } } - static async deleteCameraTask( - input: gql.DeleteCameraInput, - context: Pick, - ): Promise> { - try { - console.log('CameraModel.deleteCameraTask - input: ', input); - return await TaskModel.create( - { - type: 'DeleteCamera', - projectId: context.user['curr_project'], - user: context.user.sub, - config: input, - }, - context, - ); - } catch (err) { - if (err instanceof GraphQLError) throw err; - throw new InternalServerError(err as string); - } - } - // NOTE: this method is called by the async task handler static async updateSerialNumber( input: gql.UpdateCameraSerialNumberInput, @@ -402,6 +381,44 @@ export class CameraModel { throw new InternalServerError(err as string); } } + + static async deleteCameraTask( + input: gql.DeleteCameraInput, + context: Pick, + ): Promise> { + try { + console.log('CameraModel.deleteCameraTask - input: ', input); + return await TaskModel.create( + { + type: 'DeleteCamera', + projectId: context.user['curr_project'], + user: context.user.sub, + config: input, + }, + context, + ); + } catch (err) { + if (err instanceof GraphQLError) throw err; + throw new InternalServerError(err as string); + } + } + + static async deleteCamera( + input: gql.DeleteCameraInput, + context: Pick, + ): Promise { + console.log('CameraModel.deleteCamera - input: ', input); + ProjectModel.deleteCameraConfig( + { + cameraId: input.cameraId, + }, + context, + ); + // TODO: delete deployments from views + // TODO: delete images associated with this camera + // TODO: unregister camera + return { isOk: true }; + } } export default class AuthedCameraModel extends BaseAuthedModel { diff --git a/src/api/db/models/Project.ts b/src/api/db/models/Project.ts index 7894f81a..0d547f00 100644 --- a/src/api/db/models/Project.ts +++ b/src/api/db/models/Project.ts @@ -172,7 +172,7 @@ export class ProjectModel { static async createCameraConfig( input: { projectId: string; cameraId: string }, - context: Pick, + _: Pick, ): Promise> { console.log('Project.createCameraConfig - input: ', input); const { projectId, cameraId } = input; @@ -238,6 +238,44 @@ export class ProjectModel { } } + static async deleteCameraConfig( + input: { cameraId: string }, + context: Pick, + ): Promise> { + try { + return await retry( + async () => { + let project = await Project.findOne({ _id: context.user['curr_project'] }); + if (!project) throw new NotFoundError('Project not found'); + console.log('originalProject: ', project); + + console.log('Deleteing camera config with _id: ', input.cameraId); + // NOTE: using findOneAndUpdate() with an aggregation pipeline to update + // Projects to preserve atomicity of the operation and avoid race conditions + // during bulk upload image ingestion. + // https://github.com/tnc-ca-geo/animl-api/issues/112 + const updatedProject = await Project.findOneAndUpdate( + { _id: context.user['curr_project'] }, + [ + { $addFields: { camIds: '$cameraConfigs._id' } }, + { + $pull: { cameraConfigs: { _id: input.cameraId } }, + }, + ], + { returnDocument: 'after' }, + ); + + console.log('updatedProject: ', updatedProject); + return updatedProject!; + }, + { retries: 2 }, + ); + } catch (err) { + if (err instanceof GraphQLError) throw err; + throw new InternalServerError(err as string); + } + } + static async createView( input: gql.CreateViewInput, context: Pick, @@ -288,7 +326,7 @@ export class ProjectModel { bail(new ForbiddenError(`View ${view?.name} is not editable`)); } - // appy updates & save project + // apply updates & save project Object.assign(view, input.diffs); const updatedProj = await project.save(); return updatedProj.views.find((v): v is HydratedSingleSubdocument => diff --git a/src/task/camera.ts b/src/task/camera.ts index f76ee695..e3c2a35b 100644 --- a/src/task/camera.ts +++ b/src/task/camera.ts @@ -9,6 +9,6 @@ export async function UpdateSerialNumber(task: TaskInput) { - console.log('DeleteCamera task:', task); - return { isOk: true }; + const context = { user: { is_superuser: true, curr_project: task.projectId } as User }; + return await CameraModel.deleteCamera(task.config, context); } From 4ba9ca04055054b03b30f6f17b6db604ef0e0eac Mon Sep 17 00:00:00 2001 From: jue-henry Date: Fri, 18 Oct 2024 16:43:26 -0700 Subject: [PATCH 05/23] removing camera from views --- src/api/db/models/Camera.ts | 35 ++++++++++++++++++++-------- src/api/db/models/Project.ts | 44 ++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 9 deletions(-) diff --git a/src/api/db/models/Camera.ts b/src/api/db/models/Camera.ts index 961bd67d..34e3ed24 100644 --- a/src/api/db/models/Camera.ts +++ b/src/api/db/models/Camera.ts @@ -403,20 +403,37 @@ export class CameraModel { } } + // NOTE: this method is called by the async task handler static async deleteCamera( input: gql.DeleteCameraInput, context: Pick, ): Promise { console.log('CameraModel.deleteCamera - input: ', input); - ProjectModel.deleteCameraConfig( - { - cameraId: input.cameraId, - }, - context, - ); - // TODO: delete deployments from views - // TODO: delete images associated with this camera - // TODO: unregister camera + try { + // Step 1: delete deployments from views + await ProjectModel.removeCameraFromViews( + { + cameraId: input.cameraId, + }, + context, + ); + // Step 2: delete camera record from project + await ProjectModel.deleteCameraConfig( + { + cameraId: input.cameraId, + }, + context, + ); + + // TODO: delete images associated with this camera + // Step 4: unregister camera + if ((await CameraModel.getWirelessCameras({ _ids: [input.cameraId] }, context)).length > 0) { + await CameraModel.unregisterCamera({ cameraId: input.cameraId }, context); + } + } catch (err) { + if (err instanceof GraphQLError) throw err; + throw new InternalServerError(err as string); + } return { isOk: true }; } } diff --git a/src/api/db/models/Project.ts b/src/api/db/models/Project.ts index 0d547f00..f5811f9f 100644 --- a/src/api/db/models/Project.ts +++ b/src/api/db/models/Project.ts @@ -372,6 +372,50 @@ export class ProjectModel { } } + static async removeCameraFromViews( + input: { cameraId: string }, + context: Pick, + ): Promise> { + try { + return await retry( + async () => { + // find view + let project = await Project.findOne({ _id: context.user['curr_project'] }); + if (!project) throw new NotFoundError('Project not found'); + console.log('originalProject: ', project); + + // get all deployment ids for the camera + const projectDeps = project.cameraConfigs + .find((cc) => cc._id === input.cameraId) + ?.deployments.map((d) => d._id.toString()); + + // finds all views that filter for the cameraId or any of the deployment ids + const viewsWithCamera = project.views.filter((v) => { + // get all deployment ids from the views + const viewDeps = v.filters.deployments; + return ( + v.filters.cameras?.includes(input.cameraId) || + (viewDeps && projectDeps && projectDeps.some((d) => viewDeps.includes(d))) + ); + }); + project.views.forEach((v) => { + if (viewsWithCamera.includes(v)) { + v.filters.cameras = v.filters.cameras?.filter((c) => c === input.cameraId); + v.filters.deployments = v.filters.deployments?.filter((d) => { + projectDeps?.includes(d); + }); + } + }); + return project.save(); + }, + { retries: 2 }, + ); + } catch (err) { + if (err instanceof GraphQLError) throw err; + throw new InternalServerError(err as string); + } + } + static async updateAutomationRules( { automationRules }: gql.UpdateAutomationRulesInput, context: Pick, From 18144687dfdc0b313801760de3740a1b15ed16a1 Mon Sep 17 00:00:00 2001 From: jue-henry Date: Mon, 21 Oct 2024 10:03:11 -0700 Subject: [PATCH 06/23] fixing tody --- src/api/db/models/Camera.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/db/models/Camera.ts b/src/api/db/models/Camera.ts index 34e3ed24..27809fee 100644 --- a/src/api/db/models/Camera.ts +++ b/src/api/db/models/Camera.ts @@ -425,7 +425,7 @@ export class CameraModel { context, ); - // TODO: delete images associated with this camera + // TODO: Step3: delete images associated with this camera // Step 4: unregister camera if ((await CameraModel.getWirelessCameras({ _ids: [input.cameraId] }, context)).length > 0) { await CameraModel.unregisterCamera({ cameraId: input.cameraId }, context); From 88b25496d5fb85c95543897c563afc9e00aa257e Mon Sep 17 00:00:00 2001 From: jue-henry Date: Wed, 4 Dec 2024 16:12:08 -0800 Subject: [PATCH 07/23] renaming function --- src/api/db/models/Camera.ts | 6 +++--- src/api/resolvers/Mutation.ts | 4 ++-- src/api/type-defs/root/Mutation.ts | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/api/db/models/Camera.ts b/src/api/db/models/Camera.ts index 27809fee..bb5e681f 100644 --- a/src/api/db/models/Camera.ts +++ b/src/api/db/models/Camera.ts @@ -404,11 +404,11 @@ export class CameraModel { } // NOTE: this method is called by the async task handler - static async deleteCamera( + static async deleteCameraConfig( input: gql.DeleteCameraInput, context: Pick, ): Promise { - console.log('CameraModel.deleteCamera - input: ', input); + console.log('CameraModel.deleteCameraConfig - input: ', input); try { // Step 1: delete deployments from views await ProjectModel.removeCameraFromViews( @@ -463,7 +463,7 @@ export default class AuthedCameraModel extends BaseAuthedModel { } @roleCheck(WRITE_DELETE_CAMERA_ROLES) - async deleteCamera(...args: MethodParams) { + async deleteCameraConfig(...args: MethodParams) { return await CameraModel.deleteCameraTask(...args); } } diff --git a/src/api/resolvers/Mutation.ts b/src/api/resolvers/Mutation.ts index 4d272599..7abb42ae 100644 --- a/src/api/resolvers/Mutation.ts +++ b/src/api/resolvers/Mutation.ts @@ -188,13 +188,13 @@ export default { return context.models.Camera.updateSerialNumber(input, context); }, - deleteCamera: async ( + deleteCameraConfig: async ( _: unknown, { input }: gql.MutationDeleteCameraArgs, context: Context, ): Promise => { console.log('Mutation.deleteCamera input:', input); - return context.models.Camera.deleteCamera(input, context); + return context.models.Camera.deleteCameraConfig(input, context); }, createProject: async ( diff --git a/src/api/type-defs/root/Mutation.ts b/src/api/type-defs/root/Mutation.ts index c908e752..e20c9782 100644 --- a/src/api/type-defs/root/Mutation.ts +++ b/src/api/type-defs/root/Mutation.ts @@ -41,7 +41,7 @@ export default /* GraphQL */ ` registerCamera(input: RegisterCameraInput!): RegisterCameraPayload unregisterCamera(input: UnregisterCameraInput!): UnregisterCameraPayload updateCameraSerialNumber(input: UpdateCameraSerialNumberInput!): Task - deleteCamera(input: DeleteCameraInput!): Task + deleteCameraConfig(input: DeleteCameraInput!): Task createView(input: CreateViewInput!): CreateViewPayload updateView(input: UpdateViewInput!): UpdateViewPayload From ce3392f52b02560a16ba43c32c4dae773f8ee1cb Mon Sep 17 00:00:00 2001 From: jue-henry Date: Wed, 4 Dec 2024 16:38:45 -0800 Subject: [PATCH 08/23] moving delete camera code to async task handler --- src/api/db/models/Camera.ts | 34 ------------------------------ src/task/camera.ts | 42 ++++++++++++++++++++++++++++++++++++- 2 files changed, 41 insertions(+), 35 deletions(-) diff --git a/src/api/db/models/Camera.ts b/src/api/db/models/Camera.ts index bb5e681f..3e3f86ca 100644 --- a/src/api/db/models/Camera.ts +++ b/src/api/db/models/Camera.ts @@ -402,40 +402,6 @@ export class CameraModel { throw new InternalServerError(err as string); } } - - // NOTE: this method is called by the async task handler - static async deleteCameraConfig( - input: gql.DeleteCameraInput, - context: Pick, - ): Promise { - console.log('CameraModel.deleteCameraConfig - input: ', input); - try { - // Step 1: delete deployments from views - await ProjectModel.removeCameraFromViews( - { - cameraId: input.cameraId, - }, - context, - ); - // Step 2: delete camera record from project - await ProjectModel.deleteCameraConfig( - { - cameraId: input.cameraId, - }, - context, - ); - - // TODO: Step3: delete images associated with this camera - // Step 4: unregister camera - if ((await CameraModel.getWirelessCameras({ _ids: [input.cameraId] }, context)).length > 0) { - await CameraModel.unregisterCamera({ cameraId: input.cameraId }, context); - } - } catch (err) { - if (err instanceof GraphQLError) throw err; - throw new InternalServerError(err as string); - } - return { isOk: true }; - } } export default class AuthedCameraModel extends BaseAuthedModel { diff --git a/src/task/camera.ts b/src/task/camera.ts index e3c2a35b..27ddcf1f 100644 --- a/src/task/camera.ts +++ b/src/task/camera.ts @@ -2,6 +2,8 @@ import { type User } from '../api/auth/authorization.js'; import { CameraModel } from '../api/db/models/Camera.js'; import { type TaskInput } from '../api/db/models/Task.js'; import type * as gql from '../@types/graphql.js'; +import { ProjectModel } from '../api/db/models/Project.js'; +import { DeleteImagesByFilter } from './image.js'; export async function UpdateSerialNumber(task: TaskInput) { const context = { user: { is_superuser: true, curr_project: task.projectId } as User }; @@ -10,5 +12,43 @@ export async function UpdateSerialNumber(task: TaskInput) { const context = { user: { is_superuser: true, curr_project: task.projectId } as User }; - return await CameraModel.deleteCamera(task.config, context); + + console.log('CameraModel.deleteCameraConfig - input: ', task.config); + try { + // Step 1: delete deployments from views + await ProjectModel.removeCameraFromViews( + { + cameraId: task.config.cameraId, + }, + context, + ); + // Step 2: delete camera record from project + await ProjectModel.deleteCameraConfig( + { + cameraId: task.config.cameraId, + }, + context, + ); + + // TODO: Step3: delete images associated with this camera + await DeleteImagesByFilter({ + projectId: task.projectId, + config: { + filters: { + cameras: [task.config.cameraId], + }, + }, + type: 'DeleteImagesByFilter', + user: task.user, + }); + // Step 4: unregister camera + if ( + (await CameraModel.getWirelessCameras({ _ids: [task.config.cameraId] }, context)).length > 0 + ) { + await CameraModel.unregisterCamera({ cameraId: task.config.cameraId }, context); + } + } catch (err) { + return { isOk: false, error: err }; + } + return { isOk: true }; } From 6442d0a860a265957882881044cc8e6c31ba3129 Mon Sep 17 00:00:00 2001 From: jue-henry Date: Wed, 4 Dec 2024 17:00:46 -0800 Subject: [PATCH 09/23] fix comment --- src/api/db/models/Project.ts | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/api/db/models/Project.ts b/src/api/db/models/Project.ts index f5811f9f..0510d756 100644 --- a/src/api/db/models/Project.ts +++ b/src/api/db/models/Project.ts @@ -250,18 +250,13 @@ export class ProjectModel { console.log('originalProject: ', project); console.log('Deleteing camera config with _id: ', input.cameraId); - // NOTE: using findOneAndUpdate() with an aggregation pipeline to update - // Projects to preserve atomicity of the operation and avoid race conditions - // during bulk upload image ingestion. - // https://github.com/tnc-ca-geo/animl-api/issues/112 + // NOTE: using findOneAndUpdate() to update Projects to preserve atomicity of the + // operation and avoid race conditions const updatedProject = await Project.findOneAndUpdate( { _id: context.user['curr_project'] }, - [ - { $addFields: { camIds: '$cameraConfigs._id' } }, - { - $pull: { cameraConfigs: { _id: input.cameraId } }, - }, - ], + { + $pull: { cameraConfigs: { _id: input.cameraId } }, + }, { returnDocument: 'after' }, ); From e9a49bd9d659fb7f43abc1a6b04b4f6723875445 Mon Sep 17 00:00:00 2001 From: jue-henry Date: Thu, 5 Dec 2024 14:17:12 -0800 Subject: [PATCH 10/23] extra space --- src/api/db/models/Task.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/db/models/Task.ts b/src/api/db/models/Task.ts index 52152856..8997f2fb 100644 --- a/src/api/db/models/Task.ts +++ b/src/api/db/models/Task.ts @@ -66,7 +66,7 @@ export class TaskModel { projectId: input.projectId, type: input.type, }); - console.log('TaskModel.create task:', task); + console.log('TaskModel.create task: ', task); const sqs = new SQS.SQSClient({ region: process.env.AWS_DEFAULT_REGION }); await task.save(); From 0bc48b4322894b8a58d951191e047682d79902c3 Mon Sep 17 00:00:00 2001 From: jue-henry Date: Thu, 5 Dec 2024 16:55:44 -0800 Subject: [PATCH 11/23] labels bug --- src/task/camera.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/task/camera.ts b/src/task/camera.ts index 27ddcf1f..74b8b8fd 100644 --- a/src/task/camera.ts +++ b/src/task/camera.ts @@ -36,6 +36,7 @@ export async function DeleteCamera(task: TaskInput) { config: { filters: { cameras: [task.config.cameraId], + labels: null, }, }, type: 'DeleteImagesByFilter', From 8fabe389696e582c9be1bb186113b7d0a003c4a6 Mon Sep 17 00:00:00 2001 From: jue-henry Date: Fri, 6 Dec 2024 12:04:10 -0800 Subject: [PATCH 12/23] adding undefined check --- package.json | 2 +- src/api/db/models/utils.ts | 8 ++++---- src/task/camera.ts | 1 - 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 32e71807..b6ac9b4c 100644 --- a/package.json +++ b/package.json @@ -77,4 +77,4 @@ "typescript": "^5.4.4", "undici": "^6.0.0" } -} \ No newline at end of file +} diff --git a/src/api/db/models/utils.ts b/src/api/db/models/utils.ts index 7f86d95c..db78c6bc 100644 --- a/src/api/db/models/utils.ts +++ b/src/api/db/models/utils.ts @@ -38,11 +38,11 @@ export function buildTagPipeline(tags: string[]): PipelineStage[] { pipeline.push({ $match: { - tags: { $in: tags } - } + tags: { $in: tags }, + }, }); - return pipeline + return pipeline; } export function buildLabelPipeline(labels: string[]): PipelineStage[] { @@ -194,7 +194,7 @@ export function buildPipeline( } // match reviewedFilter - if (reviewed !== null) { + if (reviewed !== null && reviewed !== undefined) { pipeline.push({ $match: { reviewed: reviewed, diff --git a/src/task/camera.ts b/src/task/camera.ts index 74b8b8fd..27ddcf1f 100644 --- a/src/task/camera.ts +++ b/src/task/camera.ts @@ -36,7 +36,6 @@ export async function DeleteCamera(task: TaskInput) { config: { filters: { cameras: [task.config.cameraId], - labels: null, }, }, type: 'DeleteImagesByFilter', From 0b876bcaf7cf1a923ef508df0b1dba865fa599db Mon Sep 17 00:00:00 2001 From: jue-henry Date: Tue, 10 Dec 2024 14:09:10 -0800 Subject: [PATCH 13/23] updating codegen --- package-lock.json | 3 +-- src/@types/graphql.ts | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index def90141..24831ac8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15320,7 +15320,6 @@ "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.4.tgz", "integrity": "sha512-7i/dt5kGl7qR4gwPRD2biwD2/SvBn3O04J77XKFgL2OnZtQw+AG9wnuS/csmu80nPRHLYE9E41fyEiG8nhH6/Q==", "hasInstallScript": true, - "license": "Apache-2.0", "dependencies": { "color": "^4.2.3", "detect-libc": "^2.0.3", @@ -17046,4 +17045,4 @@ } } } -} \ No newline at end of file +} diff --git a/src/@types/graphql.ts b/src/@types/graphql.ts index 9872270f..821605ff 100644 --- a/src/@types/graphql.ts +++ b/src/@types/graphql.ts @@ -606,7 +606,7 @@ export type Mutation = { createUpload?: Maybe; createUser?: Maybe; createView?: Maybe; - deleteCamera?: Maybe; + deleteCameraConfig?: Maybe; deleteDeployment?: Maybe; deleteImageComment?: Maybe; deleteImageTag?: Maybe; @@ -727,7 +727,7 @@ export type MutationCreateViewArgs = { }; -export type MutationDeleteCameraArgs = { +export type MutationDeleteCameraConfigArgs = { input: DeleteCameraInput; }; From 1d26b4362c2c3720fbad9449583f9ccda02061cb Mon Sep 17 00:00:00 2001 From: jue-henry Date: Tue, 10 Dec 2024 14:20:36 -0800 Subject: [PATCH 14/23] fix arg name --- src/api/resolvers/Mutation.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/resolvers/Mutation.ts b/src/api/resolvers/Mutation.ts index 7abb42ae..9eff1b3f 100644 --- a/src/api/resolvers/Mutation.ts +++ b/src/api/resolvers/Mutation.ts @@ -190,7 +190,7 @@ export default { deleteCameraConfig: async ( _: unknown, - { input }: gql.MutationDeleteCameraArgs, + { input }: gql.MutationDeleteCameraConfigArgs, context: Context, ): Promise => { console.log('Mutation.deleteCamera input:', input); From 3448a2fa8d9f3c448d9a85109c506c6d628fce57 Mon Sep 17 00:00:00 2001 From: jue-henry Date: Wed, 11 Dec 2024 12:14:26 -0800 Subject: [PATCH 15/23] fix view bug --- src/api/db/models/Project.ts | 37 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/src/api/db/models/Project.ts b/src/api/db/models/Project.ts index 0510d756..c8a98454 100644 --- a/src/api/db/models/Project.ts +++ b/src/api/db/models/Project.ts @@ -249,7 +249,7 @@ export class ProjectModel { if (!project) throw new NotFoundError('Project not found'); console.log('originalProject: ', project); - console.log('Deleteing camera config with _id: ', input.cameraId); + console.log('Deleting camera config with _id: ', input.cameraId); // NOTE: using findOneAndUpdate() to update Projects to preserve atomicity of the // operation and avoid race conditions const updatedProject = await Project.findOneAndUpdate( @@ -377,30 +377,29 @@ export class ProjectModel { // find view let project = await Project.findOne({ _id: context.user['curr_project'] }); if (!project) throw new NotFoundError('Project not found'); - console.log('originalProject: ', project); // get all deployment ids for the camera - const projectDeps = project.cameraConfigs - .find((cc) => cc._id === input.cameraId) - ?.deployments.map((d) => d._id.toString()); - - // finds all views that filter for the cameraId or any of the deployment ids - const viewsWithCamera = project.views.filter((v) => { - // get all deployment ids from the views - const viewDeps = v.filters.deployments; - return ( + const projectDeps = + project.cameraConfigs + .find((cc) => cc._id === input.cameraId) + ?.deployments.map((d) => d._id.toString()) ?? []; + + console.log('deployments to be removed from views: ', projectDeps); + + project.views.forEach((v, index) => { + // if view filters has the camera id or any of the deployment ids + if ( v.filters.cameras?.includes(input.cameraId) || - (viewDeps && projectDeps && projectDeps.some((d) => viewDeps.includes(d))) - ); - }); - project.views.forEach((v) => { - if (viewsWithCamera.includes(v)) { + projectDeps.some((d) => v.filters.deployments?.includes(d)) + ) { v.filters.cameras = v.filters.cameras?.filter((c) => c === input.cameraId); - v.filters.deployments = v.filters.deployments?.filter((d) => { - projectDeps?.includes(d); - }); + v.filters.deployments = v.filters.deployments?.filter( + (d) => !projectDeps.includes(d), + ); + project.views[index] = v; } }); + return project.save(); }, { retries: 2 }, From 24b9259858bb47fa6a491bbe725652291fdb5a00 Mon Sep 17 00:00:00 2001 From: jue-henry Date: Wed, 11 Dec 2024 12:19:16 -0800 Subject: [PATCH 16/23] fixing comment --- src/api/db/models/Project.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/db/models/Project.ts b/src/api/db/models/Project.ts index c8a98454..e9220b5e 100644 --- a/src/api/db/models/Project.ts +++ b/src/api/db/models/Project.ts @@ -374,7 +374,7 @@ export class ProjectModel { try { return await retry( async () => { - // find view + // find project let project = await Project.findOne({ _id: context.user['curr_project'] }); if (!project) throw new NotFoundError('Project not found'); From b5611b650f8229f8c3edd6f867b69ecf045cfa88 Mon Sep 17 00:00:00 2001 From: jue-henry Date: Wed, 11 Dec 2024 12:53:09 -0800 Subject: [PATCH 17/23] removing todo --- src/task/camera.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/task/camera.ts b/src/task/camera.ts index 27ddcf1f..2e21a9d4 100644 --- a/src/task/camera.ts +++ b/src/task/camera.ts @@ -30,7 +30,7 @@ export async function DeleteCamera(task: TaskInput) { context, ); - // TODO: Step3: delete images associated with this camera + // Step3: delete images associated with this camera await DeleteImagesByFilter({ projectId: task.projectId, config: { From 7c18179e7e6cc12cd91a1d4ce2863eee5e8ffc4b Mon Sep 17 00:00:00 2001 From: jue-henry Date: Thu, 12 Dec 2024 16:05:53 -0800 Subject: [PATCH 18/23] adding more error catching --- src/api/db/models/Project.ts | 2 +- src/task/camera.ts | 9 ++++++--- src/task/image.ts | 17 +++++++++++++---- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/api/db/models/Project.ts b/src/api/db/models/Project.ts index e9220b5e..727d5162 100644 --- a/src/api/db/models/Project.ts +++ b/src/api/db/models/Project.ts @@ -392,7 +392,7 @@ export class ProjectModel { v.filters.cameras?.includes(input.cameraId) || projectDeps.some((d) => v.filters.deployments?.includes(d)) ) { - v.filters.cameras = v.filters.cameras?.filter((c) => c === input.cameraId); + v.filters.cameras = v.filters.cameras?.filter((c) => c !== input.cameraId); v.filters.deployments = v.filters.deployments?.filter( (d) => !projectDeps.includes(d), ); diff --git a/src/task/camera.ts b/src/task/camera.ts index 2e21a9d4..2da59d01 100644 --- a/src/task/camera.ts +++ b/src/task/camera.ts @@ -12,8 +12,8 @@ export async function UpdateSerialNumber(task: TaskInput) { const context = { user: { is_superuser: true, curr_project: task.projectId } as User }; - console.log('CameraModel.deleteCameraConfig - input: ', task.config); + const errors = []; try { // Step 1: delete deployments from views await ProjectModel.removeCameraFromViews( @@ -31,7 +31,7 @@ export async function DeleteCamera(task: TaskInput) { ); // Step3: delete images associated with this camera - await DeleteImagesByFilter({ + const deleteRes = await DeleteImagesByFilter({ projectId: task.projectId, config: { filters: { @@ -41,6 +41,9 @@ export async function DeleteCamera(task: TaskInput) { type: 'DeleteImagesByFilter', user: task.user, }); + if (deleteRes.errors) { + errors.push(...deleteRes.errors); + } // Step 4: unregister camera if ( (await CameraModel.getWirelessCameras({ _ids: [task.config.cameraId] }, context)).length > 0 @@ -50,5 +53,5 @@ export async function DeleteCamera(task: TaskInput) { } catch (err) { return { isOk: false, error: err }; } - return { isOk: true }; + return { isOk: true, errors: errors }; } diff --git a/src/task/image.ts b/src/task/image.ts index 97b598a1..f5b34562 100644 --- a/src/task/image.ts +++ b/src/task/image.ts @@ -15,9 +15,14 @@ export async function DeleteImagesByFilter(task: TaskInput 0) { const batch = images.results.map((image) => image._id); - await ImageModel.deleteImages({ imageIds: batch }, context); + const res = await ImageModel.deleteImages({ imageIds: batch }, context); + if (res.errors) { + errors.push(...res.errors); + } if (images.hasNext) { images = await ImageModel.queryByFilter( { @@ -32,7 +37,7 @@ export async function DeleteImagesByFilter(task: TaskInput) { @@ -44,9 +49,13 @@ export async function DeleteImages(task: TaskInput) { */ const context = { user: { is_superuser: true, curr_project: task.projectId } as User }; const imagesToDelete = task.config.imageIds?.slice() ?? []; + const errors = []; while (imagesToDelete.length > 0) { const batch = imagesToDelete.splice(0, ImageModel.DELETE_IMAGES_BATCH_SIZE); - await ImageModel.deleteImages({ imageIds: batch }, context); + const res = await ImageModel.deleteImages({ imageIds: batch }, context); + if (res.errors) { + errors.push(...res.errors); + } } - return { imageIds: task.config.imageIds }; + return { imageIds: task.config.imageIds, errors: errors }; } From 49cd1859dde6946cdbcd1c4d5ab27c6bcec72172 Mon Sep 17 00:00:00 2001 From: jue-henry Date: Fri, 13 Dec 2024 14:49:09 -0800 Subject: [PATCH 19/23] adding new method to remove projectRegistrations from camera --- src/api/db/models/Camera.ts | 62 +++++++++++++++++++++++++++++++++++++ src/task/camera.ts | 2 +- 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/src/api/db/models/Camera.ts b/src/api/db/models/Camera.ts index 3e3f86ca..50b631bf 100644 --- a/src/api/db/models/Camera.ts +++ b/src/api/db/models/Camera.ts @@ -237,6 +237,68 @@ export class CameraModel { } } + static async removeProjectRegistration( + input: { cameraId: string }, + context: Pick, + ): Promise<{ wirelessCameras: WirelessCameraSchema[]; project?: ProjectSchema }> { + const projectId = context.user['curr_project']; + + try { + const wirelessCameras = await WirelessCamera.find(); + const cam = wirelessCameras.find((c) => idMatch(c._id, input.cameraId)); + + if (!cam) { + const msg = `Couldn't find camera record for camera ${input.cameraId}`; + throw new CameraRegistrationError(msg); + } + const activeReg = cam.projRegistrations.find((pr) => pr.active); + + // if active registration === curr_project, + // set default_project registration to active + if (activeReg?.projectId === projectId) { + const defaultProjReg = cam.projRegistrations.find( + (pr) => pr.projectId === 'default_project', + ); + if (defaultProjReg) defaultProjReg.active = true; + else { + cam.projRegistrations.push({ + _id: new ObjectId(), + projectId: 'default_project', + active: true, + }); + } + } + const currProjIndex = cam.projRegistrations.findIndex((pr) => pr.projectId === projectId); + cam.projRegistrations.splice(currProjIndex, 1); + await cam.save(); + + // make sure there's a Project.cameraConfig record for this camera + // in the default_project and create one if not + let defaultProj = await Project.findOne({ _id: 'default_project' }); + if (!defaultProj) { + throw new CameraRegistrationError('Could not find default project'); + } + + let addedNewCamConfig = false; + const camConfig = defaultProj.cameraConfigs.find((cc) => idMatch(cc._id, input.cameraId)); + if (!camConfig) { + defaultProj = (await ProjectModel.createCameraConfig( + { + projectId: 'default_project', + cameraId: input.cameraId, + }, + context, + ))!; + addedNewCamConfig = true; + } + + return { wirelessCameras, ...(addedNewCamConfig && { project: defaultProj }) }; + } catch (err) { + if (err instanceof GraphQLError) throw err; + throw new InternalServerError(err as string); + } + } + static async updateSerialNumberTask( input: gql.UpdateCameraSerialNumberInput, context: Pick, diff --git a/src/task/camera.ts b/src/task/camera.ts index 2da59d01..e0a25464 100644 --- a/src/task/camera.ts +++ b/src/task/camera.ts @@ -48,7 +48,7 @@ export async function DeleteCamera(task: TaskInput) { if ( (await CameraModel.getWirelessCameras({ _ids: [task.config.cameraId] }, context)).length > 0 ) { - await CameraModel.unregisterCamera({ cameraId: task.config.cameraId }, context); + await CameraModel.removeProjectRegistration({ cameraId: task.config.cameraId }, context); } } catch (err) { return { isOk: false, error: err }; From 88ab39818520b328b7cd7e8a4efe37414fd83433 Mon Sep 17 00:00:00 2001 From: jue-henry Date: Fri, 13 Dec 2024 15:15:31 -0800 Subject: [PATCH 20/23] optimization --- src/api/db/models/Camera.ts | 50 ++++++++++++++++++++----------------- 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/src/api/db/models/Camera.ts b/src/api/db/models/Camera.ts index 50b631bf..ce363d07 100644 --- a/src/api/db/models/Camera.ts +++ b/src/api/db/models/Camera.ts @@ -251,6 +251,9 @@ export class CameraModel { const msg = `Couldn't find camera record for camera ${input.cameraId}`; throw new CameraRegistrationError(msg); } + let addedNewCamConfig = false; + let defaultProj: Maybe = null; + const activeReg = cam.projRegistrations.find((pr) => pr.active); // if active registration === curr_project, @@ -259,8 +262,29 @@ export class CameraModel { const defaultProjReg = cam.projRegistrations.find( (pr) => pr.projectId === 'default_project', ); - if (defaultProjReg) defaultProjReg.active = true; - else { + if (defaultProjReg) { + defaultProjReg.active = true; + } else { + // make sure there's a Project.cameraConfig record for this camera + // in the default_project and create one if not + defaultProj = await Project.findOne({ _id: 'default_project' }); + if (!defaultProj) { + throw new CameraRegistrationError('Could not find default project'); + } + + const camConfig = defaultProj.cameraConfigs.find((cc) => idMatch(cc._id, input.cameraId)); + if (!camConfig) { + defaultProj = (await ProjectModel.createCameraConfig( + { + projectId: 'default_project', + cameraId: input.cameraId, + }, + context, + ))!; + addedNewCamConfig = true; + } + + // create new project registration to default project cam.projRegistrations.push({ _id: new ObjectId(), projectId: 'default_project', @@ -272,27 +296,7 @@ export class CameraModel { cam.projRegistrations.splice(currProjIndex, 1); await cam.save(); - // make sure there's a Project.cameraConfig record for this camera - // in the default_project and create one if not - let defaultProj = await Project.findOne({ _id: 'default_project' }); - if (!defaultProj) { - throw new CameraRegistrationError('Could not find default project'); - } - - let addedNewCamConfig = false; - const camConfig = defaultProj.cameraConfigs.find((cc) => idMatch(cc._id, input.cameraId)); - if (!camConfig) { - defaultProj = (await ProjectModel.createCameraConfig( - { - projectId: 'default_project', - cameraId: input.cameraId, - }, - context, - ))!; - addedNewCamConfig = true; - } - - return { wirelessCameras, ...(addedNewCamConfig && { project: defaultProj }) }; + return { wirelessCameras, ...(addedNewCamConfig && { project: defaultProj! }) }; } catch (err) { if (err instanceof GraphQLError) throw err; throw new InternalServerError(err as string); From 974fff2063f15a43b1ac39048568b2e3bb789d7c Mon Sep 17 00:00:00 2001 From: jue-henry Date: Tue, 24 Dec 2024 12:00:22 -0800 Subject: [PATCH 21/23] Revert "optimization" This reverts commit 88ab39818520b328b7cd7e8a4efe37414fd83433. --- src/api/db/models/Camera.ts | 50 +++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 27 deletions(-) diff --git a/src/api/db/models/Camera.ts b/src/api/db/models/Camera.ts index ce363d07..50b631bf 100644 --- a/src/api/db/models/Camera.ts +++ b/src/api/db/models/Camera.ts @@ -251,9 +251,6 @@ export class CameraModel { const msg = `Couldn't find camera record for camera ${input.cameraId}`; throw new CameraRegistrationError(msg); } - let addedNewCamConfig = false; - let defaultProj: Maybe = null; - const activeReg = cam.projRegistrations.find((pr) => pr.active); // if active registration === curr_project, @@ -262,29 +259,8 @@ export class CameraModel { const defaultProjReg = cam.projRegistrations.find( (pr) => pr.projectId === 'default_project', ); - if (defaultProjReg) { - defaultProjReg.active = true; - } else { - // make sure there's a Project.cameraConfig record for this camera - // in the default_project and create one if not - defaultProj = await Project.findOne({ _id: 'default_project' }); - if (!defaultProj) { - throw new CameraRegistrationError('Could not find default project'); - } - - const camConfig = defaultProj.cameraConfigs.find((cc) => idMatch(cc._id, input.cameraId)); - if (!camConfig) { - defaultProj = (await ProjectModel.createCameraConfig( - { - projectId: 'default_project', - cameraId: input.cameraId, - }, - context, - ))!; - addedNewCamConfig = true; - } - - // create new project registration to default project + if (defaultProjReg) defaultProjReg.active = true; + else { cam.projRegistrations.push({ _id: new ObjectId(), projectId: 'default_project', @@ -296,7 +272,27 @@ export class CameraModel { cam.projRegistrations.splice(currProjIndex, 1); await cam.save(); - return { wirelessCameras, ...(addedNewCamConfig && { project: defaultProj! }) }; + // make sure there's a Project.cameraConfig record for this camera + // in the default_project and create one if not + let defaultProj = await Project.findOne({ _id: 'default_project' }); + if (!defaultProj) { + throw new CameraRegistrationError('Could not find default project'); + } + + let addedNewCamConfig = false; + const camConfig = defaultProj.cameraConfigs.find((cc) => idMatch(cc._id, input.cameraId)); + if (!camConfig) { + defaultProj = (await ProjectModel.createCameraConfig( + { + projectId: 'default_project', + cameraId: input.cameraId, + }, + context, + ))!; + addedNewCamConfig = true; + } + + return { wirelessCameras, ...(addedNewCamConfig && { project: defaultProj }) }; } catch (err) { if (err instanceof GraphQLError) throw err; throw new InternalServerError(err as string); From 6330625a137204e0fe2b2f3128d08a783346b3d1 Mon Sep 17 00:00:00 2001 From: jue-henry Date: Tue, 24 Dec 2024 15:17:12 -0800 Subject: [PATCH 22/23] adding comment --- src/api/db/models/Camera.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/api/db/models/Camera.ts b/src/api/db/models/Camera.ts index 50b631bf..664d45bf 100644 --- a/src/api/db/models/Camera.ts +++ b/src/api/db/models/Camera.ts @@ -237,6 +237,7 @@ export class CameraModel { } } + // NOTE: this function is called by the async task handler as part of the delete camera task static async removeProjectRegistration( input: { cameraId: string }, context: Pick, From 1bd44ccd2dd4aedbc4313a0416e7dcdb9f4c3318 Mon Sep 17 00:00:00 2001 From: jue-henry Date: Tue, 24 Dec 2024 15:28:07 -0800 Subject: [PATCH 23/23] forced upgrade --- Dockerfile-test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile-test b/Dockerfile-test index e73b86f1..c3d7b649 100644 --- a/Dockerfile-test +++ b/Dockerfile-test @@ -7,7 +7,7 @@ COPY ./ $HOME/ RUN apt-get update \ && DEBIAN_FRONTEND=noninteractive apt-get install -y curl git -RUN export NODEV='20.12.2' \ +RUN export NODEV='20.17.0' \ && curl "https://nodejs.org/dist/v${NODEV}/node-v${NODEV}-linux-x64.tar.gz" | tar -xzv \ && cp ./node-v${NODEV}-linux-x64/bin/node /usr/bin/ \ && ./node-v${NODEV}-linux-x64/bin/npm install -g npm