diff --git a/backend/src/models/Inference.ts b/backend/src/models/Inference.ts index 1139087e3..509d5ef7e 100644 --- a/backend/src/models/Inference.ts +++ b/backend/src/models/Inference.ts @@ -1,4 +1,5 @@ -import { Document, model, Schema } from 'mongoose' +import { model, Schema } from 'mongoose' +import MongooseDelete, { SoftDeleteDocument } from 'mongoose-delete' export interface InferenceSetting { processorType: string @@ -15,14 +16,16 @@ export interface InferenceInterface { settings: InferenceSetting + deleted: boolean + createdBy: string createdAt: Date updatedAt: Date } -export type InferenceDoc = InferenceInterface & Document +export type InferenceDoc = InferenceInterface & SoftDeleteDocument -const InferenceSchema = new Schema( +const InferenceSchema = new Schema( { modelId: { type: String, required: true }, image: { type: String, required: true }, @@ -55,8 +58,15 @@ const InferenceSchema = new Schema( }, ) +InferenceSchema.plugin(MongooseDelete, { + overrideMethods: 'all', + deletedBy: true, + deletedByType: String, + deletedAt: true, +}) + InferenceSchema.index({ modelId: 1, image: 1, tag: 1 }, { unique: true }) -const InferenceModel = model('v2_Model_Inference', InferenceSchema) +const InferenceModel = model('v2_Model_Inference', InferenceSchema) export default InferenceModel diff --git a/backend/src/models/ModelCardRevision.ts b/backend/src/models/ModelCardRevision.ts index ce026a571..3440709b2 100644 --- a/backend/src/models/ModelCardRevision.ts +++ b/backend/src/models/ModelCardRevision.ts @@ -1,4 +1,5 @@ -import { Document, model, Schema } from 'mongoose' +import { model, Schema } from 'mongoose' +import MongooseDelete, { SoftDeleteDocument } from 'mongoose-delete' import { ModelCardInterface } from './Model.js' @@ -7,14 +8,15 @@ import { ModelCardInterface } from './Model.js' // client. export interface ModelCardRevisionInterface extends ModelCardInterface { modelId: string + deleted: boolean } // The doc type includes all values in the plain interface, as well as all the // properties and functions that Mongoose provides. If a function takes in an // object from Mongoose it should use this interface -export type ModelCardRevisionDoc = ModelCardRevisionInterface & Document +export type ModelCardRevisionDoc = ModelCardRevisionInterface & SoftDeleteDocument -const ModelCardRevisionSchema = new Schema( +const ModelCardRevisionSchema = new Schema( { modelId: { type: String, required: true }, schemaId: { type: String, required: true }, @@ -36,6 +38,13 @@ const ModelCardRevisionSchema = new Schema( // to learn more. ModelCardRevisionSchema.index({ modelId: 1, version: 1 }, { unique: true }) -const ModelCardRevisionModel = model('v2_Model_Card_Revision', ModelCardRevisionSchema) +ModelCardRevisionSchema.plugin(MongooseDelete, { + overrideMethods: 'all', + deletedBy: true, + deletedByType: String, + deletedAt: true, +}) + +const ModelCardRevisionModel = model('v2_Model_Card_Revision', ModelCardRevisionSchema) export default ModelCardRevisionModel diff --git a/backend/src/scripts/modelSoftDelete.ts b/backend/src/scripts/modelSoftDelete.ts new file mode 100644 index 000000000..3ad7e142a --- /dev/null +++ b/backend/src/scripts/modelSoftDelete.ts @@ -0,0 +1,79 @@ +import AccessRequestModel from '../models/AccessRequest.js' +import FileModel from '../models/File.js' +import InferenceModel from '../models/Inference.js' +import ModelModel from '../models/Model.js' +import ModelCardRevisionModel from '../models/ModelCardRevision.js' +import ReleaseModel from '../models/Release.js' +import ResponseModel from '../models/Response.js' +import ReviewModel from '../models/Review.js' +import WebhookModel from '../models/Webhook.js' +import log from '../services/log.js' +import { connectToMongoose } from '../utils/database.js' + +async function script() { + const modelId = process.argv.slice(2) + + if (!modelId || !process.argv[2]) { + log.error('No model ID option. Please use format "npm run script -- modelSoftDelete "') + return + } + + await connectToMongoose() + + const model = await ModelModel.findOne({ id: modelId }) + if (!model) { + log.error(`Cannot find model using ID ${modelId}`) + return + } + const releases = await ReleaseModel.find({ modelId }) + log.info(`Deleting ${releases.length} releases`) + for (const release of releases) { + await release.delete() + } + + const accesses = await AccessRequestModel.find({ modelId }) + log.info(`Deleting ${accesses.length} access requests`) + for (const access of accesses) { + await access.delete() + } + + const revisions = await ModelCardRevisionModel.find({ modelId }) + log.info(`Deleting ${revisions.length} model card revisions`) + for (const revision of revisions) { + await revision.delete() + } + + const reviews = await ReviewModel.find({ modelId }) + log.info(`Deleting ${reviews.length} reviews`) + for (const review of reviews) { + const responses = await ResponseModel.find({ parentId: review._id }) + log.info(`Deleting ${responses.length} responses from review ${review._id} `) + for (const response of responses) { + await response.delete() + } + await review.delete() + } + + const files = await FileModel.find({ modelId }) + log.info(`Deleting ${accesses.length} files`) + for (const file of files) { + await file.delete() + } + + const webhooks = await WebhookModel.find({ modelId }) + log.info(`Deleting ${webhooks.length} webhooks`) + for (const webhook of webhooks) { + await webhook.delete() + } + + const inferences = await InferenceModel.find({ modelId }) + log.info(`Deleting ${inferences.length} inferences`) + for (const inference of inferences) { + await inference.delete() + } + + await model.delete() + return +} + +script() diff --git a/backend/src/scripts/runScript.ts b/backend/src/scripts/runScript.ts index 7ae562f40..98cb19d5a 100644 --- a/backend/src/scripts/runScript.ts +++ b/backend/src/scripts/runScript.ts @@ -10,9 +10,10 @@ const { exec } = shelljs // npx tsx src/scripts/uploadExampleModel.ts export default async function runScript() { const argv = await yargs(hideBin(process.argv)).usage('Usage: $0 [script]').argv - const script = argv._ + const script = argv._[0] + const args = argv._.slice(1) - exec(`npx tsx src/scripts/${script}.ts`) + exec(`npx tsx src/scripts/${script}.ts ${args}`) } runScript() diff --git a/backend/src/services/modelCardExport.ts b/backend/src/services/modelCardExport.ts index ca2e824ff..614db66a9 100644 --- a/backend/src/services/modelCardExport.ts +++ b/backend/src/services/modelCardExport.ts @@ -50,7 +50,7 @@ export async function getModelCardHtml( throw new Error('Failed to find model card to export.') } - const modelCardRevision: ModelCardRevisionInterface = { ...modelCard, modelId } + const modelCardRevision: ModelCardRevisionInterface = { ...modelCard, modelId, deleted: model.deleted } const html = await renderToHtml(model, modelCardRevision) return { html, modelCard } diff --git a/backend/test/routes/model/modelcard/__snapshots__/putModelCard.spec.ts.snap b/backend/test/routes/model/modelcard/__snapshots__/putModelCard.spec.ts.snap index ec38c86fa..040a8af88 100644 --- a/backend/test/routes/model/modelcard/__snapshots__/putModelCard.spec.ts.snap +++ b/backend/test/routes/model/modelcard/__snapshots__/putModelCard.spec.ts.snap @@ -4,6 +4,7 @@ exports[`routes > model > putModelCard > 200 > ok 1`] = ` { "card": { "_id": "6776901b879d08e34b599d7e", + "deleted": false, "id": "6776901b879d08e34b599d7e", "modelId": "test", }, @@ -15,6 +16,7 @@ exports[`routes > model > putModelCard > audit > expected call 1`] = `"vespillo" exports[`routes > model > putModelCard > audit > expected call 2`] = ` { "_id": "6776901b879d08e34b599d7e", + "deleted": false, "modelId": "test", } `; diff --git a/backend/test/routes/schema/__snapshots__/patchSchema.spec.ts.snap b/backend/test/routes/schema/__snapshots__/patchSchema.spec.ts.snap index b01f634de..49165442f 100644 --- a/backend/test/routes/schema/__snapshots__/patchSchema.spec.ts.snap +++ b/backend/test/routes/schema/__snapshots__/patchSchema.spec.ts.snap @@ -9,26 +9,6 @@ exports[`routes > schema > patchSchema > audit > expected call 1`] = ` } `; -exports[`routes > schema > patchSchema > patchSchema > audit > expected call 1`] = ` -{ - "active": false, - "description": "vespillo", - "hidden": false, - "name": "Jaime Hilll", -} -`; - -exports[`routes > schema > patchSchema > patchSchema > successfully updates the schema 1`] = ` -{ - "schema": { - "active": false, - "description": "vespillo", - "hidden": false, - "name": "Jaime Hilll", - }, -} -`; - exports[`routes > schema > patchSchema > successfully updates the schema 1`] = ` { "schema": { diff --git a/backend/test/services/modelCardExport.spec.ts b/backend/test/services/modelCardExport.spec.ts index 7615706c6..d56c17868 100644 --- a/backend/test/services/modelCardExport.spec.ts +++ b/backend/test/services/modelCardExport.spec.ts @@ -19,6 +19,7 @@ describe('services > export', () => { version: 1, createdBy: 'Joe Bloggs', metadata: {}, + deleted: false, } const mockSchema = { jsonSchema: { type: 'object', properties: {} } }