added cleanup mutation (#157)
* added cleanup mutation * default to empty array
This commit is contained in:
@@ -3,6 +3,7 @@ import {
|
||||
CopyObjectCommand,
|
||||
DeleteObjectCommand,
|
||||
PutObjectCommand,
|
||||
ListObjectsV2Command,
|
||||
} from '@aws-sdk/client-s3';
|
||||
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
|
||||
export const EXPIRES_DEFAULT_SECS = 300;
|
||||
@@ -46,3 +47,52 @@ export const moveS3File = async (
|
||||
});
|
||||
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;
|
||||
};
|
||||
|
||||
@@ -3,6 +3,7 @@ import { BaseSQLDataSource } from './BaseSQLDataSource';
|
||||
import { camelcase } from 'stringcase';
|
||||
import slug from 'slug';
|
||||
import * as sql from './sql';
|
||||
import * as CONFIG from '../config';
|
||||
import {
|
||||
Product,
|
||||
ProductGroup,
|
||||
@@ -35,6 +36,8 @@ import { PrintProductDefaultsAPI } from './printproduct-defaults-api';
|
||||
import { GraphQLError } from 'graphql';
|
||||
import { MINUTE } from './utils';
|
||||
import { ScopeAccess, Scopes } from '../cognito/access-control';
|
||||
import { Interior } from '../types/interior-types';
|
||||
import { deleteFiles, listS3Files } from '../aws/s3';
|
||||
|
||||
export class ProductAPI extends BaseSQLDataSource {
|
||||
textsApi: TextsAPI;
|
||||
@@ -1005,6 +1008,52 @@ export class ProductAPI extends BaseSQLDataSource {
|
||||
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
|
||||
* @description Sets propotion warning-values to a product
|
||||
|
||||
@@ -232,6 +232,28 @@ export const productMutationTypeDefs = {
|
||||
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
|
||||
* @description Mutate base information about a product
|
||||
|
||||
@@ -462,6 +462,11 @@ type IdNumberResult {
|
||||
id: Int!
|
||||
}
|
||||
|
||||
type CleanupResponse {
|
||||
active: [String]
|
||||
deleted: [String]
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
"""
|
||||
Product
|
||||
@@ -524,6 +529,7 @@ type Mutation {
|
||||
productGroup(productId: Int!, groupIds: [Int]): Product
|
||||
productGroupInteriors(productId: Int!, groupId: Int!, uris: [String]): Product
|
||||
generateInteriors(productId: Int!, roomIds: [String]!): StringResult
|
||||
cleanupInteriors(productId: Int!): CleanupResponse
|
||||
addOwnInteriorToPrintProduct(
|
||||
printId: Int!
|
||||
uploadedS3Key: String!
|
||||
|
||||
Reference in New Issue
Block a user