Skip to content

Commit

Permalink
Merge pull request #18 from dcramer/ref/overhaul-api
Browse files Browse the repository at this point in the history
ref: Overhaul the api to use jsonschema
  • Loading branch information
dcramer committed May 18, 2023
2 parents 0dda8f9 + 35e0e75 commit d7af838
Show file tree
Hide file tree
Showing 81 changed files with 1,607 additions and 941 deletions.
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,
};
},
};

0 comments on commit d7af838

Please sign in to comment.