Skip to content

Commit

Permalink
✨ feat(organizations): add color scheme, change member role (#765)
Browse files Browse the repository at this point in the history
* ✨ feat(organizations): color and change role

* chore: test color validation

* chore: use docker hub for base image
  • Loading branch information
larwaa authored Jun 26, 2024
1 parent a9dcaa4 commit df2fb06
Show file tree
Hide file tree
Showing 21 changed files with 536 additions and 39 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM public.ecr.aws/docker/library/node:lts
FROM node:lts
WORKDIR /usr/src/app

ENV PNPM_HOME="/pnpm"
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile.prod
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM public.ecr.aws/docker/library/node:lts-slim AS base
FROM node:lts-slim AS base
WORKDIR /usr/src/app

LABEL org.opencontainers.image.source https://github.com/rubberdok/indok-api
Expand Down
1 change: 1 addition & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,7 @@ model Organization {
description String @default("")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
colorScheme String?
events Event[]
members Member[]
Expand Down
29 changes: 29 additions & 0 deletions schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,11 @@ type CreateOrderResponse {
}

input CreateOrganizationInput {
"""
The primary color (hex) for the organization, for example: #FF0000 for red.
"""
colorScheme: String

"""The description of the organization, cannot exceed 10 000 characters"""
description: String

Expand Down Expand Up @@ -858,6 +863,9 @@ type Mutation {
Passing null or omitting a value will leave the value unchanged.
"""
updateOrganization(data: UpdateOrganizationInput!): UpdateOrganizationResponse!

"""Change the role of a member in the organization"""
updateRole(data: UpdateRoleInput!): UpdateRoleResponse!
updateUser(data: UpdateUserInput!): UpdateUserResponse!
uploadFile(data: UploadFileInput!): UploadFileResponse!
}
Expand Down Expand Up @@ -969,6 +977,10 @@ type OrdersResponse {
}

type Organization {
"""
The primary color (hex) for the organization, for example: #FF0000 for red.
"""
colorScheme: String
description: String!
events: [Event!]!

Expand Down Expand Up @@ -1571,6 +1583,11 @@ type UpdateListingResponse {
}

input UpdateOrganizationInput {
"""
The primary color (hex) for the organization, for example: #FF0000 for red.
"""
colorScheme: String

"""
The new description of the organization, cannot exceed 10 000 characters
Omitting the value or passing null will leave the description unchanged
Expand Down Expand Up @@ -1598,6 +1615,18 @@ type UpdateOrganizationResponse {
organization: Organization!
}

input UpdateRoleInput {
"""The ID of the member to change the role of"""
memberId: ID!

"""The new role of the member"""
role: Role!
}

type UpdateRoleResponse {
member: Member!
}

input UpdateSlotInput {
capacity: Int
gradeYears: [Int!]
Expand Down
2 changes: 2 additions & 0 deletions src/domain/organizations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class Organization {
id: string;
name: string;
description: string;
colorScheme?: string | null;
logoFileId?: string | null;
featurePermissions: FeaturePermissionType[];

Expand All @@ -28,6 +29,7 @@ class Organization {
this.description = params.description;
this.logoFileId = params.logoFileId;
this.featurePermissions = params.featurePermissions;
this.colorScheme = params.colorScheme;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { MutationResolvers } from "./../../../types.generated.js";
export const createOrganization: NonNullable<
MutationResolvers["createOrganization"]
> = async (_parent, { data }, ctx) => {
const { name, description, featurePermissions } = data;
const { name, description, featurePermissions, colorScheme } = data;
ctx.log.info(
{ name, description, featurePermissions },
"Creating organization",
Expand All @@ -11,6 +11,7 @@ export const createOrganization: NonNullable<
name,
description,
featurePermissions,
colorScheme,
});
return {
organization,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ import type { MutationResolvers } from "./../../../types.generated.js";
export const updateOrganization: NonNullable<
MutationResolvers["updateOrganization"]
> = async (_parent, { data }, ctx) => {
const { id, name, description, featurePermissions, logoFileId } = data;
const { id, name, description, featurePermissions, logoFileId, colorScheme } =
data;

const organization = await ctx.organizations.organizations.update(ctx, id, {
name,
description,
featurePermissions,
logoFileId,
colorScheme,
});
return { organization };
};
16 changes: 16 additions & 0 deletions src/graphql/organizations/resolvers/Mutation/updateRole.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { MutationResolvers } from "./../../../types.generated.js";
export const updateRole: NonNullable<MutationResolvers["updateRole"]> = async (
_parent,
{ data },
ctx,
) => {
const result = await ctx.organizations.members.updateRole(ctx, {
memberId: data.memberId,
newRole: data.role,
});

if (!result.ok) {
throw result.error;
}
return result.data;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { faker } from "@faker-js/faker";
import { InternalServerError } from "~/domain/errors.js";
import {
OrganizationMember,
OrganizationRole,
} from "~/domain/organizations.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("Organiation mutations", () => {
describe("updateRole", () => {
it("updates a role", async () => {
const { client, organizationService } = createMockApolloServer();

organizationService.members.updateRole.mockResolvedValue({
ok: true,
data: {
member: new OrganizationMember({
id: faker.string.uuid(),
role: OrganizationRole.ADMIN,
organizationId: faker.string.uuid(),
userId: faker.string.uuid(),
}),
},
});

const { errors, data } = await client.mutate({
mutation: graphql(`
mutation UpdateRole($data: UpdateRoleInput!) {
updateRole(data: $data) {
member {
id
}
}
}
`),
variables: {
data: {
memberId: faker.string.uuid(),
role: OrganizationRole.ADMIN,
},
},
});

expect(errors).toBeUndefined();
expect(data).toEqual({
updateRole: {
member: {
id: expect.any(String),
},
},
});
});

it("throws on error", async () => {
const { client, organizationService } = createMockApolloServer();

organizationService.members.updateRole.mockResolvedValue(
Result.error(new InternalServerError("")),
);

const { errors } = await client.mutate({
mutation: graphql(`
mutation UpdateRole($data: UpdateRoleInput!) {
updateRole(data: $data) {
member {
id
}
}
}
`),
variables: {
data: {
memberId: faker.string.uuid(),
role: OrganizationRole.ADMIN,
},
},
});

expect(errors).toBeDefined();
});
});
});
4 changes: 4 additions & 0 deletions src/graphql/organizations/resolvers/UpdateRoleResponse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import type { UpdateRoleResponseResolvers } from "./../../types.generated.js";
export const UpdateRoleResponse: UpdateRoleResponseResolvers = {
/* Implement UpdateRoleResponse resolver logic here */
};
32 changes: 32 additions & 0 deletions src/graphql/organizations/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ type Mutation {
Remove a member from the organization by the ID of the membership.
"""
removeMember(data: RemoveMemberInput!): RemoveMemberResponse!

"""
Change the role of a member in the organization
"""
updateRole(data: UpdateRoleInput!): UpdateRoleResponse!
}

type Organization {
Expand All @@ -52,6 +57,10 @@ type Organization {
events: [Event!]!
listings: [Listing!]!
logo: RemoteFile
"""
The primary color (hex) for the organization, for example: #FF0000 for red.
"""
colorScheme: String
}

type Member {
Expand Down Expand Up @@ -86,6 +95,21 @@ enum Role {
MEMBER
}

input UpdateRoleInput {
"""
The ID of the member to change the role of
"""
memberId: ID!
"""
The new role of the member
"""
role: Role!
}

type UpdateRoleResponse {
member: Member!
}

input UpdateOrganizationInput {
"""
The ID of the organization to update
Expand All @@ -107,6 +131,10 @@ input UpdateOrganizationInput {
"""
featurePermissions: [FeaturePermission!]
logoFileId: ID
"""
The primary color (hex) for the organization, for example: #FF0000 for red.
"""
colorScheme: String
}

type UpdateOrganizationResponse {
Expand All @@ -127,6 +155,10 @@ input CreateOrganizationInput {
Requires that the current user is a super user, otherwise, this field is ignored.
"""
featurePermissions: [FeaturePermission!]
"""
The primary color (hex) for the organization, for example: #FF0000 for red.
"""
colorScheme: String
}

type CreateOrganizationResponse {
Expand Down
13 changes: 13 additions & 0 deletions src/lib/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ interface IOrganizationService {
name: string;
description?: string | null;
featurePermissions?: FeaturePermissionType[] | null;
colorScheme?: string | null;
},
): Promise<Organization>;
update(
Expand All @@ -108,6 +109,7 @@ interface IOrganizationService {
description: string | null;
featurePermissions: FeaturePermissionType[] | null;
logoFileId: string | null;
colorScheme: string | null;
}>,
): Promise<Organization>;
get(id: string): Promise<Organization>;
Expand Down Expand Up @@ -149,6 +151,17 @@ interface IOrganizationService {
{ members: OrganizationMember[] },
PermissionDeniedError | UnauthorizedError
>;
updateRole(
ctx: Context,
params: { memberId: string; newRole: OrganizationRoleType },
): ResultAsync<
{ member: OrganizationMember },
| PermissionDeniedError
| UnauthorizedError
| InternalServerError
| InvalidArgumentErrorV2
| NotFoundError
>;
};
permissions: {
hasFeaturePermission(
Expand Down
Loading

0 comments on commit df2fb06

Please sign in to comment.