Skip to content

Commit

Permalink
✨ feat(documents): manage document categories, find single document (#…
Browse files Browse the repository at this point in the history
…762)

* chore: add graphql queries

* chore: update domain models

* chore: add document categories to repo, find documents

* chore: add categories to services

* chore: lint

* chore: cover error cases for delete
  • Loading branch information
larwaa authored Jun 26, 2024
1 parent 4ba10a9 commit 9e4fc46
Show file tree
Hide file tree
Showing 20 changed files with 928 additions and 6 deletions.
19 changes: 19 additions & 0 deletions schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,10 @@ scalar Date

scalar DateTime

type DeleteDocumentCategoryResponse {
category: DocumentCategory!
}

input DeleteDocumentInput {
id: ID!
}
Expand Down Expand Up @@ -359,6 +363,19 @@ input DocumentCategoryInput {
name: String!
}

type DocumentCategoryResponse {
categories: [DocumentCategory!]!
total: Int!
}

input DocumentInput {
id: ID!
}

type DocumentResponse {
document: Document
}

input DocumentsCategoryInput {
id: ID!
}
Expand Down Expand Up @@ -1158,6 +1175,8 @@ type Query {
bookings(data: BookingsInput): BookingsResponse!
cabins: CabinsResponse!
categories: EventCategoriesResponse!
document(data: DocumentInput!): DocumentResponse!
documentCategories: DocumentCategoryResponse!
documents(data: DocumentsInput): DocumentsResponse!
event(data: EventInput!): EventResponse!
events(data: EventsInput): EventsResponse!
Expand Down
28 changes: 28 additions & 0 deletions src/domain/documents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,34 @@ type DocumentService = {
{ documents: Document[]; total: number },
InternalServerError | PermissionDeniedError | UnauthorizedError
>;
find(
ctx: Context,
params: { id: string },
): ResultAsync<
{ document: Document },
| NotFoundError
| InternalServerError
| PermissionDeniedError
| UnauthorizedError
>;
};
categories: {
findMany(
ctx: Context,
): ResultAsync<
{ categories: DocumentCategory[]; total: number },
InternalServerError | PermissionDeniedError | UnauthorizedError
>;
delete(
ctx: Context,
params: { id: string },
): ResultAsync<
{ category: DocumentCategory },
| NotFoundError
| InternalServerError
| PermissionDeniedError
| UnauthorizedError
>;
};
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import type { DeleteDocumentCategoryResponseResolvers } from "./../../types.generated.js";
export const DeleteDocumentCategoryResponse: DeleteDocumentCategoryResponseResolvers =
{
/* Implement DeleteDocumentCategoryResponse resolver logic here */
};
4 changes: 4 additions & 0 deletions src/graphql/documents/resolvers/DocumentCategoryResponse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import type { DocumentCategoryResponseResolvers } from "./../../types.generated.js";
export const DocumentCategoryResponse: DocumentCategoryResponseResolvers = {
/* Implement DocumentCategoryResponse resolver logic here */
};
4 changes: 4 additions & 0 deletions src/graphql/documents/resolvers/DocumentResponse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import type { DocumentResponseResolvers } from "./../../types.generated.js";
export const DocumentResponse: DocumentResponseResolvers = {
/* Implement DocumentResponse resolver logic here */
};
23 changes: 23 additions & 0 deletions src/graphql/documents/resolvers/Query/document.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import type { QueryResolvers } from "./../../../types.generated.js";
export const document: NonNullable<QueryResolvers["document"]> = async (
_parent,
{ data },
ctx,
) => {
const result = await ctx.documents.documents.find(ctx, { id: data.id });
if (!result.ok) {
switch (result.error.name) {
case "NotFoundError":
case "PermissionDeniedError":
case "UnauthorizedError":
return {
document: null,
};
default:
throw result.error;
}
}
return {
document: result.data.document,
};
};
156 changes: 156 additions & 0 deletions src/graphql/documents/resolvers/Query/document.unit.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import { faker } from "@faker-js/faker";
import { Document } from "~/domain/documents.js";
import {
InternalServerError,
NotFoundError,
PermissionDeniedError,
UnauthorizedError,
} from "~/domain/errors.js";
import { createMockApolloServer } from "~/graphql/test-clients/mock-apollo-server.js";
import { graphql } from "~/graphql/test-clients/unit/gql.js";
import { Result } from "~/lib/result.js";

describe("Document queries", () => {
describe("{ document }", () => {
it("returns a document", async () => {
const { documents, client } = createMockApolloServer();

const expected = new Document({
id: faker.string.uuid(),
createdAt: new Date(),
fileId: faker.string.uuid(),
name: faker.word.adjective(),
updatedAt: new Date(),
categories: [],
description: faker.lorem.paragraph(),
});

documents.documents.find.mockResolvedValue({
ok: true,
data: {
document: expected,
},
});

const { data, errors } = await client.query({
query: graphql(`
query Document {
document(data: { id: "123"}) {
document {
id
}
}
}
`),
});

expect(errors).toBeUndefined();
expect(data).toEqual({
document: {
document: {
id: expected.id,
},
},
});
});

it("returns null if not found", async () => {
const { documents, client } = createMockApolloServer();
documents.documents.find.mockResolvedValue(
Result.error(new NotFoundError("")),
);

const { data, errors } = await client.query({
query: graphql(`
query Document {
document(data: { id: "123"}) {
document {
id
}
}
}
`),
});

expect(errors).toBeUndefined();
expect(data).toEqual({
document: {
document: null,
},
});
});

it("returns null on unauthorized error", async () => {
const { documents, client } = createMockApolloServer();
documents.documents.find.mockResolvedValue(
Result.error(new UnauthorizedError("")),
);

const { data, errors } = await client.query({
query: graphql(`
query Document {
document(data: { id: "123"}) {
document {
id
}
}
}
`),
});

expect(errors).toBeUndefined();
expect(data).toEqual({
document: {
document: null,
},
});
});

it("returns null on permission denied", async () => {
const { documents, client } = createMockApolloServer();
documents.documents.find.mockResolvedValue(
Result.error(new PermissionDeniedError("")),
);

const { data, errors } = await client.query({
query: graphql(`
query Document {
document(data: { id: "123"}) {
document {
id
}
}
}
`),
});

expect(errors).toBeUndefined();
expect(data).toEqual({
document: {
document: null,
},
});
});

it("throws on internal server error", async () => {
const { documents, client } = createMockApolloServer();
documents.documents.find.mockResolvedValue(
Result.error(new InternalServerError("something went wrong")),
);

const { errors } = await client.query({
query: graphql(`
query Document {
document(data: { id: "123"}) {
document {
id
}
}
}
`),
});

expect(errors).toBeDefined();
});
});
});
21 changes: 21 additions & 0 deletions src/graphql/documents/resolvers/Query/documentCategories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { QueryResolvers } from "./../../../types.generated.js";
export const documentCategories: NonNullable<
QueryResolvers["documentCategories"]
> = async (_parent, _arg, ctx) => {
const result = await ctx.documents.categories.findMany(ctx);

if (!result.ok) {
switch (result.error.name) {
case "PermissionDeniedError":
case "UnauthorizedError":
return {
categories: [],
total: 0,
};
default:
throw result.error;
}
}

return result.data;
};
Loading

0 comments on commit 9e4fc46

Please sign in to comment.