Skip to content

Commit c74b79c

Browse files
committed
feat: add custom cover config handling
1 parent 4e4413a commit c74b79c

6 files changed

Lines changed: 101 additions & 12 deletions

File tree

server/api.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -738,6 +738,14 @@ router
738738
orgsAPI.getCampusAdmins
739739
);
740740

741+
router
742+
.route("/org/:orgID/custom-cover-config")
743+
.get(
744+
orgsAPI.validate("getCustomCoverConfig"),
745+
middleware.checkValidationErrors,
746+
orgsAPI.getCustomCoverConfig
747+
);
748+
741749
/* Asset Tag Frameworks */
742750
router
743751
.route("/assettagframeworks")

server/api/organizations.ts

Lines changed: 56 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
'use strict';
88
import logger from "../logger.js";
9-
import express, {Request, Response, NextFunction} from 'express';
9+
import express, { Request, Response, NextFunction } from 'express';
1010
import { body, param, query } from 'express-validator';
1111
import multer from 'multer';
1212
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
@@ -71,7 +71,7 @@ async function lookupOrganization(orgID: string) {
7171
{ orgID },
7272
{ _id: 0, defaultProjectLead: 0 },
7373
).lean();
74-
if(org?.commonsModules){
74+
if (org?.commonsModules) {
7575
// Remove _id and __v fields from commonsModules subdocument
7676
// @ts-ignore
7777
delete org.commonsModules._id;
@@ -225,6 +225,45 @@ async function getLibreGridOrganizations(_req: Request, res: Response) {
225225
}
226226
}
227227

228+
async function getCustomCoverConfig(req: Request, res: Response) {
229+
try {
230+
const { orgID } = req.params;
231+
232+
const orgData = await Organization.findOne(
233+
{
234+
orgID: { $eq: orgID }
235+
},
236+
{
237+
orgID: 1,
238+
name: 1,
239+
customCoverConfig: 1,
240+
}
241+
).lean();
242+
243+
if (!orgData) {
244+
return res.status(404).send({
245+
err: true,
246+
errMsg: conductorErrors.err11,
247+
});
248+
}
249+
250+
return res.send({
251+
err: false,
252+
org: {
253+
orgID: orgData.orgID,
254+
name: orgData.name,
255+
},
256+
customCoverConfig: orgData.customCoverConfig || null,
257+
});
258+
} catch (e) {
259+
logger.error({ err: e }, "getCustomCoverConfig failed");
260+
return res.status(500).send({
261+
err: true,
262+
errMsg: conductorErrors.err6,
263+
});
264+
}
265+
}
266+
228267
/**
229268
* Updates an Organization's information.
230269
*
@@ -248,11 +287,11 @@ async function updateOrganizationInfo(req: Request, res: Response) {
248287
}
249288
};
250289

251-
if(req.body.primaryColor) {
290+
if (req.body.primaryColor) {
252291
updateObj.primaryColor = sanitizeCustomColor(req.body.primaryColor)
253292
}
254293

255-
if(req.body.footerColor) {
294+
if (req.body.footerColor) {
256295
updateObj.footerColor = sanitizeCustomColor(req.body.footerColor)
257296
}
258297

@@ -274,7 +313,7 @@ async function updateOrganizationInfo(req: Request, res: Response) {
274313
);
275314
}
276315

277-
if(Object.hasOwn(updateObj, 'collectionsDisplayLabel') && isEmptyString(updateObj.collectionsDisplayLabel ?? '')){
316+
if (Object.hasOwn(updateObj, 'collectionsDisplayLabel') && isEmptyString(updateObj.collectionsDisplayLabel ?? '')) {
278317
// Reset label to 'Collections' if empty string was passed
279318
updateObj.collectionsDisplayLabel = 'Collections'
280319
}
@@ -355,9 +394,9 @@ async function updateBrandingImageAsset(req: Request, res: Response) {
355394
errMsg: conductorErrors.err2,
356395
});
357396
}
358-
397+
359398
let assetVersion = 1;
360-
399+
361400
// @ts-ignore
362401
if (org[assetName].includes(process.env.AWS_ORGDATA_DOMAIN)) {
363402
//@ts-ignore
@@ -472,7 +511,7 @@ function validateCommonsModules(commonsModules: any) {
472511
return false;
473512
}
474513
for (const module in commonsModules) {
475-
if(module === "_id" || module === "__v"){
514+
if (module === "_id" || module === "__v") {
476515
// ignore these fields (mongodb adds them because commonsModules is technically a subdocument of the Organization model)
477516
continue;
478517
}
@@ -512,16 +551,20 @@ function validate(method: string) {
512551
return [
513552
param('orgID', conductorErrors.err1).exists().isLength({ min: 2, max: 50 }),
514553
];
554+
case 'getCustomCoverConfig':
555+
return [
556+
param('orgID', conductorErrors.err1).exists().isLength({ min: 2, max: 50 }),
557+
];
515558
case 'updateinfo':
516559
return [
517560
param('orgID', conductorErrors.err1).exists().isLength({ min: 2, max: 50 }),
518561
body('aboutLink', conductorErrors.err1).optional({ checkFalsy: true }).isURL(),
519562
body('commonsHeader', conductorErrors.err1).optional({ checkFalsy: true }).isLength({ max: 200 }),
520563
body('commonsMessage', conductorErrors.err1).optional({ checkFalsy: true }).isLength({ max: 500 }),
521-
body('collectionsDisplayLabel', conductorErrors.err1).optional({checkFalsy: true}).isLength({ max: 200 }),
522-
body('collectionsMessage', conductorErrors.err1).optional({checkFalsy: true}).isLength({max: 500}),
523-
body('primaryColor', conductorErrors.err1).optional({ checkFalsy: true }).isLength({min: 7, max: 7}).isHexColor(),
524-
body('footerColor', conductorErrors.err1).optional({ checkFalsy: true }).isLength({min: 7, max: 7}).isHexColor(),
564+
body('collectionsDisplayLabel', conductorErrors.err1).optional({ checkFalsy: true }).isLength({ max: 200 }),
565+
body('collectionsMessage', conductorErrors.err1).optional({ checkFalsy: true }).isLength({ max: 500 }),
566+
body('primaryColor', conductorErrors.err1).optional({ checkFalsy: true }).isLength({ min: 7, max: 7 }).isHexColor(),
567+
body('footerColor', conductorErrors.err1).optional({ checkFalsy: true }).isLength({ min: 7, max: 7 }).isHexColor(),
525568
body('addToLibreGridList', conductorErrors.err1).optional({ checkFalsy: true }).isBoolean().toBoolean(),
526569
body('supportTicketNotifiers', conductorErrors.err1).optional({ checkFalsy: true }).isArray().isEmail(),
527570
body('defaultAssetTagFrameworkUUID', conductorErrors.err1).optional({ checkFalsy: true }).isUUID(),
@@ -549,6 +592,7 @@ export default {
549592
getOrganizationInfo,
550593
getCampusAdmins,
551594
getCurrentOrganization,
595+
getCustomCoverConfig,
552596
getAllOrganizations,
553597
getLibreGridOrganizations,
554598
updateOrganizationInfo,

server/api/services/shapeshift-service.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ export default class ShapeshiftService {
157157
bookID,
158158
contentPageCount,
159159
timestamp,
160+
customCoverOrg,
160161
}: WebhookParams): Promise<boolean> {
161162
const result = await Book.updateOne(
162163
{
@@ -174,6 +175,7 @@ export default class ShapeshiftService {
174175
...(contentPageCount !== undefined
175176
? { "exportInfo.contentPageCount": contentPageCount }
176177
: {}),
178+
...(customCoverOrg !== undefined ? { "exportInfo.customCoverOrg": customCoverOrg } : {}),
177179
},
178180
}
179181
);

server/api/validators/shapeshift.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,5 +38,6 @@ export const WebhookValidator = z.object({
3838
bookID: bookIDSchema,
3939
contentPageCount: z.number().int().nonnegative().optional(),
4040
timestamp: z.number().int().nonnegative(), // Unix timestamp in milliseconds
41+
customCoverOrg: z.string().trim().min(1).optional(), // Only set if the book has a custom cover by the producing org
4142
}),
4243
});

server/models/book.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export interface BookInterface extends Document {
1919
lastCompiled?: number;
2020
compiledBy?: string;
2121
contentPageCount?: number;
22+
customCoverOrg?: string; // The name of the org producing the book if a custom cover is used. Should be used for display/informational purposes only, not for load-bearing logic or validation.
2223
lastJobID?: string;
2324
lastJobSubmittedAt?: Date;
2425
lastJobSubmittedBy?: string;
@@ -131,6 +132,10 @@ const BookSchema = new Schema<BookInterface>(
131132
* The number of pages in the Book's content PDF, as reported by Shapeshift during compilation.
132133
*/
133134
contentPageCount: Number,
135+
/**
136+
* The name of the org producing the book if a custom cover is used. Should be used for display/informational purposes only, not for load-bearing logic or validation.
137+
*/
138+
customCoverOrg: String,
134139
/**
135140
* The Shapeshift job ID of the most recent compile submitted from Conductor.
136141
*

server/models/organization.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,17 @@ export type CommonsModuleSettings = {
2121
minirepos: CommonsModuleConfig;
2222
}
2323

24+
export type CustomCoverConfig = {
25+
enabled: boolean;
26+
casewrapCoverFrontTemplateURL: string;
27+
casewrapCoverBackTemplateURL: string;
28+
perfectboundCoverFrontTemplateURL: string;
29+
perfectboundCoverBackTemplateURL: string;
30+
spineHexColor: string;
31+
spineImageURL?: string;
32+
matchingPaths: string[];
33+
}
34+
2435
export interface OrganizationInterface extends Document {
2536
orgID: string;
2637
active: boolean;
@@ -51,6 +62,7 @@ export interface OrganizationInterface extends Document {
5162
showCollections?: boolean;
5263
assetFilterExclusions?: string[];
5364
autoCatalogMatchingDisabled?: boolean;
65+
customCoverConfig?: CustomCoverConfig;
5466
listenerPriority: number;
5567
cpuUnitsOverride: number;
5668
memoryValueOverride: number;
@@ -234,6 +246,23 @@ const OrganizationSchema = new Schema<OrganizationInterface>(
234246
type: Boolean,
235247
default: false,
236248
},
249+
/**
250+
* Configuration for custom cover templates and spine colors for the Organization.
251+
*/
252+
customCoverConfig: {
253+
type: new Schema<CustomCoverConfig>({
254+
enabled: Boolean,
255+
casewrapCoverFrontTemplateURL: String,
256+
casewrapCoverBackTemplateURL: String,
257+
perfectboundCoverFrontTemplateURL: String,
258+
perfectboundCoverBackTemplateURL: String,
259+
spineHexColor: String,
260+
spineImageURL: String,
261+
matchingPaths: [String],
262+
},
263+
{ _id: false },
264+
),
265+
},
237266
/**
238267
* Used for deterministic routing in a load-balanced Conductor deployment.
239268
*/

0 commit comments

Comments
 (0)