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

ref: Overhaul the api to use jsonschema #18

Merged
merged 2 commits into from May 18, 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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 7 additions & 1 deletion README.md
@@ -1,4 +1,10 @@
# peated
# Peated

The application that powers peated.app.

For more details, take a look at https://peated.app/about

A Discord is available if you want to contribute: https://discord.gg/d7GFPfy88Z

## Dev

Expand Down
42 changes: 40 additions & 2 deletions apps/api/src/app.ts
Expand Up @@ -7,10 +7,28 @@ import config from "./config";
import { router } from "./routes";

import { initSentry } from "./instruments";
import bottleSchema from "./schemas/bottle";
import entitySchema from "./schemas/entity";
import FastifySentry from "./sentryPlugin";

import {
bottleSchema,
newBottleSchema,
updateBottleSchema,
} from "./schemas/bottle";
import { collectionSchema } from "./schemas/collection";
import { commentSchema, newCommentSchema } from "./schemas/comment";
import { editionSchema, newEditionSchema } from "./schemas/edition";
import {
entitySchema,
newEntitySchema,
updateEntitySchema,
} from "./schemas/entity";
import { error401Schema } from "./schemas/errors";
import { followingSchema } from "./schemas/follow";
import { notificationSchema } from "./schemas/notification";
import pagingSchema from "./schemas/paging";
import { newTastingSchema, tastingSchema } from "./schemas/tasting";
import { updateUserSchema, userSchema } from "./schemas/user";

initSentry({
dsn: config.SENTRY_DSN,
release: config.VERSION,
Expand Down Expand Up @@ -59,8 +77,28 @@ export default async function buildFastify(options = {}) {
},
});
app.register(FastifyCors, { credentials: true, origin: config.CORS_HOST });

app.addSchema(bottleSchema);
app.addSchema(newBottleSchema);
app.addSchema(updateBottleSchema);
app.addSchema(entitySchema);
app.addSchema(newEntitySchema);
app.addSchema(updateEntitySchema);
app.addSchema(followingSchema);
app.addSchema(pagingSchema);
app.addSchema(userSchema);
app.addSchema(updateUserSchema);
app.addSchema(notificationSchema);
app.addSchema(tastingSchema);
app.addSchema(collectionSchema);
app.addSchema(commentSchema);
app.addSchema(newCommentSchema);
app.addSchema(newTastingSchema);
app.addSchema(editionSchema);
app.addSchema(newEditionSchema);

app.addSchema(error401Schema);

app.register(router);
app.register(FastifySentry);

Expand Down
2 changes: 1 addition & 1 deletion apps/api/src/bin/mocks.ts
Expand Up @@ -63,7 +63,7 @@ program
await createNotification(db, {
fromUserId: follow.fromUserId,
objectType: objectTypeFromSchema(follows),
objectId: follow.fromUserId,
objectId: follow.id,
userId: follow.toUserId,
createdAt: follow.createdAt,
});
Expand Down
16 changes: 9 additions & 7 deletions apps/api/src/lib/auth.ts
Expand Up @@ -3,20 +3,22 @@ import { sign, verify } from "jsonwebtoken";
import config from "../config";
import { NewUser, User, users } from "../db/schema";
import { random } from "./rand";
import { SerializedUser, serializeUser } from "./serializers/user";
import { serialize } from "./serializers";
import { UserSerializer } from "./serializers/user";

export const createAccessToken = (user: User): Promise<string | undefined> => {
export const createAccessToken = async (
user: User,
): Promise<string | undefined> => {
const payload = await serialize(UserSerializer, user, user);
return new Promise<string | undefined>((res, rej) => {
sign(serializeUser(user, user), config.JWT_SECRET, {}, (err, token) => {
sign(payload, config.JWT_SECRET, {}, (err, token) => {
if (err) rej(err);
res(token);
});
});
};

export const verifyToken = (
token: string | undefined,
): Promise<SerializedUser> => {
export const verifyToken = (token: string | undefined): Promise<any> => {
return new Promise((res, rej) => {
if (!token) {
rej("invalid token");
Expand All @@ -31,7 +33,7 @@ export const verifyToken = (
if (!decoded || typeof decoded === "string") {
rej("invalid token");
}
res(decoded as SerializedUser);
res(decoded);
});
});
};
Expand Down
4 changes: 4 additions & 0 deletions apps/api/src/lib/filter.ts
Expand Up @@ -6,3 +6,7 @@ export const select = (obj: object, ...props: string[]) =>

export const omit = (obj: object, ...props: string[]) =>
filter(obj, (k) => !props.includes(k));

export function notEmpty<T>(value: T | null | undefined): value is T {
return value !== null && value !== undefined;
}
77 changes: 62 additions & 15 deletions apps/api/src/lib/serializers/bottle.ts
@@ -1,18 +1,65 @@
import { Bottle, Entity, User } from "../../db/schema";
import { inArray } from "drizzle-orm";
import { Result, Serializer, serialize } from ".";
import { db } from "../../db";
import { Bottle, User, bottlesToDistillers, entities } from "../../db/schema";
import { EntitySerializer } from "./entity";

export const serializeBottle = (
bottle: Bottle & {
brand: Entity;
distillers?: Entity[];
export const BottleSerializer: Serializer<Bottle> = {
attrs: async (itemList: Bottle[], currentUser?: User) => {
const itemIds = itemList.map((t) => t.id);

const distillerList = await db
.select()
.from(bottlesToDistillers)
.where(inArray(bottlesToDistillers.bottleId, itemIds));

const entityIds = Array.from(
new Set([
...itemList.map((i) => i.brandId),
...distillerList.map((d) => d.distillerId),
]),
);

const entityList = await db
.select()
.from(entities)
.where(inArray(entities.id, entityIds));
const entitiesById = Object.fromEntries(
(await serialize(EntitySerializer, entityList, currentUser)).map(
(data, index) => [entityList[index].id, data],
),
);

const distillersByBottleId: {
[bottleId: number]: Result;
} = {};
distillerList.forEach((d) => {
if (!distillersByBottleId[d.bottleId])
distillersByBottleId[d.bottleId] = [entitiesById[d.distillerId]];
else distillersByBottleId[d.bottleId].push(entitiesById[d.distillerId]);
});

return Object.fromEntries(
itemList.map((item) => {
return [
item.id,
{
brand: entitiesById[item.brandId],
distillers: distillersByBottleId[item.id] || [],
},
];
}),
);
},

item: (item: Bottle, attrs: Record<string, any>, currentUser?: User) => {
return {
id: `${item.id}`,
name: item.name,
statedAge: item.statedAge,
category: item.category,
brand: attrs.brand,
distillers: attrs.distillers,
};
},
currentUser?: User,
) => {
return {
id: bottle.id,
name: bottle.name,
statedAge: bottle.statedAge,
category: bottle.category,
brand: bottle.brand,
distillers: bottle.distillers || [],
};
};
13 changes: 13 additions & 0 deletions apps/api/src/lib/serializers/collection.ts
@@ -0,0 +1,13 @@
import { Collection, User } from "../../db/schema";

import { Serializer } from ".";

export const CollectionSerializer: Serializer<Collection> = {
item: (item: Collection, attrs: Record<string, any>, currentUser?: User) => {
return {
id: `${item.id}`,
name: item.name,
createdAt: item.createdAt,
};
},
};
42 changes: 42 additions & 0 deletions apps/api/src/lib/serializers/comment.ts
@@ -0,0 +1,42 @@
import { inArray } from "drizzle-orm";
import { Serializer } from ".";
import { db } from "../../db";
import { Comment, User, users } from "../../db/schema";

export const CommentSerializer: Serializer<Comment> = {
attrs: async (itemList: Comment[], currentUser?: User) => {
const usersById = Object.fromEntries(
(
await db
.select()
.from(users)
.where(
inArray(
users.id,
itemList.map((i) => i.createdById),
),
)
).map((u) => [u.id, u]),
);

return Object.fromEntries(
itemList.map((item) => {
return [
item.id,
{
createdBy: usersById[item.id],
},
];
}),
);
},

item: (item: Comment, attrs: Record<string, any>, currentUser?: User) => {
return {
id: `${item.id}`,
comments: item.comment,
createdAt: item.createdAt,
createdBy: attrs.createdBy,
};
},
};
15 changes: 15 additions & 0 deletions apps/api/src/lib/serializers/edition.ts
@@ -0,0 +1,15 @@
import { Edition, User } from "../../db/schema";

import { Serializer } from ".";

export const EditionSerializer: Serializer<Edition> = {
item: (item: Edition, attrs: Record<string, any>, currentUser?: User) => {
return {
id: `${item.id}`,
name: item.name,
barrel: item.barrel,
vintageYear: item.vintageYear,
createdAt: item.createdAt,
};
},
};
19 changes: 19 additions & 0 deletions apps/api/src/lib/serializers/entity.ts
@@ -0,0 +1,19 @@
import { Entity, User } from "../../db/schema";

import { Serializer } from ".";

export const EntitySerializer: Serializer<Entity> = {
item: (item: Entity, attrs: Record<string, any>, currentUser?: User) => {
return {
id: `${item.id}`,
name: item.name,
country: item.country,
region: item.region,
type: item.type,
createdAt: item.createdAt,

totalTastings: item.totalTastings,
totalBottles: item.totalBottles,
};
},
};