Skip to content

Commit

Permalink
location location location
Browse files Browse the repository at this point in the history
  • Loading branch information
willruggiano committed May 16, 2024
1 parent 993ac3a commit 3e4a9e4
Show file tree
Hide file tree
Showing 11 changed files with 122 additions and 21 deletions.
3 changes: 1 addition & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,8 @@ COPY . .

# test and build
ARG NODE_ENV=production
ENV NODE_ENV=${NODE_ENV}
RUN bun test
RUN bun compile
RUN bun test

# copy production dependencies and source code into final image.
FROM base AS release
Expand Down
3 changes: 3 additions & 0 deletions copilot/graphql/manifest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ memory: 2048
secrets:
CLERK_PUBLIC_KEY: /copilot/${COPILOT_APPLICATION_NAME}/${COPILOT_ENVIRONMENT_NAME}/secrets/clerk-public-key

variables:
NODE_ENV: production

# You can override any of the values defined above by environment.
environments:
dev:
Expand Down
3 changes: 3 additions & 0 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,8 @@ deploy:
copilot env deploy --name {{deploy_env}}
copilot deploy --env {{deploy_env}}

package:
docker build -t {{image_name}} .

start:
docker compose up --detach && docker compose logs -f
7 changes: 4 additions & 3 deletions lib/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export default {
`;

if (!user) {
return res.status(404).send("User does not exist");
return res.status(404).send({ message: "User does not exist" });
}

req["x-tendrel-user"] = user;
Expand All @@ -50,10 +50,11 @@ export default {
e instanceof JsonWebTokenError ||
(e instanceof GraphQLError && e.extensions.code === 401)
) {
return res.status(401).send(e.message);
return res.status(401).send({ message: e.message });
}

return next(e);
console.error(e);
return res.status(500).send(e);
}

return next();
Expand Down
30 changes: 30 additions & 0 deletions lib/datasources/location.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { assertAuthenticated } from "@/auth";
import type { Context, Location } from "@/schema";
import Dataloader from "dataloader";
import { sql } from "./postgres";

export default (ctx: Omit<Context, "orm">) =>
new Dataloader<string, Location>(async keys => {
assertAuthenticated(ctx);

const rows = await sql<Location[]>`
SELECT
l.locationuuid AS id,
l.locationnameid AS name_id,
p.locationuuid AS parent_id,
s.locationuuid AS site_id
FROM public.location AS l
INNER JOIN public.location AS s
ON l.locationsiteid = s.locationid
LEFT JOIN public.location AS p
ON l.locationparentid = p.locationid
WHERE l.locationuuid IN ${sql(keys)};
`;

const byId = rows.reduce(
(acc, row) => acc.set(row.id as string, row),
new Map<string, Location>(),
);

return keys.map(key => byId.get(key) ?? new Error(`${key} does not exist`));
});
4 changes: 3 additions & 1 deletion lib/datasources/postgres.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import z from "myzod";
import postgres from "postgres";

import makeLanguageLoader from "./language";
import makeLocationLoader from "./location";
import makeNameLoader from "./name";
import makeOrganizationLoader from "./organization";
import makeUserLoader from "./user";
Expand Down Expand Up @@ -43,13 +44,14 @@ export const sql = postgres({
password: DB_PASSWORD,
host: DB_HOST,
port: DB_PORT,
db: DB_NAME,
database: DB_NAME,
max: DB_MAX_CONNECTIONS,
});

export function orm(ctx: Omit<Context, "orm">) {
return {
language: makeLanguageLoader(ctx),
location: makeLocationLoader(ctx),
name: makeNameLoader(ctx),
organization: makeOrganizationLoader(ctx),
user: makeUserLoader(ctx),
Expand Down
39 changes: 37 additions & 2 deletions lib/schema/resolvers/Location.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,47 @@
import { assertAuthenticated } from "@/auth";
import type { LocationResolvers, Name } from "@/schema";
import { sql } from "@/datasources/postgres";
import type { LocationResolvers } from "@/schema";
import { isValue } from "@/util";

export const Location: LocationResolvers = {
async name(parent, _, ctx) {
async children(parent, { options }, ctx) {
assertAuthenticated(ctx);
const childIds = await sql<{ id: string }[]>`
SELECT locationuuid AS id
FROM public.location
WHERE
locationparentid = (
SELECT locationid
FROM public.location
WHERE locationuuid = ${parent.id}
)
${
options?.cornerstone
? sql`AND locationiscornerstone = ${true}`
: sql``
}
${options?.site ? sql`AND locationistop = ${true}` : sql``}
`;
const children = await ctx.orm.location.loadMany(childIds.map(c => c.id));
return children.filter(isValue);
},
name(parent, _, ctx) {
assertAuthenticated(ctx);
return ctx.orm.name.load({
id: parent.name_id as string,
language_id: ctx.user.language_id,
});
},
parent(parent, _, ctx) {
assertAuthenticated(ctx);
return ctx.orm.location.load(parent.parent_id as string);
},
site(parent, _, ctx) {
assertAuthenticated(ctx);
return ctx.orm.location.load(parent.site_id as string);
},
tags(parent, _, ctx) {
assertAuthenticated(ctx);
return [];
},
};
9 changes: 9 additions & 0 deletions lib/schema/resolvers/Query/location.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type { QueryResolvers } from "@/schema";

export const location: NonNullable<QueryResolvers["location"]> = async (
_,
{ id },
{ orm },
) => {
return await orm.location.load(id);
};
24 changes: 18 additions & 6 deletions lib/schema/resolvers/Query/locations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,32 @@ import type { Location, QueryResolvers } from "@/schema";

export const locations: NonNullable<QueryResolvers["locations"]> = async (
_,
{ customerId },
{ customerId, options },
ctx,
) => {
assertAuthenticated(ctx);

return await sql<Location[]>`
SELECT
l.locationuuid AS id,
l.locationnameid AS name_id,
l.locationparentid AS parent_id
p.locationuuid AS parent_id,
s.locationuuid AS site_id
FROM public.location AS l
INNER JOIN public.customer AS c
ON l.locationcustomerid = c.customerid
INNER JOIN public.location AS s
ON l.locationsiteid = s.locationid
LEFT JOIN public.location AS p
ON l.locationparentid = p.locationid
WHERE
c.customeruuid = ${customerId};
l.locationcustomerid = (
SELECT customerid
FROM public.customer
WHERE customeruuid = ${customerId}
)
${
options?.cornerstone
? sql`AND l.locationiscornerstone = ${true}`
: sql``
}
${options?.site ? sql`AND l.locationistop = ${true}` : sql``}
`;
};
14 changes: 7 additions & 7 deletions lib/schema/schema.gql
Original file line number Diff line number Diff line change
Expand Up @@ -34,26 +34,26 @@ type Customer {

type Location {
id: ID!
customer: Customer!
name: Name!
name_id: ID!
parent: Location
parent_id: ID!
parent_id: ID
site: Location!
site_id: ID!
children(options: LocationsQueryOptions): [Location!]!
tags: [Tag!]!
}

type Query {
customers: [Customer!]!
customer(id: ID!): Customer!
locations(customerId: ID!, options: LocationsQueryOptions): [Location!]!
location(id: ID!): Location!
}

input LocationsQueryOptions {
cornerstone: ID
parent: ID
site: ID
isCornerstone: Boolean
isSite: Boolean
cornerstone: Boolean
site: Boolean
}

# type Mutation {
Expand Down
7 changes: 7 additions & 0 deletions lib/util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export function isError<T>(e: T | Error): e is Error {
return e instanceof Error;
}

export function isValue<T>(v: T | Error): v is T {
return !isError(v);
}

0 comments on commit 3e4a9e4

Please sign in to comment.