added cleanup mutation (#157)

* added cleanup mutation

* default to empty array
This commit is contained in:
Arwid Thornström
2023-10-17 11:18:29 +02:00
committed by GitHub
parent 91d6aa1d1a
commit 0ffeacc638
4 changed files with 127 additions and 0 deletions
+50
View File
@@ -3,6 +3,7 @@ import {
CopyObjectCommand, CopyObjectCommand,
DeleteObjectCommand, DeleteObjectCommand,
PutObjectCommand, PutObjectCommand,
ListObjectsV2Command,
} from '@aws-sdk/client-s3'; } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner'; import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
export const EXPIRES_DEFAULT_SECS = 300; export const EXPIRES_DEFAULT_SECS = 300;
@@ -46,3 +47,52 @@ export const moveS3File = async (
}); });
return s3.send(deleteCmd); return s3.send(deleteCmd);
}; };
export const listS3Files = async (
bucket: string,
prefix: string,
): Promise<any> => {
const s3Client = new S3Client({ region: 'eu-west-1' });
const command = new ListObjectsV2Command({
Bucket: bucket,
Prefix: prefix,
MaxKeys: 1000,
});
try {
let isTruncated = true;
const files = [];
while (isTruncated) {
const { Contents, IsTruncated, NextContinuationToken } =
await s3Client.send(command);
files.push(...Contents);
isTruncated = IsTruncated;
command.input.ContinuationToken = NextContinuationToken;
}
return files;
} catch (err) {
console.error(err);
}
return [];
};
export const deleteFiles = async (
bucket: string,
key: string,
): Promise<any> => {
try {
const s3Client = new S3Client({ region: 'eu-west-1' });
const deleteCmd = new DeleteObjectCommand({
Bucket: bucket,
Key: key,
});
return s3Client.send(deleteCmd);
} catch (err) {
console.error(err);
}
return null;
};
+49
View File
@@ -3,6 +3,7 @@ import { BaseSQLDataSource } from './BaseSQLDataSource';
import { camelcase } from 'stringcase'; import { camelcase } from 'stringcase';
import slug from 'slug'; import slug from 'slug';
import * as sql from './sql'; import * as sql from './sql';
import * as CONFIG from '../config';
import { import {
Product, Product,
ProductGroup, ProductGroup,
@@ -35,6 +36,8 @@ import { PrintProductDefaultsAPI } from './printproduct-defaults-api';
import { GraphQLError } from 'graphql'; import { GraphQLError } from 'graphql';
import { MINUTE } from './utils'; import { MINUTE } from './utils';
import { ScopeAccess, Scopes } from '../cognito/access-control'; import { ScopeAccess, Scopes } from '../cognito/access-control';
import { Interior } from '../types/interior-types';
import { deleteFiles, listS3Files } from '../aws/s3';
export class ProductAPI extends BaseSQLDataSource { export class ProductAPI extends BaseSQLDataSource {
textsApi: TextsAPI; textsApi: TextsAPI;
@@ -1005,6 +1008,52 @@ export class ProductAPI extends BaseSQLDataSource {
return Promise.all(insertPromises); return Promise.all(insertPromises);
} }
/**
* @function cleanupInteriors
* @description Removes all not used interiors from a product
*/
async cleanupInteriors(
productId: number,
currentInteriors: Interior[],
): Promise<{ active: string[]; deleted: string[] }> {
ScopeAccess.validate(this.user).all([
Scopes.PRODUCTS_WRITE,
Scopes.INTERIORS_WRITE,
]);
// Get all interior files for product on s3
const files = await listS3Files(
CONFIG.imageBucket,
`interiors/${productId}`,
);
const shouldKeep = [];
const shouldRemove = [];
// Convert to match key format from s3
const currentUris = currentInteriors.map((item) => item.uri.substring(1));
// Go through all files on s3 and organize as delete or keep
for (const file of files) {
if (currentUris.includes(file.Key)) {
shouldKeep.push(file.Key);
} else {
shouldRemove.push(file.Key);
}
}
// Setup all delete actions
const deletePromises = shouldRemove.map((file) => {
return deleteFiles(CONFIG.imageBucket, file);
});
// Run all delete in parallell
await Promise.all(deletePromises);
// Return what we did
return { active: shouldKeep, deleted: shouldRemove };
}
/** /**
* @function setProportionsWarning * @function setProportionsWarning
* @description Sets propotion warning-values to a product * @description Sets propotion warning-values to a product
+22
View File
@@ -232,6 +232,28 @@ export const productMutationTypeDefs = {
return (<ProductAPI>dataSources.productApi).getProduct(id); return (<ProductAPI>dataSources.productApi).getProduct(id);
}, },
/**
* @function cleanupInteriors
* @description Will remove all unused interior images for s3
*/
async cleanupInteriors(_, { productId }, { dataSources }) {
const product = await (<ProductAPI>dataSources.productApi).getProduct(
productId,
);
const interiors = [];
for (const printProduct of product.printProducts) {
const i = await (<InteriorAPI>(
dataSources.interiorApi
)).getPrintProductInteriors(printProduct.id);
interiors.push(...i);
}
const response = await (<ProductAPI>(
dataSources.productApi
)).cleanupInteriors(productId, interiors);
return response;
},
/** /**
* @function productInfo * @function productInfo
* @description Mutate base information about a product * @description Mutate base information about a product
+6
View File
@@ -462,6 +462,11 @@ type IdNumberResult {
id: Int! id: Int!
} }
type CleanupResponse {
active: [String]
deleted: [String]
}
type Mutation { type Mutation {
""" """
Product Product
@@ -524,6 +529,7 @@ type Mutation {
productGroup(productId: Int!, groupIds: [Int]): Product productGroup(productId: Int!, groupIds: [Int]): Product
productGroupInteriors(productId: Int!, groupId: Int!, uris: [String]): Product productGroupInteriors(productId: Int!, groupId: Int!, uris: [String]): Product
generateInteriors(productId: Int!, roomIds: [String]!): StringResult generateInteriors(productId: Int!, roomIds: [String]!): StringResult
cleanupInteriors(productId: Int!): CleanupResponse
addOwnInteriorToPrintProduct( addOwnInteriorToPrintProduct(
printId: Int! printId: Int!
uploadedS3Key: String! uploadedS3Key: String!