Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add schema validation for the all boards from realm query #160

Merged
merged 2 commits into from
Nov 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion handlers/api-errors/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { ZodError } from "zod";
import debug from "debug";
import opentelemetry from "@opentelemetry/api";

const log = debug("bobaserver:handlers:errors");
const log = debug("bobaserver:handlers:errors-log");

// Sends the correct status code and message to the client if the error is an instance of APIError,
// else it lets the error bubble up to the global error handler.
Expand Down
37 changes: 20 additions & 17 deletions server/boards/queries.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { BoardByExternalId, BoardByExternalIdSchema } from "./sql/types";
import {
BoardByExternalId,
BoardByExternalIdSchema,
DbRealmBoardSchema,
DbRealmBoardType,
} from "./sql/types";

import { ITask } from "pg-promise";
import debug from "debug";
Expand All @@ -14,23 +19,21 @@ const info = debug("bobaserver:board:queries-info");
const log = debug("bobaserver:board:queries-log");
const error = debug("bobaserver:board:queries-error");

export const getBoards = async ({
export const getRealmBoards = async ({
firebaseId,
realmExternalId,
}: {
firebaseId: string | null;
realmExternalId?: string;
}): Promise<any> => {
try {
return await pool.many(sql.getAllBoards, {
firebase_id: firebaseId,
realm_external_id: realmExternalId,
});
} catch (e) {
error(`Error while fetching boards.`);
error(e);
return false;
}
}): Promise<DbRealmBoardType[]> => {
return pool.task("get-realm-boards", async (t) => {
return (
await t.many(sql.getAllBoards, {
firebase_id: firebaseId,
realm_external_id: realmExternalId,
})
).map((board) => DbRealmBoardSchema.parse(board));
});
};

export const getBoardByExternalId = async ({
Expand Down Expand Up @@ -425,9 +428,9 @@ export const getBoardRoles = async ({
}): Promise<
| {
user_id: string;
username:string
role_id: string;
role_name: string;
username: string;
role_id: string;
role_name: string;
label: string | null;
}[]
| null
Expand All @@ -436,4 +439,4 @@ export const getBoardRoles = async ({
board_external_id: boardExternalId,
});
return boardRoles;
};
};
2 changes: 1 addition & 1 deletion server/boards/sql/all-boards.sql
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ SELECT
GREATEST(MAX(COMMENTS.last_activity_from_others), MAX(posts.last_activity_from_others)) AS last_activity_from_others_at,
MAX(GREATEST(user_board_last_visits.last_visit_time, posts.last_thread_visit)) as last_visit_at,
user_muted_boards.board_id IS NOT NULL as muted,
COALESCE(ordered_pinned_boards.index, NULL) as pinned_order,
COALESCE(CAST(ordered_pinned_boards.index AS integer), NULL) as pinned_order,
BOOL_OR(user_muted_boards.board_id IS NULL AND (posts.has_new OR comments.has_new)) as has_updates,
to_jsonb(COALESCE(logged_out_restrictions, ARRAY[]::board_restrictions_type[])) as logged_out_restrictions,
to_jsonb(COALESCE(CASE WHEN logged_in_user.id IS NOT NULL THEN logged_in_base_restrictions ELSE NULL END, ARRAY[]::board_restrictions_type[])) as logged_in_base_restrictions
Expand Down
25 changes: 24 additions & 1 deletion server/boards/sql/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export const BoardPermissionsEnumSchema = z.enum([
"create_thread_on_realm",
"access_locked_boards_on_realm",
"view_roles_on_realm",
"view_roles_on_board",
"view_roles_on_board",
]);
export const BoardRestrictionsEnumSchema = z.enum(["lock_access", "delist"]);
export type BoardRestrictionsEnum = z.infer<typeof BoardRestrictionsEnumSchema>;
Expand Down Expand Up @@ -85,3 +85,26 @@ export const BoardByExternalIdSchema = z.object({
settings: BoardSettingsSchema,
});
export type BoardByExternalId = z.infer<typeof BoardByExternalIdSchema>;

export const DbRealmBoardSchema = z.object({
// TODO: rename this as external_id
string_id: z.string(),
realm_external_id: z.string(),
slug: z.string(),
tagline: z.string(),
avatar_reference_id: z.string(),
settings: BoardSettingsSchema,
board_categories_external_id: z.string(),
last_post_at: z.date().nullable(),
last_comment_at: z.date().nullable(),
last_activity_at: z.date().nullable(),
last_activity_from_others_at: z.date().nullable(),
last_visit_at: z.date().nullable(),
muted: z.boolean(),
pinned_order: z.number().nullable(),
has_updates: z.boolean(),
logged_out_restrictions: z.array(BoardRestrictionsEnumSchema),
logged_in_base_restrictions: z.array(BoardRestrictionsEnumSchema),
});

export type DbRealmBoardType = z.infer<typeof DbRealmBoardSchema>;
20 changes: 10 additions & 10 deletions server/boards/tests/data-all.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { BOBATAN_USER_ID, ZODIAC_KILLER_USER_ID } from "test/data/auth";

import { TWISTED_MINDS_REALM_EXTERNAL_ID } from "test/data/realms";
import { getBoards } from "../queries";
import { getRealmBoards } from "../queries";

const extractBoardDetails = (boardData: any) => {
return {
Expand Down Expand Up @@ -42,7 +42,7 @@ const extractBoardUserSettings = (boardData: any) => {
describe("Tests boards queries", () => {
describe("Boards details", () => {
test("fetches boards details(with user)", async () => {
const boards = await getBoards({
const boards = await getRealmBoards({
firebaseId: BOBATAN_USER_ID,
realmExternalId: TWISTED_MINDS_REALM_EXTERNAL_ID,
});
Expand Down Expand Up @@ -157,7 +157,7 @@ describe("Tests boards queries", () => {

describe("Boards updates", () => {
test("fetches all boards updates (with user)", async () => {
const boards = await getBoards({
const boards = await getRealmBoards({
firebaseId: BOBATAN_USER_ID,
realmExternalId: TWISTED_MINDS_REALM_EXTERNAL_ID,
});
Expand Down Expand Up @@ -266,7 +266,7 @@ describe("Tests boards queries", () => {
]);
});
test("fetches all boards updates (no user)", async () => {
const boards = await getBoards({
const boards = await getRealmBoards({
realmExternalId: TWISTED_MINDS_REALM_EXTERNAL_ID,
firebaseId: null,
});
Expand Down Expand Up @@ -375,7 +375,7 @@ describe("Tests boards queries", () => {
});

test("fetches all boards updates (dismissed notifications)", async () => {
const boards = await getBoards({
const boards = await getRealmBoards({
firebaseId: ZODIAC_KILLER_USER_ID,
realmExternalId: TWISTED_MINDS_REALM_EXTERNAL_ID,
});
Expand Down Expand Up @@ -489,7 +489,7 @@ describe("Tests boards queries", () => {

describe("User settings", () => {
test("fetches all boards (with user)", async () => {
const boards = await getBoards({
const boards = await getRealmBoards({
firebaseId: BOBATAN_USER_ID,
realmExternalId: TWISTED_MINDS_REALM_EXTERNAL_ID,
});
Expand All @@ -507,14 +507,14 @@ describe("Tests boards queries", () => {
external_id: "c6d3d10e-8e49-4d73-b28a-9d652b41beec",
realm_external_id: TWISTED_MINDS_REALM_EXTERNAL_ID,
muted: false,
pinned_order: "1",
pinned_order: 1,
},
{
slug: "anime",
external_id: "4b30fb7c-2aca-4333-aa56-ae8623a92b65",
realm_external_id: TWISTED_MINDS_REALM_EXTERNAL_ID,
muted: false,
pinned_order: "2",
pinned_order: 2,
},
{
slug: "long",
Expand Down Expand Up @@ -561,7 +561,7 @@ describe("Tests boards queries", () => {
]);
});
test("fetches all boards user settings (no user)", async () => {
const boards = await getBoards({
const boards = await getRealmBoards({
firebaseId: null,
realmExternalId: TWISTED_MINDS_REALM_EXTERNAL_ID,
});
Expand Down Expand Up @@ -639,7 +639,7 @@ describe("Tests boards queries", () => {
// that aren't tested by the above methods. Add the new field to the
// appropriate "extration" method so it can be captured by the other tests.
test("fetches all boards (with user)", async () => {
const boards = await getBoards({
const boards = await getRealmBoards({
firebaseId: BOBATAN_USER_ID,
realmExternalId: TWISTED_MINDS_REALM_EXTERNAL_ID,
});
Expand Down
4 changes: 2 additions & 2 deletions server/boards/tests/restricted.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getBoardByExternalId, getBoards } from "../queries";
import { getBoardByExternalId, getRealmBoards } from "../queries";

import { RESTRICTED_BOARD_ID } from "test/data/boards";

Expand All @@ -24,7 +24,7 @@ describe("Tests restricted board queries", () => {
});

test("board fetch contains lock access restriction for logged out users", async () => {
const boards = await getBoards({
const boards = await getRealmBoards({
firebaseId: "fb3",
});

Expand Down
39 changes: 18 additions & 21 deletions server/realms/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,15 @@ import { getRealmDataBySlug, getSettingsBySlug } from "./queries";
import {
processBoardsNotifications,
processBoardsSummary,
reduceById,
} from "utils/response-utils";

import { DbRealmBoardType } from "server/boards/sql/types";
import { RealmPermissions } from "types/permissions";
import { createInvite } from "server/realms/queries";
import debug from "debug";
import express from "express";
import firebaseAuth from "firebase-admin";
import { getBoards } from "../boards/queries";
import pool from "server/db-pool";
import { getRealmBoards } from "../boards/queries";
import { processRealmActivity } from "./utils";
import { randomBytes } from "crypto";

Expand Down Expand Up @@ -99,7 +99,7 @@ router.get("/slug/:realm_slug", withUserSettings, async (req, res) => {
realmExternalId: realmData.id,
});

const boards = await getBoards({
const boards = await getRealmBoards({
firebaseId: req.currentUser?.uid || null,
realmExternalId: realmData.id,
});
Expand Down Expand Up @@ -170,8 +170,9 @@ router.get("/slug/:realm_slug", withUserSettings, async (req, res) => {
router.get("/:realm_id/activity", ensureRealmExists, async (req, res) => {
const { realm_id } = req.params;
try {
// TODO[realms]: use a per-realm query here
const boards = await getBoards({
const { realm_id } = req.params;

const boards = await getRealmBoards({
firebaseId: req.currentUser?.uid || null,
realmExternalId: realm_id,
});
Expand Down Expand Up @@ -232,7 +233,7 @@ router.get("/:realm_id/activity", ensureRealmExists, async (req, res) => {
router.get("/:realm_id/notifications", ensureLoggedIn, async (req, res) => {
const { realm_id } = req.params;

const boards = await getBoards({
const boards = await getRealmBoards({
firebaseId: req.currentUser?.uid || null,
realmExternalId: realm_id,
});
Expand All @@ -244,24 +245,20 @@ router.get("/:realm_id/notifications", ensureLoggedIn, async (req, res) => {
boards,
});
const pinned = notifications
.filter((notification: any) =>
.filter((notification) =>
boards.find(
(board: any) =>
(board) =>
board.string_id == notification.id && board.pinned_order !== null
)
)
.reduce((result: any, current: any) => {
result[current.id] = {
...current,
};
return result;
}, {});
const realmBoards = notifications.reduce((result: any, current: any) => {
result[current.id] = {
...current,
};
return result;
}, {});
.reduce(
reduceById,
{} as Record<string, ReturnType<typeof processBoardsNotifications>[0]>
);
const realmBoards = notifications.reduce(
reduceById,
{} as Record<string, ReturnType<typeof processBoardsNotifications>[0]>
);

const hasNotifications = notifications.some(
(notification) => notification.has_updates
Expand Down
31 changes: 20 additions & 11 deletions server/users/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { RealmPermissions } from "types/permissions";
import { aggregateByType } from "utils/settings";
import debug from "debug";
import express from "express";
import { getBoards } from "../boards/queries";
import { getRealmBoards } from "../boards/queries";
import stringify from "fast-json-stable-stringify";
import { withRealmPermissions } from "handlers/permissions";

Expand Down Expand Up @@ -222,7 +222,7 @@ router.get(
log(`Returning cached pinned boards data for user ${currentUserId}`);
return res.status(200).json(JSON.parse(cachedData));
}
const boards = await getBoards({
const boards = await getRealmBoards({
firebaseId: req.currentUser!.uid,
realmExternalId: req.currentRealmIds?.string_id,
});
Expand All @@ -238,16 +238,25 @@ router.get(
RealmPermissions.accessLockedBoardsOnRealm
),
});
// Adds the pinned order to the board summaries
const pins = summaries
.filter((board: any) => board.pinned)
.reduce((result: any, current: any) => {
result[current.slug] = {
...current,
index: boards.find(({ slug }: any) => current.slug == slug)
.pinned_order,
};
return result;
}, {});
.filter((board) => board.pinned)
.reduce(
(result, current) => {
result[current.slug] = {
...current,
index: boards.find(({ slug }) => current.slug == slug)!
.pinned_order!,
};
return result;
},
{} as Record<
string,
(typeof summaries)[0] & {
index: number;
}
>
);

const pinsDataResponse = { pinned_boards: pins };
res.status(200).json(pinsDataResponse);
Expand Down
14 changes: 8 additions & 6 deletions test/data/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const BOBATAN_PINNED_BOARDS = {
avatar_url: "/anime.png",
delisted: false,
id: "4b30fb7c-2aca-4333-aa56-ae8623a92b65",
index: "2",
index: 2,
logged_in_only: false,
muted: false,
pinned: true,
Expand All @@ -27,7 +27,7 @@ export const BOBATAN_PINNED_BOARDS = {
avatar_url: "/gore.png",
delisted: false,
id: "c6d3d10e-8e49-4d73-b28a-9d652b41beec",
index: "1",
index: 1,
logged_in_only: false,
muted: false,
pinned: true,
Expand Down Expand Up @@ -100,10 +100,12 @@ export const JERSEY_DEVIL_BOBADEX = {
],
};

export const CROWN_ACCESSORY_EXTERNAL_ID = "9e593709-419f-4b2c-b7ee-88ed47884c3c"
export const CROWN_ACCESSORY_EXTERNAL_ID =
"9e593709-419f-4b2c-b7ee-88ed47884c3c";

export const MEMESTER_ROLE_EXTERNAL_ID = "70485a1e-4ce9-4064-bd87-440e16b2f219"
export const MEMESTER_ROLE_EXTERNAL_ID = "70485a1e-4ce9-4064-bd87-440e16b2f219";

export const GOREMASTER_ROLE_EXTERNAL_ID = "e5f86f53-6dcd-4f15-b6ea-6ca1f088e62d"
export const GOREMASTER_ROLE_EXTERNAL_ID =
"e5f86f53-6dcd-4f15-b6ea-6ca1f088e62d";

export const OWNER_ROLE_EXTERNAL_ID = "3df1d417-c36a-43dd-aaba-9590316ffc32"
export const OWNER_ROLE_EXTERNAL_ID = "3df1d417-c36a-43dd-aaba-9590316ffc32";
Loading
Loading