Skip to content

Commit 74c7bce

Browse files
authored
Add tag filtering to the job board API (#441)
* Fix a validation error * Add total count to paginated output * Fix a loading bug * Add tag filtering and refactor param parsing
1 parent c88c884 commit 74c7bce

File tree

3 files changed

+61
-24
lines changed

3 files changed

+61
-24
lines changed

src/features/jobs-moderation.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,8 +159,13 @@ const jobModeration = async (bot: Client) => {
159159
const errors = validate(posts, message).filter(
160160
(e) => e.type !== POST_FAILURE_REASONS.tooFrequent,
161161
);
162+
console.log(
163+
`[DEBUG] validating edited job post from @${
164+
message.author.username
165+
}, errors: ${JSON.stringify(errors)}`,
166+
);
162167

163-
if (errors) {
168+
if (errors.length > 0) {
164169
const isRecentEdit =
165170
differenceInMinutes(new Date(), message.createdAt) < REPOST_THRESHOLD;
166171
errors.unshift({

src/features/jobs-moderation/job-mod-helpers.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ export const loadJobs = async (bot: Client, channel: TextChannel) => {
143143
}
144144
oldestMessage = newMessages
145145
.sort((a, b) => compareAsc(b.createdAt, a.createdAt))
146-
.at(-0);
146+
.at(-1);
147147
if (!oldestMessage) break;
148148

149149
const [hiring, forHire] = partition(

src/server.ts

Lines changed: 54 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
StoredMessage,
99
getJobPosts,
1010
} from "./features/jobs-moderation/job-mod-helpers.js";
11-
import { compressLineBreaks } from "./helpers/string.js";
11+
import { compressLineBreaks, simplifyString } from "./helpers/string.js";
1212
import { constructDiscordLink } from "./helpers/discord.js";
1313
import { reactibotApiKey } from "./helpers/env.js";
1414

@@ -81,6 +81,7 @@ const openApiConfig = {
8181
},
8282
},
8383
JobBoardCache: {
84+
count: { type: "number" },
8485
pages: { type: "number" },
8586
page: { type: "number" },
8687
limit: { type: "number" },
@@ -129,8 +130,7 @@ const openApiConfig = {
129130

130131
fastify.addHook("onRequest", async (request, reply) => {
131132
const apiKey = request.headers["api-key"];
132-
console.log("onreq");
133-
if (apiKey !== reactibotApiKey) {
133+
if (request.hostname !== "localhost" && apiKey !== reactibotApiKey) {
134134
reply.code(401).send({ error: "Unauthorized" });
135135
return;
136136
}
@@ -160,10 +160,14 @@ fastify.get(
160160
},
161161
},
162162
async (req) => {
163-
const { page, limit } = getPaginationFromRequest(req);
163+
const { page, limit, requiredTags } = parseQuery(req);
164164
const { hiring } = getJobPosts();
165165

166-
return paginateResponse(page, limit, hiring.map(renderPost));
166+
return paginateResponse(
167+
page,
168+
limit,
169+
filterTags(requiredTags, hiring).map(renderPost),
170+
);
167171
},
168172
);
169173
fastify.get(
@@ -181,31 +185,58 @@ fastify.get(
181185
},
182186
},
183187
async (req) => {
184-
const { page, limit } = getPaginationFromRequest(req);
188+
const { page, limit, requiredTags } = parseQuery(req);
185189
const { forHire } = getJobPosts();
186190

187-
return paginateResponse(page, limit, forHire.map(renderPost));
191+
return paginateResponse(
192+
page,
193+
limit,
194+
filterTags(requiredTags, forHire).map(renderPost),
195+
);
188196
},
189197
);
190198

199+
const filterTags = (requiredTags: string[], posts: StoredMessage[]) => {
200+
return posts.filter((post) => {
201+
const simplifiedTags = post.tags.map((t) =>
202+
simplifyString(t).replaceAll(" ", ""),
203+
);
204+
return requiredTags.every((rt) => simplifiedTags.includes(rt));
205+
});
206+
};
207+
191208
const DEFAULT_LIMIT = 10;
192-
const getPaginationFromRequest = (req: FastifyRequest) => {
193-
const { page, limit } = req.query as { page?: string; limit?: string };
194-
let outPage: number, outLimit: number;
195-
if (!page) {
196-
outPage = 1;
197-
} else {
198-
outPage = parseInt(page);
199-
if (isNaN(outPage)) outPage = 1;
200-
}
201-
if (!limit) {
202-
outLimit = DEFAULT_LIMIT;
209+
const parseQuery = (req: FastifyRequest) => {
210+
const {
211+
page: rawPage,
212+
limit: rawLimit,
213+
...tags
214+
} = req.query as {
215+
page?: string;
216+
limit?: string;
217+
};
218+
219+
return {
220+
requiredTags: Object.entries(tags)
221+
.filter(([, value]) => value)
222+
.map(([tag]) => normalizeTags(tag)),
223+
page: parseNumber(rawPage, 1),
224+
limit: parseNumber(rawLimit, DEFAULT_LIMIT, MAX_LIMIT),
225+
};
226+
};
227+
228+
const normalizeTags = (tag: string) => simplifyString(tag);
229+
230+
const parseNumber = (inVal: any, defaultVal: number, max = Infinity) => {
231+
let out;
232+
if (!inVal) {
233+
out = defaultVal;
203234
} else {
204-
outLimit = parseInt(limit);
205-
if (isNaN(outLimit)) outLimit = DEFAULT_LIMIT;
206-
if (outLimit > MAX_LIMIT) outLimit = MAX_LIMIT;
235+
out = parseInt(inVal);
236+
if (isNaN(out)) out = defaultVal;
207237
}
208-
return { page: outPage, limit: outLimit };
238+
if (out > max) out = max;
239+
return out;
209240
};
210241

211242
const paginateResponse = <T extends Array<any>>(
@@ -215,6 +246,7 @@ const paginateResponse = <T extends Array<any>>(
215246
) => {
216247
const offset = (page - 1) * limit;
217248
return {
249+
count: data.length,
218250
data: data.slice(offset, offset + limit),
219251
page,
220252
limit,

0 commit comments

Comments
 (0)