added cleanup mutation (#157)
* added cleanup mutation * default to empty array
This commit is contained in:
@@ -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;
|
||||||
|
};
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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!
|
||||||
|
|||||||
Reference in New Issue
Block a user