Skip to content

Commit a29b553

Browse files
committed
feat(book): webhook for compilation updates from Shapeshift
1 parent 4a1ac38 commit a29b553

7 files changed

Lines changed: 81 additions & 4 deletions

File tree

client/src/types/Book.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ export type Book = {
1616
thumbnail: string;
1717
thumbnailIsAnimated?: boolean;
1818
summary: string;
19+
isCompiled?: boolean;
20+
lastCompiled?: number;
1921
rating: number;
2022
links: BookLinks;
2123
lastUpdated: string;

server/api.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3219,6 +3219,13 @@ router.route('/shapeshift/job').post(
32193219
catchInternal((req, res) => shapeshiftAPI.createJob(req, res)),
32203220
);
32213221

3222+
router.route("/shapeshift/webhook").post(
3223+
express.raw({ type: "application/json" }),
3224+
middleware.checkShapeshiftWebhookKey,
3225+
middleware.validateZod(ShapeshiftValidators.WebhookValidator),
3226+
catchInternal((req, res) => shapeshiftAPI.handleWebhook(req, res))
3227+
);
3228+
32223229
router.route('/book-bots/editor-preprocess').post(
32233230
authAPI.verifyRequest,
32243231
authAPI.getUserAttributes,

server/api/services/shapeshift-service.ts

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import {ShapeshiftJob, ShapeshiftJobStatus} from "../../types/Shapeshift";
2-
import axios, {AxiosInstance} from "axios";
3-
import {debugError} from "../../debug";
1+
import { ShapeshiftJob, ShapeshiftJobStatus } from "../../types/Shapeshift";
2+
import axios, { AxiosInstance } from "axios";
3+
import { debugError } from "../../debug";
4+
import Book from "../../models/book";
45

56
export default class ShapeshiftService {
67
private instance: AxiosInstance;
@@ -65,4 +66,21 @@ export default class ShapeshiftService {
6566
return emptyResponse;
6667
}
6768
}
69+
70+
public async handleWebhook(bookID: string, timestamp: number): Promise<void> {
71+
try {
72+
const book = await Book.updateOne({ bookID: { $eq: bookID } }, {
73+
$set: {
74+
isCompiled: true,
75+
lastCompiled: timestamp,
76+
},
77+
});
78+
79+
if (!book || book.matchedCount === 0) {
80+
debugError(`Book with bookID ${bookID} not found for Shapeshift webhook.`);
81+
}
82+
} catch (error) {
83+
debugError(error);
84+
}
85+
}
6886
}

server/api/shapeshift.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Response } from "express";
22
import { z } from "zod";
33
import { ZodReqWithUser } from "../types";
4-
import { CreateJobValidator, GetJobsValidator } from "./validators/shapeshift";
4+
import { CreateJobValidator, GetJobsValidator, WebhookValidator } from "./validators/shapeshift";
55
import ShapeshiftService from "./services/shapeshift-service";
66

77
export async function createJob(
@@ -40,3 +40,16 @@ export async function getJobs(
4040
jobs,
4141
});
4242
}
43+
44+
export async function handleWebhook(
45+
req: ZodReqWithUser<z.infer<typeof WebhookValidator>>,
46+
res: Response
47+
) {
48+
const service = new ShapeshiftService();
49+
const { bookID, timestamp } = req.body;
50+
await service.handleWebhook(bookID, timestamp);
51+
return res.status(200).json({
52+
err: false,
53+
msg: 'Webhook processed.',
54+
});
55+
}

server/api/validators/shapeshift.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { z } from "zod";
2+
import { bookIDSchema } from "./book.js";
23

34
export const CreateJobValidator = z.object({
45
body: z.object({
@@ -20,3 +21,11 @@ export const GetJobsValidator = z.object({
2021
.optional(),
2122
}),
2223
});
24+
25+
26+
export const WebhookValidator = z.object({
27+
body: z.object({
28+
bookID: bookIDSchema,
29+
timestamp: z.number().int().nonnegative(), // Unix timestamp in seconds
30+
}),
31+
});

server/middleware.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,23 @@ const checkEventBridgeAPIKey = (req: Request, res: Response, next: NextFunction)
104104
return res.status(401).send({ errMsg: conductorErrors.err5 });
105105
};
106106

107+
/**
108+
* Verifies that a request has provided a valid key from a Shapeshift webhook.
109+
* @param {object} req - The Express.js request object.
110+
* @param {object} res - The Express.js response object.
111+
* @param {function} next - The next function in the middleware chain.
112+
*/
113+
const checkShapeshiftWebhookKey = (req: Request, res: Response, next: NextFunction) => {
114+
if (typeof req.headers?.authorization === "string") {
115+
const foundToken = req.headers.authorization.replace("Bearer ", "");
116+
if (!process.env.SHAPESHIFT_WEBHOOK_KEY || process.env.SHAPESHIFT_WEBHOOK_KEY.length === 0) {
117+
return res.status(500).send({ errMsg: conductorErrors.err6 });
118+
}
119+
if (process.env.SHAPESHIFT_WEBHOOK_KEY === foundToken) return next();
120+
}
121+
return res.status(401).send({ errMsg: conductorErrors.err5 });
122+
};
123+
107124
/**
108125
* Reconstructs the Authorization header from cookies, if not already present.
109126
*
@@ -481,6 +498,7 @@ export default {
481498
checkLibreCommons,
482499
checkLibreAPIKey,
483500
checkEventBridgeAPIKey,
501+
checkShapeshiftWebhookKey,
484502
authSanitizer,
485503
middlewareFilter,
486504
checkCentralIdentityConfig,

server/models/book.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ export interface BookInterface extends Document {
1414
license?: string;
1515
thumbnail?: string;
1616
summary?: string;
17+
isCompiled?: boolean;
18+
lastCompiled?: number;
1719
rating?: number;
1820
links?: {
1921
online?: string;
@@ -99,6 +101,14 @@ const BookSchema = new Schema<BookInterface>(
99101
* The Book's overview/description/summary.
100102
*/
101103
summary: String,
104+
/**
105+
* Whether the Book has, at any point, been successfully compiled by Shapeshift.
106+
*/
107+
isCompiled: Boolean,
108+
/**
109+
* Timestamp of the most recent successful compilation by Shapeshift (Unix timestamp in seconds).
110+
*/
111+
lastCompiled: Number,
102112
/**
103113
* The overall quality, on a scale of 0-5. Value is the average of all Peer Review
104114
* overall ratings submitted on the Book.

0 commit comments

Comments
 (0)