This commit is contained in:
@@ -85,5 +85,29 @@ export function createImageDerivativeJobRepository(db) {
|
||||
|
||||
return failed ?? null;
|
||||
},
|
||||
|
||||
async retryForImage(imageId) {
|
||||
const [retried] = await db
|
||||
.update(imageDerivativeJobs)
|
||||
.set({ status: 'PENDING', error: null, derivatives: [], updatedAt: new Date() })
|
||||
.where(and(
|
||||
eq(imageDerivativeJobs.inventoryImageId, imageId),
|
||||
eq(imageDerivativeJobs.status, 'FAILED'),
|
||||
))
|
||||
.returning();
|
||||
|
||||
if (retried) {
|
||||
await db
|
||||
.update(inventoryImages)
|
||||
.set({ processingStatus: 'PENDING', updatedAt: new Date() })
|
||||
.where(eq(inventoryImages.id, imageId));
|
||||
}
|
||||
|
||||
return retried ?? null;
|
||||
},
|
||||
|
||||
async deleteForImage(imageId) {
|
||||
await db.delete(imageDerivativeJobs).where(eq(imageDerivativeJobs.inventoryImageId, imageId));
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -32,6 +32,70 @@ export function createInventoryImageRepository(db) {
|
||||
return rows.length;
|
||||
},
|
||||
|
||||
async findByIdForSeller(imageId, sellerId) {
|
||||
const [image] = await db
|
||||
.select({ image: inventoryImages })
|
||||
.from(inventoryImages)
|
||||
.innerJoin(inventoryItems, eq(inventoryItems.id, inventoryImages.inventoryItemId))
|
||||
.innerJoin(products, eq(products.id, inventoryItems.productId))
|
||||
.where(and(eq(inventoryImages.id, imageId), eq(products.sellerId, sellerId)));
|
||||
|
||||
return image?.image ?? null;
|
||||
},
|
||||
|
||||
async updateOrderForSeller(imageId, sortOrder, sellerId) {
|
||||
const owned = await this.findByIdForSeller(imageId, sellerId);
|
||||
if (!owned) return null;
|
||||
|
||||
const [updated] = await db
|
||||
.update(inventoryImages)
|
||||
.set({ sortOrder, updatedAt: new Date() })
|
||||
.where(eq(inventoryImages.id, imageId))
|
||||
.returning();
|
||||
return updated ?? null;
|
||||
},
|
||||
|
||||
async setPrimaryForSeller(imageId, sellerId) {
|
||||
const owned = await this.findByIdForSeller(imageId, sellerId);
|
||||
if (!owned) return null;
|
||||
|
||||
return db.transaction(async (tx) => {
|
||||
await tx
|
||||
.update(inventoryImages)
|
||||
.set({ isPrimary: false, updatedAt: new Date() })
|
||||
.where(eq(inventoryImages.inventoryItemId, owned.inventoryItemId));
|
||||
const [updated] = await tx
|
||||
.update(inventoryImages)
|
||||
.set({ isPrimary: true, updatedAt: new Date() })
|
||||
.where(eq(inventoryImages.id, imageId))
|
||||
.returning();
|
||||
return updated ?? null;
|
||||
});
|
||||
},
|
||||
|
||||
async updateAltTextForSeller(imageId, altText, sellerId) {
|
||||
const owned = await this.findByIdForSeller(imageId, sellerId);
|
||||
if (!owned) return null;
|
||||
|
||||
const [updated] = await db
|
||||
.update(inventoryImages)
|
||||
.set({ altText, updatedAt: new Date() })
|
||||
.where(eq(inventoryImages.id, imageId))
|
||||
.returning();
|
||||
return updated ?? null;
|
||||
},
|
||||
|
||||
async deleteForSeller(imageId, sellerId) {
|
||||
const owned = await this.findByIdForSeller(imageId, sellerId);
|
||||
if (!owned) return null;
|
||||
|
||||
const [deleted] = await db
|
||||
.delete(inventoryImages)
|
||||
.where(eq(inventoryImages.id, imageId))
|
||||
.returning();
|
||||
return deleted ?? null;
|
||||
},
|
||||
|
||||
async listPublicByInventoryItemId(inventoryItemId) {
|
||||
return db
|
||||
.select({
|
||||
|
||||
@@ -77,4 +77,61 @@ export async function registerAdminImageRoutes(app, options) {
|
||||
|
||||
return reply.code(201).send({ data: toAdminInventoryImageDto(image) });
|
||||
});
|
||||
|
||||
async function authorizeAdminMutation(request) {
|
||||
const admin = await authenticateAdmin(request);
|
||||
if (!admin?.sellerId) {
|
||||
throw createApplicationError('UNAUTHORIZED', 'Authentication is required.', 401);
|
||||
}
|
||||
|
||||
await authorizeUnsafeRequest?.(
|
||||
request.cookies[sessionCookieName],
|
||||
request.headers['x-csrf-token'],
|
||||
);
|
||||
return admin;
|
||||
}
|
||||
|
||||
app.patch('/images/:id/order', async (request) => {
|
||||
const admin = await authorizeAdminMutation(request);
|
||||
const image = await inventoryImageService.updateOrder({
|
||||
imageId: request.params.id,
|
||||
sortOrder: request.body?.sortOrder,
|
||||
sellerId: admin.sellerId,
|
||||
});
|
||||
return { data: toAdminInventoryImageDto(image) };
|
||||
});
|
||||
|
||||
app.post('/images/:id/primary', async (request) => {
|
||||
const admin = await authorizeAdminMutation(request);
|
||||
const image = await inventoryImageService.setPrimary({
|
||||
imageId: request.params.id,
|
||||
sellerId: admin.sellerId,
|
||||
});
|
||||
return { data: toAdminInventoryImageDto(image) };
|
||||
});
|
||||
|
||||
app.patch('/images/:id/alt-text', async (request) => {
|
||||
const admin = await authorizeAdminMutation(request);
|
||||
const image = await inventoryImageService.updateAltText({
|
||||
imageId: request.params.id,
|
||||
altText: request.body?.altText ?? null,
|
||||
sellerId: admin.sellerId,
|
||||
});
|
||||
return { data: toAdminInventoryImageDto(image) };
|
||||
});
|
||||
|
||||
app.post('/images/:id/retry', async (request) => {
|
||||
const admin = await authorizeAdminMutation(request);
|
||||
const image = await inventoryImageService.retry({
|
||||
imageId: request.params.id,
|
||||
sellerId: admin.sellerId,
|
||||
});
|
||||
return { data: toAdminInventoryImageDto(image) };
|
||||
});
|
||||
|
||||
app.delete('/images/:id', async (request, reply) => {
|
||||
const admin = await authorizeAdminMutation(request);
|
||||
await inventoryImageService.delete({ imageId: request.params.id, sellerId: admin.sellerId });
|
||||
return reply.code(204).send();
|
||||
});
|
||||
}
|
||||
@@ -37,5 +37,44 @@ export function createInventoryImageService({ inventoryImageRepository, imageDer
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
async updateOrder({ imageId, sortOrder, sellerId }) {
|
||||
return requireOwnedImage(inventoryImageRepository.updateOrderForSeller(imageId, sortOrder, sellerId));
|
||||
},
|
||||
|
||||
async setPrimary({ imageId, sellerId }) {
|
||||
return requireOwnedImage(inventoryImageRepository.setPrimaryForSeller(imageId, sellerId));
|
||||
},
|
||||
|
||||
async updateAltText({ imageId, altText, sellerId }) {
|
||||
return requireOwnedImage(inventoryImageRepository.updateAltTextForSeller(imageId, altText, sellerId));
|
||||
},
|
||||
|
||||
async delete({ imageId, sellerId }) {
|
||||
const image = await inventoryImageRepository.findByIdForSeller(imageId, sellerId);
|
||||
if (!image) throw imageNotFoundError();
|
||||
await imageDerivativeJobRepository?.deleteForImage(imageId);
|
||||
await inventoryImageRepository.deleteForSeller(imageId, sellerId);
|
||||
await imageStorage.delete(image.storageKey);
|
||||
return image;
|
||||
},
|
||||
|
||||
async retry({ imageId, sellerId }) {
|
||||
const image = await inventoryImageRepository.findByIdForSeller(imageId, sellerId);
|
||||
if (!image) throw imageNotFoundError();
|
||||
const job = await imageDerivativeJobRepository?.retryForImage(imageId);
|
||||
if (!job) throw createApplicationError('IMAGE_RETRY_NOT_AVAILABLE', 'Image retry is not available.', 409);
|
||||
return image;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function imageNotFoundError() {
|
||||
return createApplicationError('IMAGE_NOT_FOUND', 'Image was not found.', 404);
|
||||
}
|
||||
|
||||
async function requireOwnedImage(imagePromise) {
|
||||
const image = await imagePromise;
|
||||
if (!image) throw imageNotFoundError();
|
||||
return image;
|
||||
}
|
||||
Reference in New Issue
Block a user