From c6872c76ae7ee8788b74def91cc7720f211446cd Mon Sep 17 00:00:00 2001 From: Joshua Tag Howard Date: Thu, 5 Dec 2024 00:53:17 +0000 Subject: [PATCH 01/22] Add @casl/ability dependency and refactor person login logic --- packages/common/lib/api/resources/index.ts | 45 +++++ packages/common/package.json | 1 + .../routes/teams/$teamId/_layout/points.tsx | 2 +- packages/server/package.json | 1 + packages/server/src/lib/auth/casl.ts | 170 ++++++++++++++++++ .../server/src/lib/auth/findPersonForLogin.ts | 62 +------ .../repositories/person/PersonRepository.ts | 53 +++--- tsconfig.json | 2 +- yarn.lock | 47 +++++ 9 files changed, 297 insertions(+), 86 deletions(-) create mode 100644 packages/server/src/lib/auth/casl.ts diff --git a/packages/common/lib/api/resources/index.ts b/packages/common/lib/api/resources/index.ts index e7394cd9..1d750a2b 100644 --- a/packages/common/lib/api/resources/index.ts +++ b/packages/common/lib/api/resources/index.ts @@ -1,3 +1,24 @@ +import { CommitteeNode } from "./Committee.js"; +import { ConfigurationNode } from "./Configuration.js"; +import { DailyDepartmentNotificationNode } from "./DailyDepartmentNotification.js"; +import { DeviceNode } from "./Device.js"; +import { EventNode } from "./Event.js"; +import { FeedNode } from "./Feed.js"; +import { + FundraisingAssignmentNode, + FundraisingEntryNode, +} from "./Fundraising.js"; +import { ImageNode } from "./Image.js"; +import { MarathonNode } from "./Marathon.js"; +import { MarathonHourNode } from "./MarathonHour.js"; +import { MembershipNode } from "./Membership.js"; +import { NotificationDeliveryNode, NotificationNode } from "./Notification.js"; +import { PersonNode } from "./Person.js"; +import { PointEntryNode } from "./PointEntry.js"; +import { PointOpportunityNode } from "./PointOpportunity.js"; +import { SolicitationCodeNode } from "./SolicitationCode.js"; +import { TeamNode } from "./Team.js"; + export { Connection, Edge, @@ -30,3 +51,27 @@ export * from "./PointOpportunity.js"; export * from "./Resource.js"; export * from "./SolicitationCode.js"; export * from "./Team.js"; + +export const ResourceClasses = { + CommitteeNode, + ConfigurationNode, + DailyDepartmentNotificationNode, + DeviceNode, + EventNode, + FeedNode, + FundraisingAssignmentNode, + FundraisingEntryNode, + ImageNode, + MarathonNode, + MarathonHourNode, + MembershipNode, + NotificationNode, + NotificationDeliveryNode, + PersonNode, + PointEntryNode, + PointOpportunityNode, + SolicitationCodeNode, + TeamNode, +} as const; +export type ResourceClasses = + (typeof ResourceClasses)[keyof typeof ResourceClasses]; diff --git a/packages/common/package.json b/packages/common/package.json index 736df676..ab7df2c1 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -56,6 +56,7 @@ "watch": "tsc -w" }, "dependencies": { + "@casl/ability": "^6.7.2", "@graphql-typed-document-node/core": "^3.2.0", "class-validator": "^0.14.1", "graphql": "^16.9.0", diff --git a/packages/portal/src/routes/teams/$teamId/_layout/points.tsx b/packages/portal/src/routes/teams/$teamId/_layout/points.tsx index 1b113232..06932291 100644 --- a/packages/portal/src/routes/teams/$teamId/_layout/points.tsx +++ b/packages/portal/src/routes/teams/$teamId/_layout/points.tsx @@ -64,7 +64,7 @@ export const Route = createFileRoute("/teams/$teamId/_layout/points")({ staticData: { authorizationRules: [ { - accessLevel: AccessLevel.UKY, + accessLevel: AccessLevel.Committee, }, ], }, diff --git a/packages/server/package.json b/packages/server/package.json index 5993d5cf..16d0110b 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -42,6 +42,7 @@ "dependencies": { "@apollo/server": "^4.11.2", "@azure/msal-node": "^2.16.1", + "@casl/ability": "^6.7.2", "@faker-js/faker": "^9.2.0", "@freshgum/typedi": "^0.7.2", "@prisma/client": "^5.22.0", diff --git a/packages/server/src/lib/auth/casl.ts b/packages/server/src/lib/auth/casl.ts new file mode 100644 index 00000000..e82408c0 --- /dev/null +++ b/packages/server/src/lib/auth/casl.ts @@ -0,0 +1,170 @@ +import type { InferSubjects, MongoAbility } from "@casl/ability"; +import { + AbilityBuilder, + createAliasResolver, + createMongoAbility, +} from "@casl/ability"; +import type { + Authorization, + PersonNode, + ResourceClasses, + SimpleTeamMembership, +} from "@ukdanceblue/common"; +import { + AccessLevel, + CommitteeIdentifier, + CommitteeRole, +} from "@ukdanceblue/common"; + +type ResourceSubject = { + [resource in keyof typeof ResourceClasses]: { + kind: resource; + id?: string; + parentType?: string; + parentID?: string; + }; +}; + +type SubjectValue = ResourceSubject[keyof ResourceSubject]; +type Subject = InferSubjects; + +export const Action = { + Create: "create", + Read: "read", + Update: "update", + Delete: "delete", + Deploy: "deploy", + Modify: "modify", + Manage: "manage", +} as const; +export type Action = (typeof Action)[keyof typeof Action]; + +const resolveAction = createAliasResolver({ + modify: ["read", "update", "delete"], +}); + +export type AppAbility = MongoAbility< + | [ + ( + | typeof Action.Create + | typeof Action.Read + | typeof Action.Update + | typeof Action.Delete + | typeof Action.Modify + | typeof Action.Manage + ), + Subject | "all", + ] + | [typeof Action.Deploy, "NotificationNode" | "all"] +>; + +export interface AuthParam extends Authorization { + authenticatedUser: PersonNode | null; + teamMemberships: SimpleTeamMembership[]; +} + +export function getAuthorizationFor({ + accessLevel, + authenticatedUser, + teamMemberships, + effectiveCommitteeRoles, +}: AuthParam): AppAbility { + const { + can: allow, + cannot: forbid, + build, + } = new AbilityBuilder(createMongoAbility); + + function doBuild() { + return build({ + resolveAction, + }); + } + + switch (accessLevel) { + case AccessLevel.SuperAdmin: { + allow(Action.Manage, "all"); + return doBuild(); + } + case AccessLevel.None: { + return doBuild(); + } + case AccessLevel.Admin: { + allow(Action.Manage, [ + "ConfigurationNode", + "MarathonNode", + "MarathonHourNode", + "FundraisingAssignmentNode", + "FundraisingEntryNode", + ]); + // fallthrough + } + case AccessLevel.CommitteeChairOrCoordinator: { + allow(Action.Manage, [ + "CommitteeNode", + "ConfigurationNode", + "DeviceNode", + "EventNode", + "FeedNode", + "ImageNode", + "MembershipNode", + "NotificationDeliveryNode", + "NotificationNode", + "PersonNode", + ]); + // fallthrough + } + case AccessLevel.Committee: { + allow(Action.Read, ["TeamNode", "SolicitationCodeNode"]); + // fallthrough + } + case AccessLevel.UKY: + case AccessLevel.Public: { + allow(Action.Read, [ + "CommitteeNode", + "ConfigurationNode", + "EventNode", + "FeedNode", + "ImageNode", + "TeamNode", + ]); + } + } + + for (const { identifier, role } of effectiveCommitteeRoles) { + switch (identifier) { + case CommitteeIdentifier.viceCommittee: { + allow(role === CommitteeRole.Member ? Action.Modify : Action.Manage, [ + "PersonNode", + ]); + break; + } + case CommitteeIdentifier.dancerRelationsCommittee: { + allow(Action.Manage, ["PointOpportunityNode", "PointEntryNode"]); + break; + } + case CommitteeIdentifier.fundraisingCommittee: { + allow(Action.Manage, [ + "FundraisingEntryNode", + "DailyDepartmentNotificationNode", + "SolicitationCodeNode", + "FundraisingAssignmentNode", + ]); + break; + } + } + } + + if (authenticatedUser) { + allow(Action.Read, "PersonNode", { + id: authenticatedUser.id.id, + }); + } + for (const { teamId, teamType, position } of teamMemberships) { + allow(Action.Read, "TeamNode", { + id: teamId, + }); + } + + return doBuild(); +} diff --git a/packages/server/src/lib/auth/findPersonForLogin.ts b/packages/server/src/lib/auth/findPersonForLogin.ts index db5767cb..a23142b9 100644 --- a/packages/server/src/lib/auth/findPersonForLogin.ts +++ b/packages/server/src/lib/auth/findPersonForLogin.ts @@ -1,6 +1,5 @@ import type { AuthSource, Prisma, PrismaClient } from "@prisma/client"; import type { DbRole } from "@ukdanceblue/common"; -import { MembershipPositionType } from "@ukdanceblue/common"; import { logger } from "#logging/logger.js"; @@ -35,9 +34,7 @@ export async function findPersonForLogin( linkblue?: string | undefined | null; name?: string | undefined | null; dbRole?: DbRole | undefined | null; - }, - memberOf?: (string | number)[], - captainOf?: (string | number)[] + } ) { logger.trace(`Looking for person with auth IDs: ${JSON.stringify(authIds)}`); // TODO: pick specific values of authIds to search for, instead of all of them at once @@ -98,31 +95,6 @@ export async function findPersonForLogin( throw new Error("No email provided for new user"); } - const memberOfIds = await Promise.all( - memberOf?.map(async (teamId) => { - if (typeof teamId === "string") { - const team = await client.team.findUnique({ - where: { uuid: teamId }, - }); - return team?.id; - } else { - return teamId; - } - }) ?? [] - ); - const captainOfIds = await Promise.all( - captainOf?.map(async (teamId) => { - if (typeof teamId === "string") { - const team = await client.team.findUnique({ - where: { uuid: teamId }, - }); - return team?.id; - } else { - return teamId; - } - }) ?? [] - ); - currentPerson = await client.person.create({ data: { authIdPairs: { @@ -136,38 +108,6 @@ export async function findPersonForLogin( email: userInfo.email, name: userInfo.name ?? null, linkblue: userInfo.linkblue?.toLowerCase() ?? null, - memberships: { - createMany: { - data: [ - ...memberOfIds - .filter((id): id is Exclude => id != null) - .map( - ( - id - ): { - teamId: number; - position: MembershipPositionType; - } => ({ - teamId: id, - position: MembershipPositionType.Member, - }) - ), - ...captainOfIds - .filter((id): id is Exclude => id != null) - .map( - ( - id - ): { - teamId: number; - position: MembershipPositionType; - } => ({ - teamId: id, - position: MembershipPositionType.Captain, - }) - ), - ], - }, - }, }, include, }); diff --git a/packages/server/src/repositories/person/PersonRepository.ts b/packages/server/src/repositories/person/PersonRepository.ts index d9e46dc4..08de7e71 100644 --- a/packages/server/src/repositories/person/PersonRepository.ts +++ b/packages/server/src/repositories/person/PersonRepository.ts @@ -122,20 +122,12 @@ export class PersonRepository { linkblue?: string | undefined | null; name?: string | undefined | null; dbRole?: DbRole | undefined | null; - }, - memberOf?: (string | number)[], - captainOf?: (string | number)[] + } ): Promise< Result>, RepositoryError> > { try { - const row = await findPersonForLogin( - this.prisma, - authIds, - userInfo, - memberOf, - captainOf - ); + const row = await findPersonForLogin(this.prisma, authIds, userInfo); return Ok(row); } catch (error) { return handleRepositoryError(error); @@ -297,23 +289,38 @@ export class PersonRepository { committeeRole ?? CommitteeRole.Member ); if (team.correspondingCommittee.parentCommittee) { - addRole( - team.correspondingCommittee.parentCommittee.identifier, - CommitteeRole.Member - ); - if (team.correspondingCommittee.parentCommittee.parentCommittee) { - addRole( - team.correspondingCommittee.parentCommittee.parentCommittee - .identifier, - CommitteeRole.Member - ); + switch (team.correspondingCommittee.parentCommittee.identifier) { + case CommitteeIdentifier.viceCommittee: { + addRole( + CommitteeIdentifier.viceCommittee, + committeeRole ?? CommitteeRole.Member + ); + // fallthrough + } + case CommitteeIdentifier.overallCommittee: { + addRole( + CommitteeIdentifier.overallCommittee, + CommitteeRole.Member + ); + break; + } + default: { + return Err( + new InvariantError( + `Unexpected parent committee ${team.correspondingCommittee.parentCommittee.identifier}` + ) + ); + } } } if ( - committeeRole === CommitteeRole.Chair && - team.correspondingCommittee.identifier === - CommitteeIdentifier.overallCommittee + (committeeRole === CommitteeRole.Chair || + committeeRole === CommitteeRole.Coordinator) && + (team.correspondingCommittee.identifier === + CommitteeIdentifier.overallCommittee || + team.correspondingCommittee.identifier === + CommitteeIdentifier.viceCommittee) ) { for (const child of team.correspondingCommittee.childCommittees) { addRole(child.identifier, committeeRole); diff --git a/tsconfig.json b/tsconfig.json index fb61a51e..a7bb49a9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,7 +7,7 @@ "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true, + // "noFallthroughCasesInSwitch": true, // "exactOptionalPropertyTypes": true, // This is really just more trouble than it's worth "noEmit": true, "noImplicitAny": true, diff --git a/yarn.lock b/yarn.lock index 2891a901..eaf9031b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1788,6 +1788,15 @@ __metadata: languageName: node linkType: hard +"@casl/ability@npm:^6.7.2": + version: 6.7.2 + resolution: "@casl/ability@npm:6.7.2" + dependencies: + "@ucast/mongo2js": "npm:^1.3.0" + checksum: 10/48087a45c507d8d57bbc16e3b5c3e458fc2314306462b1adbf4b11cf51495dba31311fdf6c2527a57b98a66a9566039b34a7e19b94546ac320b91f1c7764be1d + languageName: node + linkType: hard + "@colors/colors@npm:1.5.0": version: 1.5.0 resolution: "@colors/colors@npm:1.5.0" @@ -9167,10 +9176,47 @@ __metadata: languageName: node linkType: hard +"@ucast/core@npm:^1.0.0, @ucast/core@npm:^1.4.1, @ucast/core@npm:^1.6.1": + version: 1.10.2 + resolution: "@ucast/core@npm:1.10.2" + checksum: 10/35c91961b534df03518ade09bf920856355bfadf0a59a8606d91ac4e22a24c2bd470964b2c54764a31e8eacecd4a2768ba8426b6a96c2474c6df305ee9b3f9a1 + languageName: node + linkType: hard + +"@ucast/js@npm:^3.0.0": + version: 3.0.4 + resolution: "@ucast/js@npm:3.0.4" + dependencies: + "@ucast/core": "npm:^1.0.0" + checksum: 10/8a8a5ebe45b29e1885f00a05b293b47c54cbbe506080720c505425cd52bc3e311d16a49069ae8d0f797faa34d439ad066591a7abfebf56e89702c9ad7e57a96a + languageName: node + linkType: hard + +"@ucast/mongo2js@npm:^1.3.0": + version: 1.3.4 + resolution: "@ucast/mongo2js@npm:1.3.4" + dependencies: + "@ucast/core": "npm:^1.6.1" + "@ucast/js": "npm:^3.0.0" + "@ucast/mongo": "npm:^2.4.0" + checksum: 10/5c8428d75babaf7059fd39dd7d9df55e4b187f306fafe674edd630e32abd9ba76dca79c32865e4ccdf53661e7c2c4cc3d98bc3815420a2c668f41aa87880e47b + languageName: node + linkType: hard + +"@ucast/mongo@npm:^2.4.0": + version: 2.4.3 + resolution: "@ucast/mongo@npm:2.4.3" + dependencies: + "@ucast/core": "npm:^1.4.1" + checksum: 10/5bcac2a06df97dda33deab3cfe1d57effd4ac49f27a846de29d6e583ca04f379f63d0def4737ec2c023dd9bbfded8ef2dfa285ed1575b8a320b1c2807c2d6200 + languageName: node + linkType: hard + "@ukdanceblue/common@workspace:^, @ukdanceblue/common@workspace:packages/common": version: 0.0.0-use.local resolution: "@ukdanceblue/common@workspace:packages/common" dependencies: + "@casl/ability": "npm:^6.7.2" "@graphql-typed-document-node/core": "npm:^3.2.0" "@types/luxon": "npm:^3.4.2" "@types/react": "npm:~18.3.12" @@ -9436,6 +9482,7 @@ __metadata: dependencies: "@apollo/server": "npm:^4.11.2" "@azure/msal-node": "npm:^2.16.1" + "@casl/ability": "npm:^6.7.2" "@faker-js/faker": "npm:^9.2.0" "@freshgum/typedi": "npm:^0.7.2" "@prisma/client": "npm:^5.22.0" From d3430b326381bc8438ceeb3be4f6bb35178ac31b Mon Sep 17 00:00:00 2001 From: Joshua Tag Howard Date: Thu, 5 Dec 2024 04:47:50 +0000 Subject: [PATCH 02/22] Add casl library --- .../common/lib/authorization/structures.ts | 1 + packages/server/src/lib/auth/casl.test.ts | 91 +++++++++ packages/server/src/lib/auth/casl.ts | 181 ++++++++++++------ .../src/resolvers/FundraisingEntryResolver.ts | 1 - 4 files changed, 216 insertions(+), 58 deletions(-) create mode 100644 packages/server/src/lib/auth/casl.test.ts diff --git a/packages/common/lib/authorization/structures.ts b/packages/common/lib/authorization/structures.ts index 969e67f1..8f5c8b9e 100644 --- a/packages/common/lib/authorization/structures.ts +++ b/packages/common/lib/authorization/structures.ts @@ -282,6 +282,7 @@ export const committeeNames: Record = { }; export interface Authorization { + /** @deprecated */ dbRole: DbRole; effectiveCommitteeRoles: EffectiveCommitteeRole[]; accessLevel: AccessLevel; diff --git a/packages/server/src/lib/auth/casl.test.ts b/packages/server/src/lib/auth/casl.test.ts new file mode 100644 index 00000000..1437dba8 --- /dev/null +++ b/packages/server/src/lib/auth/casl.test.ts @@ -0,0 +1,91 @@ +import { TeamType } from "@prisma/client"; +import { + AccessLevel, + MembershipPositionType, + PersonNode, +} from "@ukdanceblue/common"; +import { describe } from "vitest"; + +import { Action, getAuthorizationFor } from "./casl.js"; +describe("getAuthorizationFor", (test) => { + test("matches expected behavior for a team captain", ({ expect }) => { + const ability = getAuthorizationFor({ + accessLevel: AccessLevel.UKY, + authenticatedUser: PersonNode.init({ + id: "personId", + email: "email@mail.com", + }), + teamMemberships: [ + { + position: MembershipPositionType.Captain, + teamId: "captainTeamId", + teamType: TeamType.Spirit, + }, + { + position: MembershipPositionType.Member, + teamId: "memberTeamId", + teamType: TeamType.Spirit, + }, + ], + effectiveCommitteeRoles: [], + }); + + expect( + ability.can(Action.Read, { + kind: "PersonNode", + id: "personId", + }) + ).toBe(true); + + expect( + ability.can(Action.Read, { + kind: "PersonNode", + id: "otherPersonId", + }) + ).toBe(false); + + expect( + ability.can(Action.Read, { + kind: "TeamNode", + id: "captainTeamId", + }) + ).toBe(true); + + expect( + ability.can(Action.Modify, { + kind: "FundraisingAssignmentNode", + id: "someId", + withinTeamIds: ["captainTeamId"], + }) + ).toBe(true); + + expect( + ability.can(Action.Read, { + kind: "FundraisingAssignmentNode", + id: "someId", + withinTeamIds: ["memberTeamId"], + }) + ).toBe(false); + + expect( + ability.can(Action.Modify, { + kind: "FundraisingAssignmentNode", + id: "someOtherId", + withinTeamIds: ["otherTeamId"], + }) + ).toBe(false); + + expect(ability.can(Action.Read, "all")).toBe(false); + }); + + test("matches expected behavior for a super-admin", ({ expect }) => { + const ability = getAuthorizationFor({ + accessLevel: AccessLevel.SuperAdmin, + authenticatedUser: null, + effectiveCommitteeRoles: [], + teamMemberships: [], + }); + + expect(ability.can(Action.Manage, "all")).toBe(true); + }); +}); diff --git a/packages/server/src/lib/auth/casl.ts b/packages/server/src/lib/auth/casl.ts index e82408c0..200a3f29 100644 --- a/packages/server/src/lib/auth/casl.ts +++ b/packages/server/src/lib/auth/casl.ts @@ -1,4 +1,8 @@ -import type { InferSubjects, MongoAbility } from "@casl/ability"; +import type { + ExtractSubjectType, + InferSubjects, + MongoAbility, +} from "@casl/ability"; import { AbilityBuilder, createAliasResolver, @@ -14,14 +18,28 @@ import { AccessLevel, CommitteeIdentifier, CommitteeRole, + MembershipPositionType, } from "@ukdanceblue/common"; +type HasWithinTeamIds = "FundraisingAssignmentNode" | "PersonNode"; + +type HasOwnedByUserIds = "FundraisingEntryNode" | "FundraisingAssignmentNode"; + type ResourceSubject = { [resource in keyof typeof ResourceClasses]: { kind: resource; id?: string; - parentType?: string; - parentID?: string; + childOfType?: Subject; + childOfId?: string; + } & (resource extends HasWithinTeamIds + ? { withinTeamIds: string[] } + : { withinTeamIds?: never }) & + (resource extends HasOwnedByUserIds + ? { ownedByUserIds: string[] } + : { ownedByUserIds?: never }); +} & { + AuditNode: { + kind: "AuditNode"; }; }; @@ -30,7 +48,10 @@ type Subject = InferSubjects; export const Action = { Create: "create", + Get: "get", + List: "list", Read: "read", + ReadActive: "readActive", Update: "update", Delete: "delete", Deploy: "deploy", @@ -40,13 +61,17 @@ export const Action = { export type Action = (typeof Action)[keyof typeof Action]; const resolveAction = createAliasResolver({ - modify: ["read", "update", "delete"], + [Action.Modify]: [Action.Read, Action.Update, Action.Delete], + [Action.Read]: [Action.Get, Action.List, Action.ReadActive], }); export type AppAbility = MongoAbility< | [ ( | typeof Action.Create + | typeof Action.Get + | typeof Action.List + | typeof Action.ReadActive | typeof Action.Read | typeof Action.Update | typeof Action.Delete @@ -58,7 +83,7 @@ export type AppAbility = MongoAbility< | [typeof Action.Deploy, "NotificationNode" | "all"] >; -export interface AuthParam extends Authorization { +export interface AuthParam extends Omit { authenticatedUser: PersonNode | null; teamMemberships: SimpleTeamMembership[]; } @@ -71,64 +96,65 @@ export function getAuthorizationFor({ }: AuthParam): AppAbility { const { can: allow, - cannot: forbid, + // cannot: forbid, build, } = new AbilityBuilder(createMongoAbility); function doBuild() { return build({ resolveAction, + detectSubjectType(subject) { + return ((subject as { __typename?: Subject }).__typename ?? + subject.kind) as ExtractSubjectType; + }, }); } - switch (accessLevel) { - case AccessLevel.SuperAdmin: { - allow(Action.Manage, "all"); - return doBuild(); - } - case AccessLevel.None: { - return doBuild(); - } - case AccessLevel.Admin: { - allow(Action.Manage, [ - "ConfigurationNode", - "MarathonNode", - "MarathonHourNode", - "FundraisingAssignmentNode", - "FundraisingEntryNode", - ]); - // fallthrough - } - case AccessLevel.CommitteeChairOrCoordinator: { - allow(Action.Manage, [ - "CommitteeNode", - "ConfigurationNode", - "DeviceNode", - "EventNode", - "FeedNode", - "ImageNode", - "MembershipNode", - "NotificationDeliveryNode", - "NotificationNode", - "PersonNode", - ]); - // fallthrough - } - case AccessLevel.Committee: { - allow(Action.Read, ["TeamNode", "SolicitationCodeNode"]); - // fallthrough - } - case AccessLevel.UKY: - case AccessLevel.Public: { - allow(Action.Read, [ - "CommitteeNode", - "ConfigurationNode", - "EventNode", - "FeedNode", - "ImageNode", - "TeamNode", - ]); - } + if (accessLevel === AccessLevel.None) { + return doBuild(); + } + if (accessLevel === AccessLevel.SuperAdmin) { + allow(Action.Manage, "all"); + return doBuild(); + } + + allow(Action.Get, "DeviceNode"); + allow(Action.ReadActive, [ + "FeedNode", + "ConfigurationNode", + "MarathonNode", + "MarathonHourNode", + ]); + allow(Action.Read, ["CommitteeNode", "EventNode", "ImageNode", "TeamNode"]); + + allow(Action.Read, "TeamNode", "members"); + + if (accessLevel >= AccessLevel.Committee) { + allow(Action.Read, ["SolicitationCodeNode", "MembershipNode"]); + } + + if (accessLevel >= AccessLevel.CommitteeChairOrCoordinator) { + allow(Action.Manage, [ + "CommitteeNode", + "EventNode", + "FeedNode", + "ImageNode", + "MembershipNode", + "PersonNode", + ]); + allow(Action.Read, "NotificationDeliveryNode"); + allow([Action.Modify, Action.Create], "NotificationNode"); + } + + if (accessLevel >= AccessLevel.Admin) { + allow(Action.Manage, [ + "ConfigurationNode", + "MarathonNode", + "MarathonHourNode", + "FundraisingAssignmentNode", + "FundraisingEntryNode", + ]); + allow(Action.Deploy, "NotificationNode"); } for (const { identifier, role } of effectiveCommitteeRoles) { @@ -137,6 +163,14 @@ export function getAuthorizationFor({ allow(role === CommitteeRole.Member ? Action.Modify : Action.Manage, [ "PersonNode", ]); + // fallthrough + } + case CommitteeIdentifier.communityDevelopmentCommittee: + case CommitteeIdentifier.techCommittee: + case CommitteeIdentifier.marketingCommittee: { + if (role !== CommitteeRole.Member) { + allow(Action.Deploy, "NotificationNode"); + } break; } case CommitteeIdentifier.dancerRelationsCommittee: { @@ -157,12 +191,45 @@ export function getAuthorizationFor({ if (authenticatedUser) { allow(Action.Read, "PersonNode", { - id: authenticatedUser.id.id, + id: { + $eq: authenticatedUser.id.id, + }, + }); + allow(Action.Read, "FundraisingAssignmentNode", { + ownedByUserIds: [authenticatedUser.id.id], }); } - for (const { teamId, teamType, position } of teamMemberships) { + + const authTeamMemberships = teamMemberships.map( + (membership) => membership.teamId + ); + if (authTeamMemberships.length > 0) { allow(Action.Read, "TeamNode", { - id: teamId, + id: { $in: authTeamMemberships }, + }); + allow(Action.Read, "MembershipNode", { + childOfType: "TeamNode", + childOfId: { $in: authTeamMemberships }, + }); + } + + allow(Action.Read, "TeamNode", ["child"]); + + const authTeamCaptaincies = teamMemberships + .filter( + (membership) => membership.position === MembershipPositionType.Captain + ) + .map((membership) => membership.teamId); + if (authTeamCaptaincies.length > 0) { + allow(Action.Read, "PersonNode", { + withinTeamIds: { $in: authTeamCaptaincies }, + }); + allow([Action.Modify, Action.Create], "FundraisingAssignmentNode", { + withinTeamIds: { $in: authTeamCaptaincies }, + }); + allow(Action.Read, "FundraisingEntryNode", { + childOfType: "TeamNode", + childOfId: { $in: authTeamCaptaincies }, }); } diff --git a/packages/server/src/resolvers/FundraisingEntryResolver.ts b/packages/server/src/resolvers/FundraisingEntryResolver.ts index 0d91757e..09a8532e 100644 --- a/packages/server/src/resolvers/FundraisingEntryResolver.ts +++ b/packages/server/src/resolvers/FundraisingEntryResolver.ts @@ -44,7 +44,6 @@ import { FundraisingEntryRepository } from "#repositories/fundraising/Fundraisin import { SolicitationCodeRepository } from "#repositories/solicitationCode/SolicitationCodeRepository.js"; import { globalFundraisingAccessParam } from "./accessParams.js"; - @Resolver(() => FundraisingEntryNode) @Service([ DBFundsFundraisingProvider, From a90ca411c3c9a2165020dc5c78fa87cb68d8e33b Mon Sep 17 00:00:00 2001 From: Joshua Tag Howard Date: Thu, 5 Dec 2024 18:54:25 +0000 Subject: [PATCH 03/22] More casl --- packages/common/lib/api/params/LoginState.ts | 8 +- .../lib/api/params/fundraisingAccess.ts | 18 +- .../common/lib/api/resources/Notification.ts | 31 +- .../lib/api/resources/authorization.test.ts | 271 --------- .../lib/authorization/AccessControlParam.ts | 14 + .../common/lib/authorization/accessControl.ts | 517 ++++++++-------- .../lib/authorization/customAccessControl.ts | 147 ----- .../common/lib/authorization/role.test.ts | 89 --- packages/common/lib/authorization/role.ts | 90 ++- .../common/lib/authorization/structures.ts | 4 +- packages/common/lib/error/control.ts | 9 +- packages/common/lib/index.ts | 2 +- packages/portal/graphql/graphql-env.d.ts | 7 +- packages/portal/package.json | 1 + packages/portal/src/config/marathon.tsx | 11 +- .../portal/src/config/refine/authorization.ts | 55 +- .../portal/src/config/refine/resources.tsx | 340 +++++------ .../forms/person/edit/PersonEditor.tsx | 5 +- .../elements/singletons/NavigationMenu.tsx | 4 +- .../elements/viewers/person/PersonViewer.tsx | 15 +- .../src/elements/viewers/team/TeamViewer.tsx | 26 +- packages/portal/src/hooks/useLoginState.ts | 60 +- packages/portal/src/main.tsx | 12 +- packages/portal/src/routes/__root.tsx | 2 - packages/portal/src/routes/admin/logs.tsx | 2 - packages/portal/src/routes/config/index.tsx | 5 +- .../src/routes/events/$eventId/edit.tsx | 2 - .../src/routes/events/$eventId/index.tsx | 2 - packages/portal/src/routes/events/create.tsx | 5 +- packages/portal/src/routes/events/index.tsx | 5 +- packages/portal/src/routes/feed/index.tsx | 2 - .../src/routes/fundraising/$entryId/edit.tsx | 5 +- .../portal/src/routes/fundraising/dbfunds.tsx | 9 +- .../routes/fundraising/ddn/$ddnId/index.tsx | 4 - .../src/routes/fundraising/ddn/index.tsx | 4 - .../src/routes/fundraising/ddn/upload.tsx | 4 - .../portal/src/routes/fundraising/index.tsx | 4 - .../$solicitationCodeId/index.tsx | 5 +- .../fundraising/solicitation-code/create.tsx | 5 +- .../fundraising/solicitation-code/index.tsx | 5 +- packages/portal/src/routes/images/$.tsx | 5 +- packages/portal/src/routes/images/index.tsx | 5 +- packages/portal/src/routes/index.tsx | 5 +- .../src/routes/marathon/$marathonId/edit.tsx | 5 +- .../$marathonId/hours/$hourId/index.tsx | 2 - .../routes/marathon/$marathonId/hours/add.tsx | 5 +- .../src/routes/marathon/$marathonId/index.tsx | 2 - .../portal/src/routes/marathon/create.tsx | 5 +- packages/portal/src/routes/marathon/index.tsx | 5 +- .../notifications/$notificationId/index.tsx | 2 - .../notifications/$notificationId/manage.tsx | 2 - .../src/routes/notifications/create.tsx | 5 +- .../portal/src/routes/notifications/index.tsx | 5 +- .../src/routes/people/$personId/edit.tsx | 2 - .../src/routes/people/$personId/index.tsx | 2 - packages/portal/src/routes/people/bulk.tsx | 5 +- packages/portal/src/routes/people/create.tsx | 5 +- packages/portal/src/routes/people/index.tsx | 34 +- .../src/routes/teams/$teamId/_layout.tsx | 2 - .../teams/$teamId/_layout/fundraising.tsx | 15 +- .../routes/teams/$teamId/_layout/index.tsx | 13 +- .../routes/teams/$teamId/_layout/points.tsx | 16 +- .../portal/src/routes/teams/$teamId/edit.tsx | 2 - packages/portal/src/routes/teams/bulk.tsx | 5 +- packages/portal/src/routes/teams/create.tsx | 5 +- packages/portal/src/routes/teams/index.tsx | 18 +- packages/portal/src/tools/routerAuthCheck.tsx | 50 -- packages/server/src/lib/auth/casl.test.ts | 91 --- packages/server/src/lib/auth/casl.ts | 237 -------- .../src/{resolvers => lib/auth}/context.ts | 236 +++----- packages/server/src/lib/graphqlSchema.ts | 120 +++- .../src/resolvers/ConfigurationResolver.ts | 14 +- .../resolvers/DailyDepartmentNotification.ts | 73 +-- .../server/src/resolvers/DeviceResolver.ts | 6 +- .../server/src/resolvers/EventResolver.ts | 59 +- packages/server/src/resolvers/FeedResolver.ts | 58 +- .../FundraisingAssignmentResolver.ts | 552 +++++++++--------- .../src/resolvers/FundraisingEntryResolver.ts | 134 ++--- .../server/src/resolvers/ImageResolver.ts | 24 +- packages/server/src/resolvers/LoginState.ts | 16 +- .../src/resolvers/MarathonHourResolver.ts | 89 +-- .../server/src/resolvers/MarathonResolver.ts | 71 +-- packages/server/src/resolvers/NodeResolver.ts | 2 +- .../src/resolvers/NotificationResolver.ts | 46 +- .../server/src/resolvers/PersonResolver.ts | 238 +------- .../src/resolvers/PointEntryResolver.ts | 30 +- .../src/resolvers/PointOpportunityResolver.ts | 39 +- .../server/src/resolvers/ReportResolver.ts | 4 +- .../src/resolvers/SolicitationCodeResolver.ts | 18 +- packages/server/src/resolvers/TeamResolver.ts | 232 +------- packages/server/src/resolvers/accessParams.ts | 20 - packages/server/src/server.ts | 6 +- schema.graphql | 57 +- yarn.lock | 1 + 94 files changed, 1364 insertions(+), 3142 deletions(-) delete mode 100644 packages/common/lib/api/resources/authorization.test.ts create mode 100644 packages/common/lib/authorization/AccessControlParam.ts delete mode 100644 packages/common/lib/authorization/customAccessControl.ts delete mode 100644 packages/common/lib/authorization/role.test.ts delete mode 100644 packages/portal/src/tools/routerAuthCheck.tsx delete mode 100644 packages/server/src/lib/auth/casl.test.ts delete mode 100644 packages/server/src/lib/auth/casl.ts rename packages/server/src/{resolvers => lib/auth}/context.ts (52%) delete mode 100644 packages/server/src/resolvers/accessParams.ts diff --git a/packages/common/lib/api/params/LoginState.ts b/packages/common/lib/api/params/LoginState.ts index edec516b..bf17b874 100644 --- a/packages/common/lib/api/params/LoginState.ts +++ b/packages/common/lib/api/params/LoginState.ts @@ -1,5 +1,8 @@ -import { Field,ObjectType } from "type-graphql"; +import { PackRule } from "@casl/ability/extra"; +import { JSONObjectResolver } from "graphql-scalars"; +import { Field, ObjectType } from "type-graphql"; +import { AppAbility } from "../../authorization/accessControl.js"; import { AccessLevel, Authorization, @@ -24,4 +27,7 @@ export class LoginState implements Authorization { @Field(() => [EffectiveCommitteeRole]) effectiveCommitteeRoles!: EffectiveCommitteeRole[]; + + @Field(() => [JSONObjectResolver]) + abilityRules!: PackRule[]; } diff --git a/packages/common/lib/api/params/fundraisingAccess.ts b/packages/common/lib/api/params/fundraisingAccess.ts index dc79b60d..a4dd59d7 100644 --- a/packages/common/lib/api/params/fundraisingAccess.ts +++ b/packages/common/lib/api/params/fundraisingAccess.ts @@ -1,11 +1,4 @@ -import { Field,InputType } from "type-graphql"; - -import { AccessControlParam } from "../../authorization/accessControl.js"; -import { - CommitteeIdentifier, - CommitteeRole, -} from "../../authorization/structures.js"; -import { FundraisingAssignmentNode } from "../resources/Fundraising.js"; +import { Field, InputType } from "type-graphql"; @InputType() export class AssignEntryToPersonInput { @@ -18,12 +11,3 @@ export class UpdateFundraisingAssignmentInput { @Field() amount!: number; } -export const fundraisingAccess: AccessControlParam = - { - authRules: [ - { - minCommitteeRole: CommitteeRole.Coordinator, - committeeIdentifiers: [CommitteeIdentifier.fundraisingCommittee, CommitteeIdentifier.dancerRelationsCommittee], - }, - ], - }; diff --git a/packages/common/lib/api/resources/Notification.ts b/packages/common/lib/api/resources/Notification.ts index 62d6d650..1118c09e 100644 --- a/packages/common/lib/api/resources/Notification.ts +++ b/packages/common/lib/api/resources/Notification.ts @@ -2,8 +2,7 @@ import { DateTimeISOResolver, URLResolver } from "graphql-scalars"; import type { DateTime } from "luxon"; import { Field, ObjectType } from "type-graphql"; -import { AccessControlAuthorized } from "../../authorization/accessControl.js"; -import { AccessLevel } from "../../authorization/structures.js"; +import { AccessControlAuthorized } from "../../authorization/AccessControlParam.js"; import { dateTimeFromSomething } from "../../utility/time/intervalTools.js"; import { createNodeClasses, Node } from "../relay.js"; import type { GlobalId } from "../scalars/GlobalId.js"; @@ -27,15 +26,15 @@ export class NotificationNode extends TimestampedResource implements Node { url?: URL | undefined | null; @Field(() => String, { nullable: true }) - @AccessControlAuthorized({ - accessLevel: AccessLevel.CommitteeChairOrCoordinator, - }) + @AccessControlAuthorized("get", "NotificationNode", "deliveryIssue") deliveryIssue?: string | undefined | null; @Field(() => DateTimeISOResolver, { nullable: true }) - @AccessControlAuthorized({ - accessLevel: AccessLevel.CommitteeChairOrCoordinator, - }) + @AccessControlAuthorized( + "get", + "NotificationNode", + "deliveryIssueAcknowledgedAt" + ) deliveryIssueAcknowledgedAt?: Date | undefined | null; get deliveryIssueAcknowledgedAtDateTime(): DateTime | null { return dateTimeFromSomething(this.deliveryIssueAcknowledgedAt ?? null); @@ -105,9 +104,11 @@ export class NotificationDeliveryNode description: "The time the server received a delivery receipt from the user.", }) - @AccessControlAuthorized({ - accessLevel: AccessLevel.CommitteeChairOrCoordinator, - }) + @AccessControlAuthorized( + "get", + "NotificationDeliveryNode", + "receiptCheckedAt" + ) receiptCheckedAt?: Date | undefined | null; @Field(() => String, { @@ -115,9 +116,7 @@ export class NotificationDeliveryNode description: "A unique identifier corresponding the group of notifications this was sent to Expo with.", }) - @AccessControlAuthorized({ - accessLevel: AccessLevel.CommitteeChairOrCoordinator, - }) + @AccessControlAuthorized("get", "NotificationDeliveryNode", "chunkUuid") chunkUuid?: string | undefined | null; @Field(() => String, { @@ -125,9 +124,7 @@ export class NotificationDeliveryNode description: "Any error message returned by Expo when sending the notification.", }) - @AccessControlAuthorized({ - accessLevel: AccessLevel.CommitteeChairOrCoordinator, - }) + @AccessControlAuthorized("get", "NotificationDeliveryNode", "deliveryError") deliveryError?: string | undefined | null; public getUniqueId(): string { diff --git a/packages/common/lib/api/resources/authorization.test.ts b/packages/common/lib/api/resources/authorization.test.ts deleted file mode 100644 index 52a0b972..00000000 --- a/packages/common/lib/api/resources/authorization.test.ts +++ /dev/null @@ -1,271 +0,0 @@ -import { describe, expect, it } from "vitest"; - -import { checkAuthorization } from "../../authorization/accessControl.js"; -import type { Authorization } from "../../index.js"; -import { - AccessLevel, - CommitteeIdentifier, - CommitteeRole, - DbRole, -} from "../../index.js"; - -const techChair: Authorization = { - accessLevel: AccessLevel.Admin, - dbRole: DbRole.Committee, - effectiveCommitteeRoles: [ - { - identifier: CommitteeIdentifier.techCommittee, - role: CommitteeRole.Chair, - }, - ], -}; - -const techCoordinator: Authorization = { - accessLevel: AccessLevel.Admin, - dbRole: DbRole.Committee, - effectiveCommitteeRoles: [ - { - identifier: CommitteeIdentifier.techCommittee, - role: CommitteeRole.Coordinator, - }, - ], -}; - -const techMember: Authorization = { - accessLevel: AccessLevel.Admin, - dbRole: DbRole.Committee, - effectiveCommitteeRoles: [ - { - identifier: CommitteeIdentifier.techCommittee, - role: CommitteeRole.Member, - }, - ], -}; - -const overallChair: Authorization = { - accessLevel: AccessLevel.CommitteeChairOrCoordinator, - dbRole: DbRole.Committee, - effectiveCommitteeRoles: [ - { - identifier: CommitteeIdentifier.techCommittee, - role: CommitteeRole.Chair, - }, - { - identifier: CommitteeIdentifier.viceCommittee, - role: CommitteeRole.Chair, - }, - ], -}; - -const dancerRelationsChair: Authorization = { - accessLevel: AccessLevel.CommitteeChairOrCoordinator, - dbRole: DbRole.Committee, - effectiveCommitteeRoles: [ - { - identifier: CommitteeIdentifier.dancerRelationsCommittee, - role: CommitteeRole.Chair, - }, - ], -}; - -const dancerRelationsCoordinator: Authorization = { - accessLevel: AccessLevel.CommitteeChairOrCoordinator, - dbRole: DbRole.Committee, - effectiveCommitteeRoles: [ - { - identifier: CommitteeIdentifier.dancerRelationsCommittee, - role: CommitteeRole.Coordinator, - }, - ], -}; - -const dancerRelationsMember: Authorization = { - accessLevel: AccessLevel.Committee, - dbRole: DbRole.Committee, - effectiveCommitteeRoles: [ - { - identifier: CommitteeIdentifier.dancerRelationsCommittee, - role: CommitteeRole.Member, - }, - ], -}; - -const member: Authorization = { - accessLevel: AccessLevel.UKY, - dbRole: DbRole.UKY, - effectiveCommitteeRoles: [], -}; - -const publicAuth: Authorization = { - accessLevel: AccessLevel.Public, - dbRole: DbRole.Public, - effectiveCommitteeRoles: [], -}; - -const none: Authorization = { - accessLevel: AccessLevel.None, - dbRole: DbRole.None, - effectiveCommitteeRoles: [], -}; -describe("checkAuthorization", () => { - it("should return true when the user's access level matches the required access level", () => { - expect.hasAssertions(); - - expect( - checkAuthorization({ accessLevel: AccessLevel.Admin }, techChair) - ).toBe(true); - expect( - checkAuthorization({ accessLevel: AccessLevel.Admin }, techCoordinator) - ).toBe(true); - expect( - checkAuthorization({ accessLevel: AccessLevel.Admin }, techMember) - ).toBe(true); - expect( - checkAuthorization( - { accessLevel: AccessLevel.CommitteeChairOrCoordinator }, - overallChair - ) - ).toBe(true); - expect( - checkAuthorization( - { accessLevel: AccessLevel.CommitteeChairOrCoordinator }, - dancerRelationsChair - ) - ).toBe(true); - expect( - checkAuthorization( - { accessLevel: AccessLevel.CommitteeChairOrCoordinator }, - dancerRelationsCoordinator - ) - ).toBe(true); - expect( - checkAuthorization( - { accessLevel: AccessLevel.Committee }, - dancerRelationsMember - ) - ).toBe(true); - expect(checkAuthorization({ accessLevel: AccessLevel.UKY }, member)).toBe( - true - ); - expect( - checkAuthorization({ accessLevel: AccessLevel.Public }, publicAuth) - ).toBe(true); - expect(checkAuthorization({ accessLevel: AccessLevel.None }, none)).toBe( - true - ); - }); - - // TODO: Make the rest of these async - - it("should return true when the user's access level is higher than the required access level", () => { - expect.assertions(3); - expect( - checkAuthorization({ accessLevel: AccessLevel.Committee }, techChair) - ).toBe(true); - expect( - checkAuthorization( - { accessLevel: AccessLevel.Committee }, - techCoordinator - ) - ).toBe(true); - expect( - checkAuthorization({ accessLevel: AccessLevel.Committee }, techMember) - ).toBe(true); - }); - - it("should return false when the user's access level is lower than the required access level", () => { - expect.assertions(4); - expect( - checkAuthorization( - { accessLevel: AccessLevel.CommitteeChairOrCoordinator }, - dancerRelationsMember - ) - ).toBe(false); - expect( - checkAuthorization({ accessLevel: AccessLevel.UKY }, publicAuth) - ).toBe(false); - expect(checkAuthorization({ accessLevel: AccessLevel.Public }, none)).toBe( - false - ); - expect( - checkAuthorization( - { accessLevel: AccessLevel.CommitteeChairOrCoordinator }, - none - ) - ).toBe(false); - }); - - it("should work with committeeIdentifier matching", () => { - expect.assertions(2); - expect( - checkAuthorization( - { - committeeIdentifier: CommitteeIdentifier.techCommittee, - }, - techChair - ) - ).toBe(true); - expect( - checkAuthorization( - { - committeeIdentifier: CommitteeIdentifier.techCommittee, - }, - none - ) - ).toBe(false); - }); - - it("should work with committeeIdentifiers matching", () => { - expect.assertions(2); - expect( - checkAuthorization( - { - committeeIdentifiers: [ - CommitteeIdentifier.techCommittee, - CommitteeIdentifier.viceCommittee, - ], - }, - techChair - ) - ).toBe(true); - expect( - checkAuthorization( - { - committeeIdentifiers: [ - CommitteeIdentifier.techCommittee, - CommitteeIdentifier.viceCommittee, - ], - }, - none - ) - ).toBe(false); - }); - - it("should work with minimum committeeRole matching", () => { - expect.assertions(3); - expect( - checkAuthorization( - { - minCommitteeRole: CommitteeRole.Chair, - }, - techChair - ) - ).toBe(true); - expect( - checkAuthorization( - { - minCommitteeRole: CommitteeRole.Coordinator, - }, - techChair - ) - ).toBe(true); - expect( - checkAuthorization( - { - minCommitteeRole: CommitteeRole.Coordinator, - }, - none - ) - ).toBe(false); - }); -}); diff --git a/packages/common/lib/authorization/AccessControlParam.ts b/packages/common/lib/authorization/AccessControlParam.ts new file mode 100644 index 00000000..82056718 --- /dev/null +++ b/packages/common/lib/authorization/AccessControlParam.ts @@ -0,0 +1,14 @@ +import { Authorized } from "type-graphql"; + +import type { AppAbility } from "./accessControl.js"; + +export type AccessControlParam = + AllowShortForm extends true + ? Parameters | [Parameters[0]] + : Parameters; + +export function AccessControlAuthorized( + ...check: AccessControlParam +): PropertyDecorator & MethodDecorator & ClassDecorator { + return Authorized(check); +} diff --git a/packages/common/lib/authorization/accessControl.ts b/packages/common/lib/authorization/accessControl.ts index ee5ef1eb..242d176d 100644 --- a/packages/common/lib/authorization/accessControl.ts +++ b/packages/common/lib/authorization/accessControl.ts @@ -1,302 +1,293 @@ -import type { Result } from "ts-results-es"; -import { Err, Ok } from "ts-results-es"; -import type { ArgsDictionary } from "type-graphql"; -import { Authorized } from "type-graphql"; -import type { Primitive } from "utility-types"; +import type { + ExtractSubjectType, + InferSubjects, + MongoAbility, +} from "@casl/ability"; +import { + AbilityBuilder, + createAliasResolver, + createMongoAbility, +} from "@casl/ability"; +import validator from "validator"; -import { parseGlobalId } from "../api/scalars/GlobalId.js"; -import type { InvalidArgumentError } from "../error/direct.js"; -import { InvariantError } from "../error/direct.js"; import { - type Authorization, - type CommitteeIdentifier, - type CommitteeRole, - type GlobalId, - type MembershipPositionType, + parseGlobalId, type PersonNode, - type TeamType, - type UserData, -} from "../index.js"; -import type { AccessLevel } from "./structures.js"; + type ResourceClasses, +} from "../api/resources/index.js"; +import { MembershipPositionType } from "../api/resources/Membership.js"; +import type { TeamType } from "../api/resources/Team.js"; +import type { Authorization } from "./structures.js"; import { - committeeNames, - compareCommitteeRole, - stringifyAccessLevel, + AccessLevel, + CommitteeIdentifier, + CommitteeRole, } from "./structures.js"; -export interface AuthorizationRule { - /** - * The minimum access level required to access this resource - */ - accessLevel?: AccessLevel; - // /** - // * Exact committee role, cannot be used with minCommitteeRole - // */ - // committeeRole?: CommitteeRole; - /** - * Minimum committee role, cannot be used with committeeRole - */ - minCommitteeRole?: CommitteeRole; - /** - * The committee's identifier, currently these are not normalized in an enum or the database - * so just go off what's used elsewhere in the codebase - * - * Cannot be used with committeeIdentifiers - */ - committeeIdentifier?: CommitteeIdentifier; - /** - * Same as committeeIdentifier, but allows any of the listed identifiers - * - * Cannot be used with committeeIdentifier - */ - committeeIdentifiers?: readonly CommitteeIdentifier[]; -} - -export function prettyPrintAuthorizationRule(rule: AuthorizationRule): string { - const parts: string[] = []; - if (rule.accessLevel != null) { - parts.push( - `have an access level of at least ${stringifyAccessLevel(rule.accessLevel)}` - ); - } - if (rule.minCommitteeRole != null) { - parts.push(`have a committee role of at least ${rule.minCommitteeRole}`); - } - if (rule.committeeIdentifier != null) { - parts.push(`be a member of ${committeeNames[rule.committeeIdentifier]}`); - } - if (rule.committeeIdentifiers != null) { - parts.push( - `be a member of one of the following committees: ${rule.committeeIdentifiers - .map((id) => committeeNames[id]) - .join(", ")}` - ); - } - return parts.join(" and "); -} - -export interface AccessControlContext { - authenticatedUser: PersonNode | null; - teamMemberships: SimpleTeamMembership[]; - userData: UserData; - authorization: Authorization; -} - -/** - * An AccessControlParam accepts a user if: - * - * 1. The user's access level is greater than or equal to the access level specified (AccessLevel.None by default) - * 2. The user's role matches one of the specified authorization rules - * 3. The resolver arguments match ALL of the specified argument matchers - * 4. The root object matches ALL of the specified root matchers - * 5. The custom authorization rule returns true - */ -export interface AccessControlParam { - authRules?: - | readonly AuthorizationRule[] - | ((root: RootType) => readonly AuthorizationRule[]); - accessLevel?: AccessLevel; - argumentMatch?: { - argument: string | ((args: ArgsDictionary) => Primitive | Primitive[]); - extractor: (param: AccessControlContext) => Primitive | Primitive[]; - }[]; - rootMatch?: { - root: string | ((root: RootType) => Primitive | Primitive[]); - extractor: (param: AccessControlContext) => Primitive | Primitive[]; - }[]; -} - export interface SimpleTeamMembership { teamType: TeamType; teamId: string; position: MembershipPositionType; } -export interface AuthorizationContext { +type HasWithinTeamIds = "FundraisingAssignmentNode" | "PersonNode"; + +type HasOwnedByUserIds = "FundraisingEntryNode" | "FundraisingAssignmentNode"; + +type ResourceSubject = { + [resource in keyof typeof ResourceClasses]: { + kind: resource; + id?: string; + childOfType?: Subject; + childOfId?: string; + } & (resource extends HasWithinTeamIds + ? { withinTeamIds: string[] } + : { withinTeamIds?: never }) & + (resource extends HasOwnedByUserIds + ? { ownedByUserIds: string[] } + : { ownedByUserIds?: never }); +} & { + AuditNode: { + kind: "AuditNode"; + }; +}; + +type SubjectValue = ResourceSubject[keyof ResourceSubject]; +type Subject = InferSubjects; + +export const Action = { + Create: "create", + Get: "get", + List: "list", + Read: "read", + ReadActive: "readActive", + Update: "update", + Delete: "delete", + Deploy: "deploy", + Modify: "modify", + Manage: "manage", +} as const; +export type Action = (typeof Action)[keyof typeof Action]; + +const resolveAction = createAliasResolver({ + [Action.Modify]: [Action.Read, Action.Update, Action.Delete], + [Action.Read]: [Action.Get, Action.List, Action.ReadActive], +}); + +export type AppAbility = MongoAbility< + | [ + ( + | typeof Action.Create + | typeof Action.Get + | typeof Action.List + | typeof Action.Read + | typeof Action.Update + | typeof Action.Delete + | typeof Action.Modify + | typeof Action.Manage + ), + Subject | "all", + ] + | [ + typeof Action.ReadActive, + "FeedNode" | "ConfigurationNode" | "MarathonNode" | "MarathonHourNode", + ] + | [typeof Action.Deploy, "NotificationNode" | "all"] +>; + +export interface AuthorizationContext extends Authorization { authenticatedUser: PersonNode | null; teamMemberships: SimpleTeamMembership[]; - userData: UserData; - authorization: Authorization; } -export function checkAuthorization( - { - accessLevel, - committeeIdentifier, - committeeIdentifiers, - // committeeRole, - minCommitteeRole, - }: AuthorizationRule, - authorization: Authorization -) { - if (committeeIdentifier != null && committeeIdentifiers != null) { - throw new TypeError( - `Cannot specify both committeeIdentifier and committeeIdentifiers.` - ); - } - - let matches = true; +export function getAuthorizationFor({ + accessLevel, + userId, + teamMemberships, + effectiveCommitteeRoles, +}: Pick< + AuthorizationContext, + "accessLevel" | "teamMemberships" | "effectiveCommitteeRoles" +> & { + userId: string | null; +}): AppAbility { + const { + can: allow, + cannot: forbid, + build, + } = new AbilityBuilder(createMongoAbility); - // Access Level - if (accessLevel != null) { - matches &&= authorization.accessLevel >= accessLevel; + function doBuild() { + return build({ + resolveAction, + detectSubjectType(subject) { + return ((subject as { __typename?: Subject }).__typename ?? + subject.kind) as ExtractSubjectType; + }, + }); } - if (minCommitteeRole != null) { - if (authorization.effectiveCommitteeRoles.length === 0) { - matches = false; - } else { - matches &&= authorization.effectiveCommitteeRoles.some( - (committee) => - compareCommitteeRole(committee.role, minCommitteeRole) >= 0 - ); - } + if (accessLevel === AccessLevel.None) { + return doBuild(); } - - // Committee identifier(s) - if (committeeIdentifier != null) { - matches &&= authorization.effectiveCommitteeRoles.some( - (committee) => committee.identifier === committeeIdentifier - ); - } - if (committeeIdentifiers != null) { - matches &&= authorization.effectiveCommitteeRoles.some((committee) => - committeeIdentifiers.includes(committee.identifier) - ); + if (accessLevel === AccessLevel.SuperAdmin) { + allow(Action.Manage, "all"); + return doBuild(); } - return matches; -} + allow(Action.Get, "DeviceNode"); + allow(Action.ReadActive, [ + "FeedNode", + "ConfigurationNode", + "MarathonNode", + "MarathonHourNode", + ]); + allow(Action.Read, ["CommitteeNode", "EventNode", "ImageNode", "TeamNode"]); -export function checkParam( - rule: AccessControlParam, - authorization: Authorization, - root: RootType, - args: ArgsDictionary, - context: AccessControlContext -): Result { - if (rule.accessLevel != null) { - if (rule.accessLevel > authorization.accessLevel) { - return Ok(false); - } + forbid(Action.Read, "TeamNode", [ + "fundraisingTotal", + "solicitationCode", + "fundraisingEntries", + ]); + forbid(Action.Read, "NotificationNode", [ + "deliveryIssue", + "deliveryIssueAcknowledgedAt", + ]); + forbid(Action.Read, "NotificationDeliveryNode", [ + "receiptCheckedAt", + "chunkUuid", + "deliveryError", + ]); + + if (accessLevel >= AccessLevel.Committee) { + allow(Action.Read, ["SolicitationCodeNode", "MembershipNode"]); } - if (rule.authRules != null) { - const authRules = - typeof rule.authRules === "function" - ? rule.authRules(root) - : rule.authRules; - if (authRules.length === 0) { - return Err( - new InvariantError("Resource has no allowed authorization rules.") - ); - } - let matches = false; - for (const authRule of authRules) { - matches = checkAuthorization(authRule, authorization); - if (matches) { - break; - } - } - if (!matches) { - return Ok(false); - } + if (accessLevel >= AccessLevel.CommitteeChairOrCoordinator) { + allow(Action.Manage, [ + "CommitteeNode", + "EventNode", + "FeedNode", + "ImageNode", + "MembershipNode", + "PersonNode", + ]); + allow(Action.Read, "NotificationDeliveryNode"); + allow([Action.Modify, Action.Create], "NotificationNode"); + allow(Action.Read, "NotificationNode", [ + "deliveryIssue", + "deliveryIssueAcknowledgedAt", + ]); + allow(Action.Read, "NotificationDeliveryNode", [ + "receiptCheckedAt", + "chunkUuid", + "deliveryError", + ]); + } + + if (accessLevel >= AccessLevel.Admin) { + allow(Action.Manage, [ + "ConfigurationNode", + "MarathonNode", + "MarathonHourNode", + "FundraisingAssignmentNode", + "FundraisingEntryNode", + ]); + allow(Action.Deploy, "NotificationNode"); } - if (rule.argumentMatch != null) { - for (const match of rule.argumentMatch) { - let argValue: Primitive | Primitive[]; - if (match.argument === "id") { - // I think this code might be wrong, but I'm not 100% sure either way and don't have time to investigate - const parseReult = parseGlobalId(args.id as string).map( - ({ id: id }) => args[id] as Primitive | Primitive[] - ); - if (parseReult.isErr()) { - return parseReult; + for (const { identifier, role } of effectiveCommitteeRoles) { + switch (identifier) { + case CommitteeIdentifier.viceCommittee: { + allow(role === CommitteeRole.Member ? Action.Modify : Action.Manage, [ + "PersonNode", + ]); + allow(Action.Read, "TeamNode", "fundraisingTotal"); + // fallthrough + } + case CommitteeIdentifier.communityDevelopmentCommittee: + case CommitteeIdentifier.techCommittee: + case CommitteeIdentifier.marketingCommittee: { + if (role !== CommitteeRole.Member) { + allow(Action.Deploy, "NotificationNode"); } - argValue = parseReult.value; - } else if (typeof match.argument === "string") { - argValue = args[match.argument] as Primitive | Primitive[]; - } else { - argValue = match.argument(args); + break; } - if (argValue == null) { - return Err( - new InvariantError( - "FieldMatchAuthorized argument is null or undefined." - ) - ); + case CommitteeIdentifier.dancerRelationsCommittee: { + allow(Action.Manage, ["PointOpportunityNode", "PointEntryNode"]); + break; } - const expectedValue = match.extractor(context); - - if (Array.isArray(expectedValue)) { - if (Array.isArray(argValue)) { - if (argValue.some((v) => expectedValue.includes(v))) { - return Ok(false); - } - } else if (expectedValue.includes(argValue)) { - return Ok(false); - } - } else if (argValue !== expectedValue) { - if (Array.isArray(argValue)) { - if (argValue.includes(expectedValue)) { - return Ok(false); - } - } + case CommitteeIdentifier.fundraisingCommittee: { + allow(Action.Manage, [ + "FundraisingEntryNode", + "DailyDepartmentNotificationNode", + "SolicitationCodeNode", + "FundraisingAssignmentNode", + ]); + break; } } } - if (rule.rootMatch != null) { - let shouldContinue = false; - for (const match of rule.rootMatch) { - let rootValue: Primitive | Primitive[]; - if (match.root === "id") { - rootValue = (root as { id: GlobalId }).id.id; - } else if (typeof match.root === "string") { - rootValue = (root as Record)[ - match.root - ]; - } else { - rootValue = match.root(root); - } - if (rootValue == null) { - return Err( - new InvariantError("FieldMatchAuthorized root is null or undefined.") - ); - } - const expectedValue = match.extractor(context); + const parsedUserId = userId + ? validator.isUUID(userId) + ? userId + : parseGlobalId(userId).unwrap().id + : null; - if (Array.isArray(expectedValue)) { - if (Array.isArray(rootValue)) { - if (!rootValue.some((v) => expectedValue.includes(v))) { - shouldContinue = true; - break; - } - } else if (!expectedValue.includes(rootValue)) { - shouldContinue = true; - break; - } - } else if (Array.isArray(rootValue)) { - if (!rootValue.includes(expectedValue)) { - shouldContinue = true; - break; - } - } else if (rootValue !== expectedValue) { - shouldContinue = true; - break; - } - } - if (shouldContinue) { - return Ok(false); - } + if (parsedUserId) { + allow(Action.Read, "PersonNode", { + id: { + $eq: parsedUserId, + }, + }); + allow(Action.Read, "FundraisingAssignmentNode", { + ownedByUserIds: [parsedUserId], + }); } - return Ok(true); -} + const authTeamMemberships = teamMemberships.map( + (membership) => membership.teamId + ); + if (authTeamMemberships.length > 0) { + allow(Action.Read, "TeamNode", { + id: { $in: authTeamMemberships }, + }); + allow(Action.Read, "MembershipNode", { + childOfType: "TeamNode", + childOfId: { $in: authTeamMemberships }, + }); + allow(Action.List, "PersonNode", { + childOfType: "TeamNode", + childOfId: { $in: authTeamMemberships }, + }); + allow(Action.Read, "TeamNode", ["fundraisingTotal", "solicitationCode"], { + id: { $in: authTeamMemberships }, + }); + } + + allow(Action.Read, "TeamNode", ["child"]); + + const authTeamCaptaincies = teamMemberships + .filter( + (membership) => membership.position === MembershipPositionType.Captain + ) + .map((membership) => membership.teamId); + if (authTeamCaptaincies.length > 0) { + allow(Action.Read, "PersonNode", { + withinTeamIds: { $in: authTeamCaptaincies }, + }); + allow([Action.Modify, Action.Create], "FundraisingAssignmentNode", { + withinTeamIds: { $in: authTeamCaptaincies }, + }); + allow(Action.Read, "FundraisingEntryNode", { + childOfType: "TeamNode", + childOfId: { $in: authTeamCaptaincies }, + }); + allow(Action.Read, "TeamNode", "fundraisingEntries", { + id: { $in: authTeamMemberships }, + }); + } -export function AccessControlAuthorized( - ...roles: readonly AccessControlParam[] -): PropertyDecorator & MethodDecorator & ClassDecorator { - return Authorized(...roles); + return doBuild(); } diff --git a/packages/common/lib/authorization/customAccessControl.ts b/packages/common/lib/authorization/customAccessControl.ts deleted file mode 100644 index 5d64c064..00000000 --- a/packages/common/lib/authorization/customAccessControl.ts +++ /dev/null @@ -1,147 +0,0 @@ -import { Err, None, Option, Result, Some } from "ts-results-es"; -import type { MiddlewareFn } from "type-graphql"; -import { UseMiddleware } from "type-graphql"; - -import { AccessControlError } from "../error/control.js"; -import type { ConcreteResult } from "../error/result.js"; -import type { - AccessControlContext, - AuthorizationContext, -} from "./accessControl.js"; -import { AccessLevel } from "./structures.js"; - -/** - * Custom authorization rule - * - * Should usually be avoided, but can be used for more complex authorization - * rules - * - * - If the custom rule returns a boolean the user is allowed access if the rule - * returns true and an error is thrown if the rule returns false. - * - If the custom rule returns null the field is set to null (make sure the - * field is nullable in the schema) - * - If one param returns false and another returns null, an error will be - * thrown and the null ignored. - */ -export type CustomQueryAuthorizationFunction = ( - root: RootType, - context: AccessControlContext, - result: Option, - args: Record -) => boolean | null | Promise; - -/** - * Custom mutation authorization function - * - * Same as CustomAuthorizationFunction, but without root or result - * - * Should usually be avoided, but can be used for more complex authorization - * rules - * - * - If the custom rule returns a boolean the user is allowed access if the rule - * returns true and an error is thrown if the rule returns false. - * - If the custom rule returns null the field is set to null (make sure the - * field is nullable in the schema) - * - If one param returns false and another returns null, an error will be - * thrown and the null ignored. - */ -export type CustomMutationAuthorizationFunction = ( - context: AccessControlContext, - args: Record -) => boolean | null | Promise; - -/** - * Adds an access control check to a query resolver as a middleware. - * - * Note that this middleware will run the protected resolver before the access - * control check, so the resolver should be written to be safe to run even if - * the user doesn't have access. The advantage is that you can inspect the - * result of the resolver to make access control decisions. - * - * @param func The custom authorization function - * - * @see CustomQueryAuthorizationFunction - */ -export function CustomQueryAccessControl< - RootType extends object = never, - ResultType extends object = never, ->( - func: CustomQueryAuthorizationFunction -): MethodDecorator { - const middleware: MiddlewareFn = async ( - resolverData, - next - ) => { - const { context, args, info } = resolverData; - const root = resolverData.root as RootType; - const { authorization } = context; - - if (authorization.accessLevel === AccessLevel.SuperAdmin) { - // Super admins have access to everything - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return next(); - } - - let result = (await next()) as - | ResultType - | Option - | ConcreteResult - | ConcreteResult>; - if (Result.isResult(result)) { - result = result.unwrapOr(None); - } - if (!Option.isOption(result)) { - result = Some(result); - } - - const customResult = await func(root, context, result, args); - - if (customResult === false) { - return Err(new AccessControlError(info)); - } else if (customResult === null) { - return null; - } - - return result; - }; - - return UseMiddleware(middleware); -} - -/** - * Adds an access control check to a mutation resolver as a middleware. - * - * This middleware will wait to run the protected resolver until after the access - * control check. This means the resolver will not run if the user does not have - * access. - */ -export function CustomMutationAccessControl( - func: CustomMutationAuthorizationFunction -): MethodDecorator { - const middleware: MiddlewareFn = async ( - resolverData, - next - ) => { - const { context, args, info } = resolverData; - const { authorization } = context; - - if (authorization.accessLevel === AccessLevel.SuperAdmin) { - // Super admins have access to everything - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return next(); - } - - const customResult = await func(context, args); - - if (customResult === false) { - return Err(new AccessControlError(info)); - } else if (customResult === null) { - return null; - } - - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return next(); - }; - - return UseMiddleware(middleware); -} diff --git a/packages/common/lib/authorization/role.test.ts b/packages/common/lib/authorization/role.test.ts deleted file mode 100644 index ce071cba..00000000 --- a/packages/common/lib/authorization/role.test.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { describe, it } from "vitest"; - -import { - AccessLevel, - CommitteeIdentifier, - CommitteeRole, - DbRole, -} from "../index.js"; -import { roleToAccessLevel } from "./role.js"; - -// TODO test the committee hierarchy system (i.e. overall and vice roles vs other committees) - -describe("roleToAccessLevel", { todo: true }, () => { - it("returns the correct access level for a given role normally", ({ - expect, - }) => { - const chairRole = { - dbRole: DbRole.Committee, - committeeRole: CommitteeRole.Chair, - committeeIdentifier: CommitteeIdentifier.dancerRelationsCommittee, - }; - expect(roleToAccessLevel(chairRole)).toBe( - AccessLevel.CommitteeChairOrCoordinator - ); - - const coordinatorRole = { - dbRole: DbRole.Committee, - committeeRole: CommitteeRole.Coordinator, - committeeIdentifier: CommitteeIdentifier.dancerRelationsCommittee, - }; - expect(roleToAccessLevel(coordinatorRole)).toBe( - AccessLevel.CommitteeChairOrCoordinator - ); - - const memberRole = { - dbRole: DbRole.Committee, - committeeRole: CommitteeRole.Member, - committeeIdentifier: CommitteeIdentifier.dancerRelationsCommittee, - }; - expect(roleToAccessLevel(memberRole)).toBe(AccessLevel.Committee); - - const teamMemberRole = { - dbRole: DbRole.UKY, - }; - expect(roleToAccessLevel(teamMemberRole)).toBe(AccessLevel.UKY); - - const publicRole = { - dbRole: DbRole.Public, - }; - expect(roleToAccessLevel(publicRole)).toBe(AccessLevel.Public); - - const noneRole = { - dbRole: DbRole.None, - }; - expect(roleToAccessLevel(noneRole)).toBe(AccessLevel.None); - }); - - it("grants any member of the tech committee admin access", ({ expect }) => { - const chairRole = { - dbRole: DbRole.Committee, - committeeRole: CommitteeRole.Chair, - committeeIdentifier: CommitteeIdentifier.techCommittee, - }; - expect(roleToAccessLevel(chairRole)).toBe(AccessLevel.Admin); - - const coordinatorRole = { - dbRole: DbRole.Committee, - committeeRole: CommitteeRole.Coordinator, - committeeIdentifier: CommitteeIdentifier.techCommittee, - }; - expect(roleToAccessLevel(coordinatorRole)).toBe(AccessLevel.Admin); - - const memberRole = { - dbRole: DbRole.Committee, - committeeRole: CommitteeRole.Member, - committeeIdentifier: CommitteeIdentifier.techCommittee, - }; - expect(roleToAccessLevel(memberRole)).toBe(AccessLevel.Admin); - }); - - it("throws an error for an illegal role", ({ expect }) => { - const illegalRole = { - dbRole: "illegal" as DbRole, - }; - expect(() => roleToAccessLevel(illegalRole)).toThrow( - "Illegal DbRole: [Parsing of 'illegal' failed]" - ); - }); -}); diff --git a/packages/common/lib/authorization/role.ts b/packages/common/lib/authorization/role.ts index 6b74e0bf..f9f55d98 100644 --- a/packages/common/lib/authorization/role.ts +++ b/packages/common/lib/authorization/role.ts @@ -1,8 +1,9 @@ +import type { EffectiveCommitteeRole } from "../api/types/EffectiveCommitteeRole.js"; import { AccessLevel, + AuthSource, CommitteeIdentifier, CommitteeRole, - DbRole, } from "./structures.js"; /** @@ -12,63 +13,46 @@ import { * @return The equivalent AccessLevel * @throws Error if the DbRole is not a valid member of the DbRole enum */ -export function roleToAccessLevel({ - dbRole, - effectiveCommitteeRoles, -}: { - dbRole: DbRole; - effectiveCommitteeRoles?: { - identifier: CommitteeIdentifier; - role: CommitteeRole; - }[]; -}): AccessLevel { - switch (dbRole) { - case DbRole.None: { - return AccessLevel.None; - } - case DbRole.Public: { - return AccessLevel.Public; - } - case DbRole.UKY: { - return AccessLevel.UKY; - } - case DbRole.Committee: { - let maxLevel: AccessLevel | null = null; - for (const committee of effectiveCommitteeRoles ?? []) { - let thisLevel: AccessLevel; +export function roleToAccessLevel( + effectiveCommitteeRoles: EffectiveCommitteeRole[], + authSource: AuthSource +): AccessLevel { + if (authSource === AuthSource.None) { + return AccessLevel.None; + } + if (authSource === AuthSource.Anonymous) { + return AccessLevel.Public; + } - if (committee.identifier === CommitteeIdentifier.techCommittee) { - thisLevel = - committee.role === CommitteeRole.Chair - ? AccessLevel.SuperAdmin - : AccessLevel.Admin; - } else if ( - committee.role === CommitteeRole.Coordinator || - committee.role === CommitteeRole.Chair - ) { - thisLevel = AccessLevel.CommitteeChairOrCoordinator; - } else { - thisLevel = AccessLevel.Committee; - } + if (effectiveCommitteeRoles.length > 0) { + let maxLevel: AccessLevel | null = null; + for (const committee of effectiveCommitteeRoles) { + let thisLevel: AccessLevel; - if (maxLevel === null || thisLevel > maxLevel) { - maxLevel = thisLevel; - } + if (committee.identifier === CommitteeIdentifier.techCommittee) { + thisLevel = + committee.role === CommitteeRole.Chair + ? AccessLevel.SuperAdmin + : AccessLevel.Admin; + } else if ( + committee.role === CommitteeRole.Coordinator || + committee.role === CommitteeRole.Chair + ) { + thisLevel = AccessLevel.CommitteeChairOrCoordinator; + } else { + thisLevel = AccessLevel.Committee; } - if (maxLevel === null) { - throw new Error("No committee roles found when DbRole was Committee"); + + if (maxLevel === null || thisLevel > maxLevel) { + maxLevel = thisLevel; } - return maxLevel; } - default: { - dbRole satisfies never; - try { - throw new Error(`Illegal DbRole: ${String(dbRole)}`); - } catch (error) { - throw new Error( - `Illegal DbRole: [Parsing of '${String(dbRole)}' failed]` - ); - } + if (maxLevel === null) { + throw new Error("No committee roles found when DbRole was Committee"); } + return maxLevel; + } else { + authSource satisfies typeof AuthSource.LinkBlue | typeof AuthSource.Demo; + return AccessLevel.UKY; } } diff --git a/packages/common/lib/authorization/structures.ts b/packages/common/lib/authorization/structures.ts index 8f5c8b9e..fb0eb570 100644 --- a/packages/common/lib/authorization/structures.ts +++ b/packages/common/lib/authorization/structures.ts @@ -283,9 +283,10 @@ export const committeeNames: Record = { export interface Authorization { /** @deprecated */ - dbRole: DbRole; + dbRole?: DbRole; effectiveCommitteeRoles: EffectiveCommitteeRole[]; accessLevel: AccessLevel; + authSource: AuthSource; } /** @@ -297,6 +298,7 @@ export const defaultAuthorization = { dbRole: DbRole.None, accessLevel: AccessLevel.None, effectiveCommitteeRoles: [], + authSource: AuthSource.None, } satisfies Authorization; // Registering the enum types with TypeGraphQL diff --git a/packages/common/lib/error/control.ts b/packages/common/lib/error/control.ts index dd8aeef3..4496cea5 100644 --- a/packages/common/lib/error/control.ts +++ b/packages/common/lib/error/control.ts @@ -1,8 +1,6 @@ import type { GraphQLResolveInfo } from "graphql"; import type { Path } from "graphql/jsutils/Path.js"; -import type { AuthorizationRule } from "../authorization/accessControl.js"; -import { prettyPrintAuthorizationRule } from "../authorization/accessControl.js"; import { ConcreteError } from "./error.js"; import * as ErrorCode from "./errorCode.js"; @@ -27,12 +25,9 @@ export abstract class ControlError extends ConcreteError { export class AuthorizationRuleFailedError extends ControlError { readonly message = "Unauthorized"; - constructor(protected readonly requiredAuthorization: AuthorizationRule[]) { - super(); - } - + // eslint-disable-next-line @typescript-eslint/class-literal-property-style get detailedMessage() { - return `Unauthorized: ${this.requiredAuthorization.map(prettyPrintAuthorizationRule).join(", ")}`; + return `You do not have access to this resource`; } get tag(): ErrorCode.AuthorizationRuleFailed { diff --git a/packages/common/lib/index.ts b/packages/common/lib/index.ts index eccfcefe..0b60e111 100644 --- a/packages/common/lib/index.ts +++ b/packages/common/lib/index.ts @@ -24,7 +24,7 @@ export * from "./api/params/index.js"; export * from "./api/resources/index.js"; export * from "./api/standardResolver.js"; export * from "./authorization/accessControl.js"; -export * from "./authorization/customAccessControl.js"; +export * from "./authorization/AccessControlParam.js"; export * from "./authorization/role.js"; export * from "./authorization/structures.js"; export * from "./utility/errors/ApiError.js"; diff --git a/packages/portal/graphql/graphql-env.d.ts b/packages/portal/graphql/graphql-env.d.ts index b3b4290e..1cd2fd90 100644 --- a/packages/portal/graphql/graphql-env.d.ts +++ b/packages/portal/graphql/graphql-env.d.ts @@ -39,7 +39,6 @@ export type introspection_types = { 'DailyDepartmentNotificationResolverOneOfFilterKeys': { name: 'DailyDepartmentNotificationResolverOneOfFilterKeys'; enumValues: 'BatchType' | 'SolicitationCodeNumber' | 'SolicitationCodePrefix'; }; 'DailyDepartmentNotificationResolverStringFilterKeys': { name: 'DailyDepartmentNotificationResolverStringFilterKeys'; enumValues: 'Comment' | 'Donor' | 'SolicitationCodeName'; }; 'DateTimeISO': unknown; - 'DbFundsTeamInfo': { kind: 'OBJECT'; name: 'DbFundsTeamInfo'; fields: { 'dbNum': { name: 'dbNum'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'name': { name: 'name'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; }; }; 'DbRole': { name: 'DbRole'; enumValues: 'Committee' | 'None' | 'Public' | 'UKY'; }; 'DeviceNode': { kind: 'OBJECT'; name: 'DeviceNode'; fields: { 'createdAt': { name: 'createdAt'; type: { kind: 'SCALAR'; name: 'DateTimeISO'; ofType: null; } }; 'id': { name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'GlobalId'; ofType: null; }; } }; 'lastLoggedInUser': { name: 'lastLoggedInUser'; type: { kind: 'OBJECT'; name: 'PersonNode'; ofType: null; } }; 'lastLogin': { name: 'lastLogin'; type: { kind: 'SCALAR'; name: 'DateTimeISO'; ofType: null; } }; 'notificationDeliveries': { name: 'notificationDeliveries'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'NotificationDeliveryNode'; ofType: null; }; }; }; } }; 'updatedAt': { name: 'updatedAt'; type: { kind: 'SCALAR'; name: 'DateTimeISO'; ofType: null; } }; }; }; 'DeviceResolverAllKeys': { name: 'DeviceResolverAllKeys'; enumValues: 'createdAt' | 'expoPushToken' | 'lastSeen' | 'updatedAt'; }; @@ -147,7 +146,7 @@ export type introspection_types = { 'NotificationResolverOneOfFilterKeys': { name: 'NotificationResolverOneOfFilterKeys'; enumValues: 'deliveryIssue'; }; 'NotificationResolverStringFilterKeys': { name: 'NotificationResolverStringFilterKeys'; enumValues: 'body' | 'title'; }; 'NumericComparator': { name: 'NumericComparator'; enumValues: 'EQUALS' | 'GREATER_THAN' | 'GREATER_THAN_OR_EQUAL_TO' | 'IS' | 'LESS_THAN' | 'LESS_THAN_OR_EQUAL_TO'; }; - 'PersonNode': { kind: 'OBJECT'; name: 'PersonNode'; fields: { 'assignedDonationEntries': { name: 'assignedDonationEntries'; type: { kind: 'OBJECT'; name: 'ListFundraisingEntriesResponse'; ofType: null; } }; 'committees': { name: 'committees'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'CommitteeMembershipNode'; ofType: null; }; }; }; } }; 'createdAt': { name: 'createdAt'; type: { kind: 'SCALAR'; name: 'DateTimeISO'; ofType: null; } }; 'dbRole': { name: 'dbRole'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'ENUM'; name: 'DbRole'; ofType: null; }; } }; 'email': { name: 'email'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'fundraisingAssignments': { name: 'fundraisingAssignments'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'FundraisingAssignmentNode'; ofType: null; }; }; }; } }; 'fundraisingTotalAmount': { name: 'fundraisingTotalAmount'; type: { kind: 'SCALAR'; name: 'Float'; ofType: null; } }; 'id': { name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'GlobalId'; ofType: null; }; } }; 'linkblue': { name: 'linkblue'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'moraleTeams': { name: 'moraleTeams'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'MembershipNode'; ofType: null; }; }; }; } }; 'name': { name: 'name'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'primaryCommittee': { name: 'primaryCommittee'; type: { kind: 'OBJECT'; name: 'CommitteeMembershipNode'; ofType: null; } }; 'primaryTeam': { name: 'primaryTeam'; type: { kind: 'OBJECT'; name: 'MembershipNode'; ofType: null; } }; 'teams': { name: 'teams'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'MembershipNode'; ofType: null; }; }; }; } }; 'updatedAt': { name: 'updatedAt'; type: { kind: 'SCALAR'; name: 'DateTimeISO'; ofType: null; } }; }; }; + 'PersonNode': { kind: 'OBJECT'; name: 'PersonNode'; fields: { 'committees': { name: 'committees'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'CommitteeMembershipNode'; ofType: null; }; }; }; } }; 'createdAt': { name: 'createdAt'; type: { kind: 'SCALAR'; name: 'DateTimeISO'; ofType: null; } }; 'dbRole': { name: 'dbRole'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'ENUM'; name: 'DbRole'; ofType: null; }; } }; 'email': { name: 'email'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'fundraisingAssignments': { name: 'fundraisingAssignments'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'FundraisingAssignmentNode'; ofType: null; }; }; }; } }; 'fundraisingTotalAmount': { name: 'fundraisingTotalAmount'; type: { kind: 'SCALAR'; name: 'Float'; ofType: null; } }; 'id': { name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'GlobalId'; ofType: null; }; } }; 'linkblue': { name: 'linkblue'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'moraleTeams': { name: 'moraleTeams'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'MembershipNode'; ofType: null; }; }; }; } }; 'name': { name: 'name'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'primaryCommittee': { name: 'primaryCommittee'; type: { kind: 'OBJECT'; name: 'CommitteeMembershipNode'; ofType: null; } }; 'primaryTeam': { name: 'primaryTeam'; type: { kind: 'OBJECT'; name: 'MembershipNode'; ofType: null; } }; 'teams': { name: 'teams'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'MembershipNode'; ofType: null; }; }; }; } }; 'updatedAt': { name: 'updatedAt'; type: { kind: 'SCALAR'; name: 'DateTimeISO'; ofType: null; } }; }; }; 'PersonResolverAllKeys': { name: 'PersonResolverAllKeys'; enumValues: 'committeeName' | 'committeeRole' | 'dbRole' | 'email' | 'linkblue' | 'name'; }; 'PersonResolverKeyedIsNullFilterItem': { kind: 'INPUT_OBJECT'; name: 'PersonResolverKeyedIsNullFilterItem'; isOneOf: false; inputFields: [{ name: 'field'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'ENUM'; name: 'PersonResolverAllKeys'; ofType: null; }; }; defaultValue: null }, { name: 'negate'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; defaultValue: "false" }]; }; 'PersonResolverKeyedOneOfFilterItem': { kind: 'INPUT_OBJECT'; name: 'PersonResolverKeyedOneOfFilterItem'; isOneOf: false; inputFields: [{ name: 'field'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'ENUM'; name: 'PersonResolverOneOfFilterKeys'; ofType: null; }; }; defaultValue: null }, { name: 'negate'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; defaultValue: "false" }, { name: 'value'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; }; }; defaultValue: null }]; }; @@ -169,7 +168,7 @@ export type introspection_types = { 'PointOpportunityResolverOneOfFilterKeys': { name: 'PointOpportunityResolverOneOfFilterKeys'; enumValues: 'marathonUuid' | 'type'; }; 'PointOpportunityResolverStringFilterKeys': { name: 'PointOpportunityResolverStringFilterKeys'; enumValues: 'name'; }; 'PositiveInt': unknown; - 'Query': { kind: 'OBJECT'; name: 'Query'; fields: { 'activeConfiguration': { name: 'activeConfiguration'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'GetConfigurationByUuidResponse'; ofType: null; }; } }; 'allConfigurations': { name: 'allConfigurations'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'ConfigurationNode'; ofType: null; }; }; }; } }; 'auditLog': { name: 'auditLog'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'configuration': { name: 'configuration'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'ConfigurationNode'; ofType: null; }; } }; 'currentMarathon': { name: 'currentMarathon'; type: { kind: 'OBJECT'; name: 'MarathonNode'; ofType: null; } }; 'currentMarathonHour': { name: 'currentMarathonHour'; type: { kind: 'OBJECT'; name: 'MarathonHourNode'; ofType: null; } }; 'dailyDepartmentNotification': { name: 'dailyDepartmentNotification'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'DailyDepartmentNotificationNode'; ofType: null; }; } }; 'dailyDepartmentNotificationBatch': { name: 'dailyDepartmentNotificationBatch'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'DailyDepartmentNotificationBatchNode'; ofType: null; }; } }; 'dailyDepartmentNotifications': { name: 'dailyDepartmentNotifications'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'ListDailyDepartmentNotificationsResponse'; ofType: null; }; } }; 'dbFundsTeams': { name: 'dbFundsTeams'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'DbFundsTeamInfo'; ofType: null; }; }; }; } }; 'device': { name: 'device'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'GetDeviceByUuidResponse'; ofType: null; }; } }; 'devices': { name: 'devices'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'ListDevicesResponse'; ofType: null; }; } }; 'event': { name: 'event'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'EventNode'; ofType: null; }; } }; 'events': { name: 'events'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'ListEventsResponse'; ofType: null; }; } }; 'feed': { name: 'feed'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'INTERFACE'; name: 'FeedItem'; ofType: null; }; }; }; } }; 'feedItem': { name: 'feedItem'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'FeedNode'; ofType: null; }; } }; 'fundraisingAssignment': { name: 'fundraisingAssignment'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'FundraisingAssignmentNode'; ofType: null; }; } }; 'fundraisingEntries': { name: 'fundraisingEntries'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'ListFundraisingEntriesResponse'; ofType: null; }; } }; 'fundraisingEntry': { name: 'fundraisingEntry'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'FundraisingEntryNode'; ofType: null; }; } }; 'image': { name: 'image'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'ImageNode'; ofType: null; }; } }; 'images': { name: 'images'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'ListImagesResponse'; ofType: null; }; } }; 'latestMarathon': { name: 'latestMarathon'; type: { kind: 'OBJECT'; name: 'MarathonNode'; ofType: null; } }; 'loginState': { name: 'loginState'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'LoginState'; ofType: null; }; } }; 'marathon': { name: 'marathon'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'MarathonNode'; ofType: null; }; } }; 'marathonForYear': { name: 'marathonForYear'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'MarathonNode'; ofType: null; }; } }; 'marathonHour': { name: 'marathonHour'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'MarathonHourNode'; ofType: null; }; } }; 'marathonHours': { name: 'marathonHours'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'ListMarathonHoursResponse'; ofType: null; }; } }; 'marathons': { name: 'marathons'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'ListMarathonsResponse'; ofType: null; }; } }; 'me': { name: 'me'; type: { kind: 'OBJECT'; name: 'PersonNode'; ofType: null; } }; 'node': { name: 'node'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'INTERFACE'; name: 'Node'; ofType: null; }; } }; 'notification': { name: 'notification'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'NotificationNode'; ofType: null; }; } }; 'notificationDeliveries': { name: 'notificationDeliveries'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'ListNotificationDeliveriesResponse'; ofType: null; }; } }; 'notifications': { name: 'notifications'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'ListNotificationsResponse'; ofType: null; }; } }; 'people': { name: 'people'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'ListPeopleResponse'; ofType: null; }; } }; 'person': { name: 'person'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'PersonNode'; ofType: null; }; } }; 'personByLinkBlue': { name: 'personByLinkBlue'; type: { kind: 'OBJECT'; name: 'PersonNode'; ofType: null; } }; 'pointEntries': { name: 'pointEntries'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'ListPointEntriesResponse'; ofType: null; }; } }; 'pointEntry': { name: 'pointEntry'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'PointEntryNode'; ofType: null; }; } }; 'pointOpportunities': { name: 'pointOpportunities'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'ListPointOpportunitiesResponse'; ofType: null; }; } }; 'pointOpportunity': { name: 'pointOpportunity'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'PointOpportunityNode'; ofType: null; }; } }; 'rawFundraisingEntries': { name: 'rawFundraisingEntries'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'rawFundraisingTotals': { name: 'rawFundraisingTotals'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'report': { name: 'report'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'Report'; ofType: null; }; } }; 'searchPeopleByName': { name: 'searchPeopleByName'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'PersonNode'; ofType: null; }; }; }; } }; 'solicitationCode': { name: 'solicitationCode'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'SolicitationCodeNode'; ofType: null; }; } }; 'solicitationCodes': { name: 'solicitationCodes'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'ListSolicitationCodesResponse'; ofType: null; }; } }; 'team': { name: 'team'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'TeamNode'; ofType: null; }; } }; 'teams': { name: 'teams'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'ListTeamsResponse'; ofType: null; }; } }; }; }; + 'Query': { kind: 'OBJECT'; name: 'Query'; fields: { 'activeConfiguration': { name: 'activeConfiguration'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'GetConfigurationByUuidResponse'; ofType: null; }; } }; 'allConfigurations': { name: 'allConfigurations'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'ConfigurationNode'; ofType: null; }; }; }; } }; 'auditLog': { name: 'auditLog'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'configuration': { name: 'configuration'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'ConfigurationNode'; ofType: null; }; } }; 'currentMarathon': { name: 'currentMarathon'; type: { kind: 'OBJECT'; name: 'MarathonNode'; ofType: null; } }; 'currentMarathonHour': { name: 'currentMarathonHour'; type: { kind: 'OBJECT'; name: 'MarathonHourNode'; ofType: null; } }; 'dailyDepartmentNotification': { name: 'dailyDepartmentNotification'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'DailyDepartmentNotificationNode'; ofType: null; }; } }; 'dailyDepartmentNotificationBatch': { name: 'dailyDepartmentNotificationBatch'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'DailyDepartmentNotificationBatchNode'; ofType: null; }; } }; 'dailyDepartmentNotifications': { name: 'dailyDepartmentNotifications'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'ListDailyDepartmentNotificationsResponse'; ofType: null; }; } }; 'device': { name: 'device'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'GetDeviceByUuidResponse'; ofType: null; }; } }; 'devices': { name: 'devices'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'ListDevicesResponse'; ofType: null; }; } }; 'event': { name: 'event'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'EventNode'; ofType: null; }; } }; 'events': { name: 'events'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'ListEventsResponse'; ofType: null; }; } }; 'feed': { name: 'feed'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'INTERFACE'; name: 'FeedItem'; ofType: null; }; }; }; } }; 'feedItem': { name: 'feedItem'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'FeedNode'; ofType: null; }; } }; 'fundraisingAssignment': { name: 'fundraisingAssignment'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'FundraisingAssignmentNode'; ofType: null; }; } }; 'fundraisingEntries': { name: 'fundraisingEntries'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'ListFundraisingEntriesResponse'; ofType: null; }; } }; 'fundraisingEntry': { name: 'fundraisingEntry'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'FundraisingEntryNode'; ofType: null; }; } }; 'image': { name: 'image'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'ImageNode'; ofType: null; }; } }; 'images': { name: 'images'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'ListImagesResponse'; ofType: null; }; } }; 'latestMarathon': { name: 'latestMarathon'; type: { kind: 'OBJECT'; name: 'MarathonNode'; ofType: null; } }; 'loginState': { name: 'loginState'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'LoginState'; ofType: null; }; } }; 'marathon': { name: 'marathon'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'MarathonNode'; ofType: null; }; } }; 'marathonForYear': { name: 'marathonForYear'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'MarathonNode'; ofType: null; }; } }; 'marathonHour': { name: 'marathonHour'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'MarathonHourNode'; ofType: null; }; } }; 'marathonHours': { name: 'marathonHours'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'ListMarathonHoursResponse'; ofType: null; }; } }; 'marathons': { name: 'marathons'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'ListMarathonsResponse'; ofType: null; }; } }; 'me': { name: 'me'; type: { kind: 'OBJECT'; name: 'PersonNode'; ofType: null; } }; 'node': { name: 'node'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'INTERFACE'; name: 'Node'; ofType: null; }; } }; 'notification': { name: 'notification'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'NotificationNode'; ofType: null; }; } }; 'notificationDeliveries': { name: 'notificationDeliveries'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'ListNotificationDeliveriesResponse'; ofType: null; }; } }; 'notifications': { name: 'notifications'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'ListNotificationsResponse'; ofType: null; }; } }; 'people': { name: 'people'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'ListPeopleResponse'; ofType: null; }; } }; 'person': { name: 'person'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'PersonNode'; ofType: null; }; } }; 'personByLinkBlue': { name: 'personByLinkBlue'; type: { kind: 'OBJECT'; name: 'PersonNode'; ofType: null; } }; 'pointEntries': { name: 'pointEntries'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'ListPointEntriesResponse'; ofType: null; }; } }; 'pointEntry': { name: 'pointEntry'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'PointEntryNode'; ofType: null; }; } }; 'pointOpportunities': { name: 'pointOpportunities'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'ListPointOpportunitiesResponse'; ofType: null; }; } }; 'pointOpportunity': { name: 'pointOpportunity'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'PointOpportunityNode'; ofType: null; }; } }; 'rawFundraisingEntries': { name: 'rawFundraisingEntries'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'rawFundraisingTotals': { name: 'rawFundraisingTotals'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'report': { name: 'report'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'Report'; ofType: null; }; } }; 'searchPeopleByName': { name: 'searchPeopleByName'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'PersonNode'; ofType: null; }; }; }; } }; 'solicitationCode': { name: 'solicitationCode'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'SolicitationCodeNode'; ofType: null; }; } }; 'solicitationCodes': { name: 'solicitationCodes'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'ListSolicitationCodesResponse'; ofType: null; }; } }; 'team': { name: 'team'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'TeamNode'; ofType: null; }; } }; 'teams': { name: 'teams'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'ListTeamsResponse'; ofType: null; }; } }; }; }; 'RegisterDeviceInput': { kind: 'INPUT_OBJECT'; name: 'RegisterDeviceInput'; isOneOf: false; inputFields: [{ name: 'deviceId'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }, { name: 'expoPushToken'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'lastUserId'; type: { kind: 'SCALAR'; name: 'GlobalId'; ofType: null; }; defaultValue: null }, { name: 'verifier'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }]; }; 'RegisterDeviceResponse': { kind: 'OBJECT'; name: 'RegisterDeviceResponse'; fields: { 'data': { name: 'data'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'DeviceNode'; ofType: null; }; } }; 'ok': { name: 'ok'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; } }; }; }; 'Report': { kind: 'OBJECT'; name: 'Report'; fields: { 'pages': { name: 'pages'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'ReportPage'; ofType: null; }; }; }; } }; }; }; @@ -198,7 +197,7 @@ export type introspection_types = { 'String': unknown; 'StringComparator': { name: 'StringComparator'; enumValues: 'ENDS_WITH' | 'EQUALS' | 'GREATER_THAN' | 'GREATER_THAN_OR_EQUAL_TO' | 'IS' | 'LESS_THAN' | 'LESS_THAN_OR_EQUAL_TO' | 'STARTS_WITH' | 'SUBSTRING'; }; 'TeamLegacyStatus': { name: 'TeamLegacyStatus'; enumValues: 'DemoTeam' | 'NewTeam' | 'ReturningTeam'; }; - 'TeamNode': { kind: 'OBJECT'; name: 'TeamNode'; fields: { 'captains': { name: 'captains'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'MembershipNode'; ofType: null; }; }; }; } }; 'committeeIdentifier': { name: 'committeeIdentifier'; type: { kind: 'ENUM'; name: 'CommitteeIdentifier'; ofType: null; } }; 'createdAt': { name: 'createdAt'; type: { kind: 'SCALAR'; name: 'DateTimeISO'; ofType: null; } }; 'dbFundsTeam': { name: 'dbFundsTeam'; type: { kind: 'OBJECT'; name: 'DbFundsTeamInfo'; ofType: null; } }; 'fundraisingEntries': { name: 'fundraisingEntries'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'ListFundraisingEntriesResponse'; ofType: null; }; } }; 'fundraisingTotalAmount': { name: 'fundraisingTotalAmount'; type: { kind: 'SCALAR'; name: 'Float'; ofType: null; } }; 'id': { name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'GlobalId'; ofType: null; }; } }; 'legacyStatus': { name: 'legacyStatus'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'ENUM'; name: 'TeamLegacyStatus'; ofType: null; }; } }; 'marathon': { name: 'marathon'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'MarathonNode'; ofType: null; }; } }; 'members': { name: 'members'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'MembershipNode'; ofType: null; }; }; }; } }; 'name': { name: 'name'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'pointEntries': { name: 'pointEntries'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'PointEntryNode'; ofType: null; }; }; }; } }; 'solicitationCode': { name: 'solicitationCode'; type: { kind: 'OBJECT'; name: 'SolicitationCodeNode'; ofType: null; } }; 'totalPoints': { name: 'totalPoints'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'type': { name: 'type'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'ENUM'; name: 'TeamType'; ofType: null; }; } }; 'updatedAt': { name: 'updatedAt'; type: { kind: 'SCALAR'; name: 'DateTimeISO'; ofType: null; } }; }; }; + 'TeamNode': { kind: 'OBJECT'; name: 'TeamNode'; fields: { 'committeeIdentifier': { name: 'committeeIdentifier'; type: { kind: 'ENUM'; name: 'CommitteeIdentifier'; ofType: null; } }; 'createdAt': { name: 'createdAt'; type: { kind: 'SCALAR'; name: 'DateTimeISO'; ofType: null; } }; 'fundraisingEntries': { name: 'fundraisingEntries'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'ListFundraisingEntriesResponse'; ofType: null; }; } }; 'fundraisingTotalAmount': { name: 'fundraisingTotalAmount'; type: { kind: 'SCALAR'; name: 'Float'; ofType: null; } }; 'id': { name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'GlobalId'; ofType: null; }; } }; 'legacyStatus': { name: 'legacyStatus'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'ENUM'; name: 'TeamLegacyStatus'; ofType: null; }; } }; 'marathon': { name: 'marathon'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'MarathonNode'; ofType: null; }; } }; 'members': { name: 'members'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'MembershipNode'; ofType: null; }; }; }; } }; 'name': { name: 'name'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'pointEntries': { name: 'pointEntries'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'PointEntryNode'; ofType: null; }; }; }; } }; 'solicitationCode': { name: 'solicitationCode'; type: { kind: 'OBJECT'; name: 'SolicitationCodeNode'; ofType: null; } }; 'totalPoints': { name: 'totalPoints'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'type': { name: 'type'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'ENUM'; name: 'TeamType'; ofType: null; }; } }; 'updatedAt': { name: 'updatedAt'; type: { kind: 'SCALAR'; name: 'DateTimeISO'; ofType: null; } }; }; }; 'TeamResolverAllKeys': { name: 'TeamResolverAllKeys'; enumValues: 'legacyStatus' | 'marathonId' | 'name' | 'type'; }; 'TeamResolverKeyedIsNullFilterItem': { kind: 'INPUT_OBJECT'; name: 'TeamResolverKeyedIsNullFilterItem'; isOneOf: false; inputFields: [{ name: 'field'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'ENUM'; name: 'TeamResolverAllKeys'; ofType: null; }; }; defaultValue: null }, { name: 'negate'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; defaultValue: "false" }]; }; 'TeamResolverKeyedOneOfFilterItem': { kind: 'INPUT_OBJECT'; name: 'TeamResolverKeyedOneOfFilterItem'; isOneOf: false; inputFields: [{ name: 'field'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'ENUM'; name: 'TeamResolverOneOfFilterKeys'; ofType: null; }; }; defaultValue: null }, { name: 'negate'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; defaultValue: "false" }, { name: 'value'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; }; }; defaultValue: null }]; }; diff --git a/packages/portal/package.json b/packages/portal/package.json index 7977f2cd..bf087f05 100644 --- a/packages/portal/package.json +++ b/packages/portal/package.json @@ -40,6 +40,7 @@ }, "dependencies": { "@ant-design/icons": "^5.5.1", + "@casl/ability": "^6.7.2", "@refinedev/antd": "^5.44.0", "@refinedev/core": "^4.56.0", "@refinedev/devtools": "^1.2.10", diff --git a/packages/portal/src/config/marathon.tsx b/packages/portal/src/config/marathon.tsx index ba2a5c71..be328489 100644 --- a/packages/portal/src/config/marathon.tsx +++ b/packages/portal/src/config/marathon.tsx @@ -1,4 +1,8 @@ -import { AccessLevel, dateTimeFromSomething } from "@ukdanceblue/common"; +import { + AccessLevel, + Action, + dateTimeFromSomething, +} from "@ukdanceblue/common"; import { useEffect, useMemo, useState } from "react"; import { useQuery } from "urql"; @@ -49,7 +53,10 @@ export const MarathonConfigProvider = ({ children: React.ReactNode; valueOverride?: Pick; }) => { - const canSeeMarathonList = useAuthorizationRequirement(AccessLevel.Committee); + const canSeeMarathonList = useAuthorizationRequirement( + Action.List, + "MarathonNode" + ); const [marathonId, setMarathonId] = useState(null); diff --git a/packages/portal/src/config/refine/authorization.ts b/packages/portal/src/config/refine/authorization.ts index 2f0f9330..5a183bf8 100644 --- a/packages/portal/src/config/refine/authorization.ts +++ b/packages/portal/src/config/refine/authorization.ts @@ -1,16 +1,41 @@ -// import type { AccessControlProvider } from "@refinedev/core"; +import type { AccessControlProvider } from "@refinedev/core"; +import { Action } from "@ukdanceblue/common"; -// const accessControlProvider: AccessControlProvider = { -// can: async ({ resource, action, params }) => { -// return { can: true }; -// }, -// options: { -// buttons: { -// enableAccessControl: true, -// hideIfUnauthorized: false, -// }, -// queryOptions: { -// // ... default global query options -// }, -// }, -// }; +import { useAuthorizationRequirement } from "#hooks/useLoginState.ts"; + +export const accessControlProvider: AccessControlProvider = { + can: ({ action, params }) => { + return Promise.resolve({ + // @ts-expect-error TODO: Fix this + can: useAuthorizationRequirement([ + action === "create" + ? Action.Create + : action === "edit" + ? Action.Update + : action === "show" + ? Action.Read + : action === "list" + ? Action.Read + : Action.Manage, + params?.resource?.meta?.nodeName + ? { + id: params.id ? String(params.id) : undefined, + kind: params.resource.meta + .nodeName as "FundraisingAssignmentNode", + ownedByUserIds: [], + withinTeamIds: [], + } + : "all", + ]), + }); + }, + options: { + buttons: { + enableAccessControl: true, + hideIfUnauthorized: false, + }, + queryOptions: { + // ... default global query options + }, + }, +}; diff --git a/packages/portal/src/config/refine/resources.tsx b/packages/portal/src/config/refine/resources.tsx index cec943e8..5181d365 100644 --- a/packages/portal/src/config/refine/resources.tsx +++ b/packages/portal/src/config/refine/resources.tsx @@ -11,200 +11,184 @@ import { UserOutlined, } from "@ant-design/icons"; import type { ResourceProps } from "@refinedev/core"; -import type { Register } from "@tanstack/react-router"; -import type { Authorization, AuthorizationRule } from "@ukdanceblue/common"; -import { checkAuthorization, defaultAuthorization } from "@ukdanceblue/common"; -import type { FileRoutesByFullPath } from "routeTree.gen"; -import { useLoginState } from "#hooks/useLoginState.ts"; - -function shouldShowMenuItem( - authorizationRules: AuthorizationRule[] | null, - authorization: Authorization | undefined -): boolean { - if (!authorizationRules) { - return true; - } else { - let isAuthorized = false; - for (const authorizationRule of authorizationRules) { - if ( - checkAuthorization( - authorizationRule, - authorization ?? defaultAuthorization - ) - ) { - isAuthorized = true; - break; - } - } - return isAuthorized; - } -} - -export function useRefineResources({ router }: { router: Register["router"] }) { - const { authorization } = useLoginState(); - - const refineResources: ResourceProps[] = [ - { - name: "event", - meta: { - icon: , - label: "Events", - }, - create: "/events/create", - edit: "/events/:id/edit", - show: "/events/:id", - list: "/events", +export const refineResources: ResourceProps[] = [ + { + name: "event", + meta: { + icon: , + label: "Events", + nodeName: "EventNode", }, - { - name: "team", - meta: { - icon: , - label: "Teams", - }, - create: "/teams/create", - edit: "/teams/:id/edit", - show: "/teams/:id", - list: "/teams", + create: "/events/create", + edit: "/events/:id/edit", + show: "/events/:id", + list: "/events", + }, + { + name: "team", + meta: { + icon: , + label: "Teams", + nodeName: "TeamNode", }, - { - name: "fundraising", - meta: { - icon: , - label: "Fundraising", - }, - create: "/fundraising/create", - edit: "/fundraising/:id/edit", - show: "/fundraising/:id", - list: "/fundraising", + create: "/teams/create", + edit: "/teams/:id/edit", + show: "/teams/:id", + list: "/teams", + }, + { + name: "fundraising", + meta: { + icon: , + label: "Fundraising", + nodeName: "FundraisingEntryNode", }, - { - name: "solicitationCode", - identifier: "solicitation-code", - meta: { - label: "Solicitation Codes", - parent: "fundraising", - }, - create: "/fundraising/solicitation-code/create", - edit: "/fundraising/solicitation-code/:id/edit", - show: "/fundraising/solicitation-code/:id", - list: "/fundraising/solicitation-code", + create: "/fundraising/create", + edit: "/fundraising/:id/edit", + show: "/fundraising/:id", + list: "/fundraising", + }, + { + name: "solicitationCode", + identifier: "solicitation-code", + meta: { + label: "Solicitation Codes", + parent: "fundraising", + nodeName: "SolicitationCodeNode", }, - { - name: "ddn", - meta: { - label: "Uploaded DDNs", - parent: "fundraising", - }, - create: "/fundraising/ddn/create", - edit: "/fundraising/ddn/:id/edit", - show: "/fundraising/ddn/:id", - list: "/fundraising/ddn", + create: "/fundraising/solicitation-code/create", + edit: "/fundraising/solicitation-code/:id/edit", + show: "/fundraising/solicitation-code/:id", + list: "/fundraising/solicitation-code", + }, + { + name: "ddn", + meta: { + label: "Uploaded DDNs", + parent: "fundraising", + nodeName: "DailyDepartmentNotificationNode", }, - { - name: "dbfunds", - meta: { - label: "DB Funds (Legacy)", - parent: "fundraising", - }, - create: "/fundraising/dbfunds/create", - edit: "/fundraising/dbfunds/:id/edit", - show: "/fundraising/dbfunds/:id", - list: "/fundraising/dbfunds", + create: "/fundraising/ddn/create", + edit: "/fundraising/ddn/:id/edit", + show: "/fundraising/ddn/:id", + list: "/fundraising/ddn", + }, + { + name: "dbfunds", + meta: { + label: "DB Funds (Legacy)", + parent: "fundraising", + modelName: "FundraisingEntryNode", }, - { - name: "person", - meta: { - icon: , - label: "People", - }, - create: "/people/create", - edit: "/people/:id/edit", - show: "/people/:id", - list: "/people", + create: "/fundraising/dbfunds/create", + edit: "/fundraising/dbfunds/:id/edit", + show: "/fundraising/dbfunds/:id", + list: "/fundraising/dbfunds", + }, + { + name: "person", + meta: { + icon: , + label: "People", + modelName: "PersonNode", }, - { - name: "notification", - meta: { - icon: , - label: "Notifications", - }, - create: "/notifications/create", - edit: "/notifications/:id/edit", - show: "/notifications/:id", - list: "/notifications", + create: "/people/create", + edit: "/people/:id/edit", + show: "/people/:id", + list: "/people", + }, + { + name: "notification", + meta: { + icon: , + label: "Notifications", + modelName: "NotificationNode", }, - { - name: "marathon", - meta: { - icon: , - label: "Marathon", - }, - create: "/marathon/create", - edit: "/marathon/:id/edit", - show: "/marathon/:id", - list: "/marathon", + create: "/notifications/create", + edit: "/notifications/:id/edit", + show: "/notifications/:id", + list: "/notifications", + }, + { + name: "marathon", + meta: { + icon: , + label: "Marathon", + modelName: "MarathonNode", }, - { - name: "feed", - meta: { - icon: , - label: "Feed", - }, - create: "/feed/create", - edit: "/feed/:id/edit", - show: "/feed/:id", - list: "/feed", + create: "/marathon/create", + edit: "/marathon/:id/edit", + show: "/marathon/:id", + list: "/marathon", + }, + { + name: "feed", + meta: { + icon: , + label: "Feed", + modelName: "FeedNode", }, - { - name: "image", - meta: { - icon: , - label: "Images", - }, - create: "/images/create", - edit: "/images/:id/edit", - show: "/images/:id", - list: "/images", + create: "/feed/create", + edit: "/feed/:id/edit", + show: "/feed/:id", + list: "/feed", + }, + { + name: "image", + meta: { + icon: , + label: "Images", + modelName: "ImageNode", }, - { - name: "config", - meta: { - icon: , - label: "Config", - }, - create: "/config/create", - edit: "/config/:id/edit", - show: "/config/:id", - list: "/config", + create: "/images/create", + edit: "/images/:id/edit", + show: "/images/:id", + list: "/images", + }, + { + name: "config", + meta: { + icon: , + label: "Config", + modelName: "ConfigNode", }, - { - name: "log", - meta: { - icon: , - label: "Logs", - }, - create: "/admin/logs/create", - edit: "/admin/logs/:id/edit", - show: "/admin/logs/:id", - list: "/admin/logs", + create: "/config/create", + edit: "/config/:id/edit", + show: "/config/:id", + list: "/config", + }, + { + name: "log", + meta: { + icon: , + label: "Logs", + modelName: "LogNode", }, - ]; + create: "/admin/logs/create", + edit: "/admin/logs/:id/edit", + show: "/admin/logs/:id", + list: "/admin/logs", + }, +]; + +export function useRefineResources() { + // const { authorization } = useLoginState(); - for (const resource of refineResources) { - const route = router.routesByPath[ - String(resource.list) as keyof FileRoutesByFullPath - ] as (typeof router)["routesByPath"]["/"] | undefined; - if (!route) { - continue; - } + // for (const resource of refineResources) { + // const route = router.routesByPath[ + // String(resource.list) as keyof FileRoutesByFullPath + // ] as (typeof router)["routesByPath"]["/"] | undefined; + // if (!route) { + // continue; + // } - const { authorizationRules } = route.options.staticData; - resource.meta = { - ...resource.meta, - hide: !shouldShowMenuItem(authorizationRules, authorization), - }; - } + // const { authorizationRules } = route.options.staticData; + // resource.meta = { + // ...resource.meta, + // hide: !shouldShowMenuItem(authorizationRules, authorization), + // }; + // } return refineResources; } diff --git a/packages/portal/src/elements/forms/person/edit/PersonEditor.tsx b/packages/portal/src/elements/forms/person/edit/PersonEditor.tsx index aefb7db5..624446ca 100644 --- a/packages/portal/src/elements/forms/person/edit/PersonEditor.tsx +++ b/packages/portal/src/elements/forms/person/edit/PersonEditor.tsx @@ -1,5 +1,5 @@ import type { Authorization } from "@ukdanceblue/common"; -import { AccessLevel, CommitteeRole } from "@ukdanceblue/common"; +import { AccessLevel, Action, CommitteeRole } from "@ukdanceblue/common"; import { App, AutoComplete, @@ -41,7 +41,8 @@ export function PersonEditor({ }) { const selectedMarathon = useMarathon(); - const isAdmin = useAuthorizationRequirement(AccessLevel.Admin); + // TODO: Make auth actually check for privallege escalation + const isAdmin = useAuthorizationRequirement(Action.Manage, "all"); const { message } = App.useApp(); diff --git a/packages/portal/src/elements/singletons/NavigationMenu.tsx b/packages/portal/src/elements/singletons/NavigationMenu.tsx index bb7a593e..861903c0 100644 --- a/packages/portal/src/elements/singletons/NavigationMenu.tsx +++ b/packages/portal/src/elements/singletons/NavigationMenu.tsx @@ -1,6 +1,6 @@ import "./NavigationMenu.css"; -import { AccessLevel } from "@ukdanceblue/common"; +import { AccessLevel, Action } from "@ukdanceblue/common"; import { Button, Modal, Select } from "antd"; import { useContext } from "react"; @@ -17,7 +17,7 @@ export const ConfigModal = ({ open: boolean; onClose: () => void; }) => { - const canMasquerade = useAuthorizationRequirement(AccessLevel.SuperAdmin); + const canMasquerade = useAuthorizationRequirement(Action.Manage, "all"); const { setMarathon, marathon, loading, marathons } = useContext(marathonContext); diff --git a/packages/portal/src/elements/viewers/person/PersonViewer.tsx b/packages/portal/src/elements/viewers/person/PersonViewer.tsx index 3102e085..5fedfb8c 100644 --- a/packages/portal/src/elements/viewers/person/PersonViewer.tsx +++ b/packages/portal/src/elements/viewers/person/PersonViewer.tsx @@ -7,6 +7,8 @@ import type { } from "@ukdanceblue/common"; import { AccessLevel, + Action, + AuthSource, committeeNames, MembershipPositionType, roleToAccessLevel, @@ -15,7 +17,7 @@ import { import { Button, Card, Descriptions, Empty, Flex, Typography } from "antd"; import type { FragmentOf } from "#graphql/index.js"; -import { graphql,readFragment } from "#graphql/index.js"; +import { graphql, readFragment } from "#graphql/index.js"; import { useAuthorizationRequirement } from "#hooks/useLoginState.js"; import { usePersonDeletePopup } from "../../components/person/PersonDeletePopup"; @@ -72,7 +74,8 @@ export function PersonViewer({ }); const canEditPerson = useAuthorizationRequirement( - AccessLevel.CommitteeChairOrCoordinator + Action.Update, + "PersonNode" ); if (!personData) { @@ -141,10 +144,10 @@ export function PersonViewer({ { label: "Access Level", children: stringifyAccessLevel( - roleToAccessLevel({ - dbRole: personData.dbRole, - effectiveCommitteeRoles: committees, - }) + roleToAccessLevel( + committees, + authorization?.authSource ?? AuthSource.None + ) ), }, ...committees.map((committee) => ({ diff --git a/packages/portal/src/elements/viewers/team/TeamViewer.tsx b/packages/portal/src/elements/viewers/team/TeamViewer.tsx index fd69547c..a8d0d509 100644 --- a/packages/portal/src/elements/viewers/team/TeamViewer.tsx +++ b/packages/portal/src/elements/viewers/team/TeamViewer.tsx @@ -6,6 +6,7 @@ import { import { Link, useNavigate } from "@tanstack/react-router"; import { AccessLevel, + Action, CommitteeIdentifier, CommitteeRole, MembershipPositionType, @@ -16,7 +17,7 @@ import { useMutation } from "urql"; import { PersonSearch } from "#elements/components/person/PersonSearch.js"; import type { FragmentOf } from "#graphql/index.js"; -import { graphql,readFragment } from "#graphql/index.js"; +import { graphql, readFragment } from "#graphql/index.js"; import { useAuthorizationRequirement } from "#hooks/useLoginState.js"; import { useQueryStatusWatcher } from "#hooks/useQueryStatusWatcher.js"; @@ -56,29 +57,14 @@ export function TeamViewer({ }) { const teamData = readFragment(TeamViewerFragment, teamFragment) ?? undefined; - const canEditTeams = useAuthorizationRequirement( - { - accessLevel: AccessLevel.Admin, - }, - { - committeeIdentifier: CommitteeIdentifier.dancerRelationsCommittee, - minCommitteeRole: CommitteeRole.Coordinator, - } - ); + const canEditTeams = useAuthorizationRequirement(Action.Update, "TeamNode"); const canEditMemberships = useAuthorizationRequirement( - { - accessLevel: AccessLevel.Admin, - }, - { - accessLevel: AccessLevel.CommitteeChairOrCoordinator, - committeeIdentifier: CommitteeIdentifier.viceCommittee, - } + Action.Update, + "MembershipNode" ); - const canViewPeople = useAuthorizationRequirement({ - accessLevel: AccessLevel.Committee, - }); + const canViewPeople = useAuthorizationRequirement(Action.Get, "PersonNode"); const [personToAssignToTeam, setPersonToAssignToTeam] = useState<{ uuid: string; diff --git a/packages/portal/src/hooks/useLoginState.ts b/packages/portal/src/hooks/useLoginState.ts index 7ff18409..6bb2c243 100644 --- a/packages/portal/src/hooks/useLoginState.ts +++ b/packages/portal/src/hooks/useLoginState.ts @@ -1,11 +1,15 @@ +import { PureAbility } from "@casl/ability"; +import type { PackRule } from "@casl/ability/extra"; +import { unpackRules } from "@casl/ability/extra"; import type { - AccessLevel, + AccessControlParam, + AppAbility, Authorization, - AuthorizationRule, } from "@ukdanceblue/common"; +import { AccessLevel } from "@ukdanceblue/common"; import { - checkAuthorization, defaultAuthorization, + getAuthorizationFor, roleToAccessLevel, } from "@ukdanceblue/common"; import { useMemo } from "react"; @@ -20,12 +24,15 @@ const loginStateDocument = graphql(/* GraphQL */ ` loginState { loggedIn dbRole + authSource effectiveCommitteeRoles { role identifier } + abilityRules } me { + id name linkblue email @@ -38,12 +45,15 @@ export interface PortalAuthData { loggedIn: boolean | undefined; me: | { + id: string; name?: string | null | undefined; linkblue?: string | null | undefined; email?: string | null | undefined; } | null | undefined; + + ability: AppAbility; } function parseLoginState( @@ -62,6 +72,12 @@ function parseLoginState( loggedIn: undefined, authorization: undefined, me: undefined, + ability: getAuthorizationFor({ + accessLevel: AccessLevel.None, + effectiveCommitteeRoles: [], + teamMemberships: [], + userId: null, + }), }; } @@ -75,11 +91,19 @@ function parseLoginState( authorization: { effectiveCommitteeRoles: committees, dbRole: result.data.loginState.dbRole, - accessLevel: roleToAccessLevel({ - dbRole: result.data.loginState.dbRole, - effectiveCommitteeRoles: committees, - }), + accessLevel: roleToAccessLevel( + committees, + result.data.loginState.authSource + ), + authSource: result.data.loginState.authSource, }, + ability: new PureAbility( + unpackRules( + result.data.loginState.abilityRules as PackRule< + AppAbility["rules"][number] + >[] + ) + ), me: result.data.me, }; } else { @@ -87,6 +111,12 @@ function parseLoginState( loggedIn: false, authorization: defaultAuthorization, me: null, + ability: getAuthorizationFor({ + accessLevel: AccessLevel.None, + effectiveCommitteeRoles: [], + teamMemberships: [], + userId: null, + }), }; } } @@ -119,19 +149,9 @@ export function useLoginState(): PortalAuthData { } export function useAuthorizationRequirement( - ...rules: AuthorizationRule[] | [AccessLevel] + ...rule: AccessControlParam ): boolean { - const { authorization } = useLoginState(); - - if (!authorization) { - return false; - } + const { ability } = useLoginState(); - if (typeof rules[0] === "number") { - return authorization.accessLevel >= rules[0]; - } - - return (rules as AuthorizationRule[]).some((r) => - checkAuthorization(r, authorization) - ); + return ability.can(...rule); } diff --git a/packages/portal/src/main.tsx b/packages/portal/src/main.tsx index ea37a6e1..2c5546b3 100644 --- a/packages/portal/src/main.tsx +++ b/packages/portal/src/main.tsx @@ -13,7 +13,7 @@ import { Link, RouterProvider, } from "@tanstack/react-router"; -import type { AuthorizationRule } from "@ukdanceblue/common"; +import type { AccessControlParam } from "@ukdanceblue/common"; import { App, Empty, Spin } from "antd"; import { App as AntApp } from "antd"; import type { useAppProps } from "antd/es/app/context.js"; @@ -25,8 +25,9 @@ import { AntConfigProvider, ThemeConfigProvider } from "#config/ant.js"; import { API_BASE_URL, urqlClient } from "#config/api.js"; import { MarathonConfigProvider } from "#config/marathon.js"; import { authProvider } from "#config/refine/authentication.js"; +import { accessControlProvider } from "#config/refine/authorization.js"; import { dataProvider } from "#config/refine/data.js"; -import { useRefineResources } from "#config/refine/resources.js"; +import { refineResources } from "#config/refine/resources.js"; import { SpinningRibbon } from "#elements/components/design/RibbonSpinner.js"; import { routeTree } from "./routeTree.gen.js"; @@ -97,7 +98,7 @@ declare module "@tanstack/react-router" { router: typeof router; } interface StaticDataRouteOption { - authorizationRules: AuthorizationRule[] | null; + authorizationRules: unknown; } } @@ -135,8 +136,6 @@ function RouterWrapper() { const antApp = App.useApp(); - const resources = useRefineResources({ router }); - return isServerReachable === false ? (
{import.meta.env.MODE === "development" && } diff --git a/packages/portal/src/routes/__root.tsx b/packages/portal/src/routes/__root.tsx index fa1f58f5..5fb20729 100644 --- a/packages/portal/src/routes/__root.tsx +++ b/packages/portal/src/routes/__root.tsx @@ -12,7 +12,6 @@ import { themeConfigContext } from "#config/antThemeConfig.ts"; import { Sider } from "#elements/components/sider/index.tsx"; import { ConfigModal } from "#elements/singletons/NavigationMenu.tsx"; import { refreshLoginState, useLoginState } from "#hooks/useLoginState.js"; -import { routerAuthCheck } from "#tools/routerAuthCheck.js"; const TanStackRouterDevtools = process.env.NODE_ENV === "production" @@ -135,7 +134,6 @@ export const Route = createRootRouteWithContext()({ component: RootComponent, beforeLoad: async ({ context }) => { await refreshLoginState(context.urqlClient); - routerAuthCheck(Route, context); }, staticData: { authorizationRules: null, diff --git a/packages/portal/src/routes/admin/logs.tsx b/packages/portal/src/routes/admin/logs.tsx index 3fde0227..4a67937e 100644 --- a/packages/portal/src/routes/admin/logs.tsx +++ b/packages/portal/src/routes/admin/logs.tsx @@ -5,7 +5,6 @@ import { useQuery } from "urql"; import { LogViewer } from "#elements/viewers/admin/LogViewer.js"; import { graphql } from "#graphql/index.js"; import { useQueryStatusWatcher } from "#hooks/useQueryStatusWatcher.js"; -import { routerAuthCheck } from "#tools/routerAuthCheck.js"; const logsPageDocument = graphql(/* GraphQL */ ` query LogsPage { @@ -31,7 +30,6 @@ export const Route = createFileRoute("/admin/logs")({ component: LogsPage, async beforeLoad({ context }) { await context.urqlClient.query(logsPageDocument, {}); - routerAuthCheck(Route, context); }, staticData: { authorizationRules: [ diff --git a/packages/portal/src/routes/config/index.tsx b/packages/portal/src/routes/config/index.tsx index 3fb6a715..781bccb7 100644 --- a/packages/portal/src/routes/config/index.tsx +++ b/packages/portal/src/routes/config/index.tsx @@ -5,7 +5,6 @@ import { useState } from "react"; import { ConfigItem } from "#elements/forms/config/ConfigItem.js"; import { useConfigForm } from "#elements/forms/config/useConfigForm.js"; -import { routerAuthCheck } from "#tools/routerAuthCheck.js"; // Form keys can only contain uppercase letters and underscores const FORM_KEY_REGEX = /^[A-Z_]+$/; @@ -126,9 +125,7 @@ function ConfigPage() { export const Route = createFileRoute("/config/")({ component: ConfigPage, - beforeLoad({ context }) { - routerAuthCheck(Route, context); - }, + staticData: { authorizationRules: [ { diff --git a/packages/portal/src/routes/events/$eventId/edit.tsx b/packages/portal/src/routes/events/$eventId/edit.tsx index 77d1be05..c04c834f 100644 --- a/packages/portal/src/routes/events/$eventId/edit.tsx +++ b/packages/portal/src/routes/events/$eventId/edit.tsx @@ -6,7 +6,6 @@ import { EventEditor } from "#elements/forms/event/edit/EventEditor.js"; import { EventEditorFragment } from "#elements/forms/event/edit/EventEditorGQL.ts"; import { graphql } from "#graphql/index.js"; import { useQueryStatusWatcher } from "#hooks/useQueryStatusWatcher.js"; -import { routerAuthCheck } from "#tools/routerAuthCheck.js"; const viewEventPageDocument = graphql( /* GraphQL */ ` @@ -44,7 +43,6 @@ export const Route = createFileRoute("/events/$eventId/edit")({ component: EditEvent, async beforeLoad({ context, params: { eventId } }) { await context.urqlClient.query(viewEventPageDocument, { uuid: eventId }); - routerAuthCheck(Route, context); }, staticData: { authorizationRules: [ diff --git a/packages/portal/src/routes/events/$eventId/index.tsx b/packages/portal/src/routes/events/$eventId/index.tsx index f6b67dba..6b5035b7 100644 --- a/packages/portal/src/routes/events/$eventId/index.tsx +++ b/packages/portal/src/routes/events/$eventId/index.tsx @@ -8,7 +8,6 @@ import { } from "#elements/viewers/event/EventViewer.js"; import { graphql } from "#graphql/index.js"; import { useQueryStatusWatcher } from "#hooks/useQueryStatusWatcher.js"; -import { routerAuthCheck } from "#tools/routerAuthCheck.js"; const viewEventPageDocument = graphql( /* GraphQL */ ` @@ -46,7 +45,6 @@ export const Route = createFileRoute("/events/$eventId/")({ component: ViewEvent, async beforeLoad({ context, params: { eventId } }) { await context.urqlClient.query(viewEventPageDocument, { uuid: eventId }); - routerAuthCheck(Route, context); }, staticData: { authorizationRules: [ diff --git a/packages/portal/src/routes/events/create.tsx b/packages/portal/src/routes/events/create.tsx index f2dee702..149d141a 100644 --- a/packages/portal/src/routes/events/create.tsx +++ b/packages/portal/src/routes/events/create.tsx @@ -2,7 +2,6 @@ import { createFileRoute } from "@tanstack/react-router"; import { AccessLevel, CommitteeRole } from "@ukdanceblue/common"; import { EventCreator } from "#elements/forms/event/create/EventCreator.js"; -import { routerAuthCheck } from "#tools/routerAuthCheck.js"; function EventsCreate() { return ( @@ -16,9 +15,7 @@ function EventsCreate() { export const Route = createFileRoute("/events/create")({ component: EventsCreate, - beforeLoad({ context }) { - routerAuthCheck(Route, context); - }, + staticData: { authorizationRules: [ { diff --git a/packages/portal/src/routes/events/index.tsx b/packages/portal/src/routes/events/index.tsx index 410f61e8..77d3a652 100644 --- a/packages/portal/src/routes/events/index.tsx +++ b/packages/portal/src/routes/events/index.tsx @@ -4,7 +4,6 @@ import { AccessLevel } from "@ukdanceblue/common"; import { Button, Flex, Typography } from "antd"; import { EventsTable } from "#elements/tables/EventsTable.js"; -import { routerAuthCheck } from "#tools/routerAuthCheck.js"; function Events() { return ( @@ -24,9 +23,7 @@ function Events() { export const Route = createFileRoute("/events/")({ component: Events, - beforeLoad({ context }) { - routerAuthCheck(Route, context); - }, + staticData: { authorizationRules: [ { diff --git a/packages/portal/src/routes/feed/index.tsx b/packages/portal/src/routes/feed/index.tsx index c494264b..bbb42f80 100644 --- a/packages/portal/src/routes/feed/index.tsx +++ b/packages/portal/src/routes/feed/index.tsx @@ -21,7 +21,6 @@ import { useClient, useQuery } from "urql"; import { graphql } from "#graphql/index.js"; import { useImagePicker } from "#hooks/useImagePicker.js"; import { useQueryStatusWatcher } from "#hooks/useQueryStatusWatcher.js"; -import { routerAuthCheck } from "#tools/routerAuthCheck.js"; const feedPageDocument = graphql(/* GraphQL */ ` query FeedPage { @@ -222,7 +221,6 @@ export const Route = createFileRoute("/feed/")({ component: FeedPage, async beforeLoad({ context }) { await context.urqlClient.query(feedPageDocument, {}); - routerAuthCheck(Route, context); }, staticData: { authorizationRules: [ diff --git a/packages/portal/src/routes/fundraising/$entryId/edit.tsx b/packages/portal/src/routes/fundraising/$entryId/edit.tsx index c76d21b8..3cd98fda 100644 --- a/packages/portal/src/routes/fundraising/$entryId/edit.tsx +++ b/packages/portal/src/routes/fundraising/$entryId/edit.tsx @@ -2,13 +2,10 @@ import { createFileRoute, useParams } from "@tanstack/react-router"; import { AccessLevel, CommitteeIdentifier } from "@ukdanceblue/common"; import { FundraisingEntryEditor } from "#elements/forms/fundraising-entry/edit/FundraisingEntryEditor.tsx"; -import { routerAuthCheck } from "#tools/routerAuthCheck.tsx"; export const Route = createFileRoute("/fundraising/$entryId/edit")({ component: RouteComponent, - beforeLoad({ context }) { - routerAuthCheck(Route, context); - }, + staticData: { authorizationRules: [ { diff --git a/packages/portal/src/routes/fundraising/dbfunds.tsx b/packages/portal/src/routes/fundraising/dbfunds.tsx index 7dbef7ee..448b8afe 100644 --- a/packages/portal/src/routes/fundraising/dbfunds.tsx +++ b/packages/portal/src/routes/fundraising/dbfunds.tsx @@ -9,7 +9,6 @@ import { utils, writeFile } from "xlsx"; import { useMarathon } from "#config/marathonContext.js"; import { graphql } from "#graphql/index.js"; -import { routerAuthCheck } from "#tools/routerAuthCheck.js"; interface FundraisingTeam { name: string; @@ -78,7 +77,7 @@ function DbFundsViewer() { }, [marathonYear, selectedId, teamData.data]); return ( - ( + Fundraising Teams @@ -221,15 +220,13 @@ function DbFundsViewer() { }} /> - ) + ); } export const Route = createFileRoute("/fundraising/dbfunds")({ component: DbFundsViewer, - beforeLoad({ context }) { - routerAuthCheck(Route, context); - }, + staticData: { authorizationRules: [ { diff --git a/packages/portal/src/routes/fundraising/ddn/$ddnId/index.tsx b/packages/portal/src/routes/fundraising/ddn/$ddnId/index.tsx index 2dc3568d..e7822e69 100644 --- a/packages/portal/src/routes/fundraising/ddn/$ddnId/index.tsx +++ b/packages/portal/src/routes/fundraising/ddn/$ddnId/index.tsx @@ -11,7 +11,6 @@ import { useQuery } from "urql"; import { SpinningRibbon } from "#elements/components/design/RibbonSpinner"; import { graphql, readFragment } from "#graphql/index"; import { useQueryStatusWatcher } from "#hooks/useQueryStatusWatcher"; -import { routerAuthCheck } from "#tools/routerAuthCheck"; export const Route = createFileRoute("/fundraising/ddn/$ddnId/")({ component: RouteComponent, @@ -26,9 +25,6 @@ export const Route = createFileRoute("/fundraising/ddn/$ddnId/")({ }, ], }, - beforeLoad({ context }) { - routerAuthCheck(Route, context); - }, }); const ViewDdnFragment = graphql(/* GraphQL */ ` diff --git a/packages/portal/src/routes/fundraising/ddn/index.tsx b/packages/portal/src/routes/fundraising/ddn/index.tsx index 0135f650..e4578927 100644 --- a/packages/portal/src/routes/fundraising/ddn/index.tsx +++ b/packages/portal/src/routes/fundraising/ddn/index.tsx @@ -8,7 +8,6 @@ import { import { Button, Flex } from "antd"; import { DDNTable } from "#elements/tables/fundraising/DDNTable"; -import { routerAuthCheck } from "#tools/routerAuthCheck"; export const Route = createFileRoute("/fundraising/ddn/")({ component: RouteComponent, @@ -23,9 +22,6 @@ export const Route = createFileRoute("/fundraising/ddn/")({ }, ], }, - beforeLoad({ context }) { - routerAuthCheck(Route, context); - }, }); function RouteComponent() { diff --git a/packages/portal/src/routes/fundraising/ddn/upload.tsx b/packages/portal/src/routes/fundraising/ddn/upload.tsx index 78677add..20d8bce4 100644 --- a/packages/portal/src/routes/fundraising/ddn/upload.tsx +++ b/packages/portal/src/routes/fundraising/ddn/upload.tsx @@ -6,7 +6,6 @@ import { } from "@ukdanceblue/common"; import { DDNUploadForm } from "#elements/forms/ddn/DDNUploadForm"; -import { routerAuthCheck } from "#tools/routerAuthCheck"; function DDNSpreadsheetUploader() { return ( @@ -35,7 +34,4 @@ export const Route = createFileRoute("/fundraising/ddn/upload")({ }, ], }, - beforeLoad({ context }) { - routerAuthCheck(Route, context); - }, }); diff --git a/packages/portal/src/routes/fundraising/index.tsx b/packages/portal/src/routes/fundraising/index.tsx index ac6ef422..e0845c7c 100644 --- a/packages/portal/src/routes/fundraising/index.tsx +++ b/packages/portal/src/routes/fundraising/index.tsx @@ -13,7 +13,6 @@ import { import { graphql } from "#graphql/index"; import { useListQuery } from "#hooks/useListQuery"; import { useQueryStatusWatcher } from "#hooks/useQueryStatusWatcher"; -import { routerAuthCheck } from "#tools/routerAuthCheck"; const ViewTeamFundraisingDocument = graphql( /* GraphQL */ ` @@ -57,9 +56,6 @@ export const Route = createFileRoute("/fundraising/")({ }, ], }, - beforeLoad({ context }) { - routerAuthCheck(Route, context); - }, }); function RouteComponent() { diff --git a/packages/portal/src/routes/fundraising/solicitation-code/$solicitationCodeId/index.tsx b/packages/portal/src/routes/fundraising/solicitation-code/$solicitationCodeId/index.tsx index c5224318..051ede4f 100644 --- a/packages/portal/src/routes/fundraising/solicitation-code/$solicitationCodeId/index.tsx +++ b/packages/portal/src/routes/fundraising/solicitation-code/$solicitationCodeId/index.tsx @@ -24,15 +24,12 @@ import { graphql } from "#graphql/index"; import { useAntFeedback, useAskConfirm } from "#hooks/useAntFeedback"; import { useListQuery } from "#hooks/useListQuery"; import { useQueryStatusWatcher } from "#hooks/useQueryStatusWatcher"; -import { routerAuthCheck } from "#tools/routerAuthCheck"; export const Route = createFileRoute( "/fundraising/solicitation-code/$solicitationCodeId/" )({ component: RouteComponent, - beforeLoad({ context }) { - routerAuthCheck(Route, context); - }, + staticData: { authorizationRules: [ { diff --git a/packages/portal/src/routes/fundraising/solicitation-code/create.tsx b/packages/portal/src/routes/fundraising/solicitation-code/create.tsx index 53dc2198..42dd5be6 100644 --- a/packages/portal/src/routes/fundraising/solicitation-code/create.tsx +++ b/packages/portal/src/routes/fundraising/solicitation-code/create.tsx @@ -7,13 +7,10 @@ import { Button, Flex, Form, Input, InputNumber } from "antd"; import { createSolicitationCodeDocument } from "#documents/solicitationCode.ts"; import type { ResultOf, VariablesOf } from "#graphql/index"; -import { routerAuthCheck } from "#tools/routerAuthCheck.tsx"; export const Route = createFileRoute("/fundraising/solicitation-code/create")({ component: RouteComponent, - beforeLoad({ context }) { - routerAuthCheck(Route, context); - }, + staticData: { authorizationRules: [ { diff --git a/packages/portal/src/routes/fundraising/solicitation-code/index.tsx b/packages/portal/src/routes/fundraising/solicitation-code/index.tsx index 9493d582..b2671fd8 100644 --- a/packages/portal/src/routes/fundraising/solicitation-code/index.tsx +++ b/packages/portal/src/routes/fundraising/solicitation-code/index.tsx @@ -4,13 +4,10 @@ import { AccessLevel, CommitteeIdentifier } from "@ukdanceblue/common"; import { Button, Flex } from "antd"; import { SolicitationCodeTable } from "#elements/tables/fundraising/SolicitationCodeTable"; -import { routerAuthCheck } from "#tools/routerAuthCheck"; export const Route = createFileRoute("/fundraising/solicitation-code/")({ component: RouteComponent, - beforeLoad({ context }) { - routerAuthCheck(Route, context); - }, + staticData: { authorizationRules: [ { diff --git a/packages/portal/src/routes/images/$.tsx b/packages/portal/src/routes/images/$.tsx index 06eed644..aa8d6616 100644 --- a/packages/portal/src/routes/images/$.tsx +++ b/packages/portal/src/routes/images/$.tsx @@ -6,7 +6,6 @@ import { useState } from "react"; import { CreateImagePopup } from "#elements/components/image/CreateImagePopup.js"; import { ImagesTable } from "#elements/tables/ImagesTable.js"; -import { routerAuthCheck } from "#tools/routerAuthCheck.js"; function ListImagesPage() { const [createImageOpen, setCreateImageOpen] = useState(false); @@ -45,9 +44,7 @@ function ListImagesPage() { export const Route = createFileRoute("/images/$")({ component: ListImagesPage, - beforeLoad({ context }) { - routerAuthCheck(Route, context); - }, + staticData: { authorizationRules: [ { diff --git a/packages/portal/src/routes/images/index.tsx b/packages/portal/src/routes/images/index.tsx index c44cd374..0e492fb0 100644 --- a/packages/portal/src/routes/images/index.tsx +++ b/packages/portal/src/routes/images/index.tsx @@ -6,13 +6,10 @@ import { useState } from "react"; import { CreateImagePopup } from "#elements/components/image/CreateImagePopup.js"; import { ImagesTable } from "#elements/tables/ImagesTable.js"; -import { routerAuthCheck } from "#tools/routerAuthCheck.js"; export const Route = createFileRoute("/images/")({ component: RouteComponent, - beforeLoad({ context }) { - routerAuthCheck(Route, context); - }, + staticData: { authorizationRules: [ { diff --git a/packages/portal/src/routes/index.tsx b/packages/portal/src/routes/index.tsx index 872a1e52..82e52f7a 100644 --- a/packages/portal/src/routes/index.tsx +++ b/packages/portal/src/routes/index.tsx @@ -9,7 +9,6 @@ import { import { graphql } from "#graphql/index.js"; import { useLoginState } from "#hooks/useLoginState.js"; import { useQueryStatusWatcher } from "#hooks/useQueryStatusWatcher.js"; -import { routerAuthCheck } from "#tools/routerAuthCheck.js"; const ViewMePageDocument = graphql( /* GraphQL */ ` @@ -24,9 +23,7 @@ const ViewMePageDocument = graphql( export const Route = createFileRoute("/")({ component: HomePage, - beforeLoad({ context }) { - routerAuthCheck(Route, context); - }, + staticData: { authorizationRules: null, }, diff --git a/packages/portal/src/routes/marathon/$marathonId/edit.tsx b/packages/portal/src/routes/marathon/$marathonId/edit.tsx index 6ea2e574..441e1e30 100644 --- a/packages/portal/src/routes/marathon/$marathonId/edit.tsx +++ b/packages/portal/src/routes/marathon/$marathonId/edit.tsx @@ -2,7 +2,6 @@ import { createFileRoute } from "@tanstack/react-router"; import { AccessLevel } from "@ukdanceblue/common"; import { EditMarathonForm } from "#elements/forms/marathon/EditMarathonForm.js"; -import { routerAuthCheck } from "#tools/routerAuthCheck.js"; function EditMarathonPage() { return ( @@ -14,9 +13,7 @@ function EditMarathonPage() { export const Route = createFileRoute("/marathon/$marathonId/edit")({ component: EditMarathonPage, - beforeLoad({ context }) { - routerAuthCheck(Route, context); - }, + staticData: { authorizationRules: [ { diff --git a/packages/portal/src/routes/marathon/$marathonId/hours/$hourId/index.tsx b/packages/portal/src/routes/marathon/$marathonId/hours/$hourId/index.tsx index a5858f40..cfc8b0d3 100644 --- a/packages/portal/src/routes/marathon/$marathonId/hours/$hourId/index.tsx +++ b/packages/portal/src/routes/marathon/$marathonId/hours/$hourId/index.tsx @@ -13,7 +13,6 @@ import type { TanAntChildInputProps } from "#elements/components/form/TanAntForm import { TanAntFormItem } from "#elements/components/form/TanAntFormItem.js"; import { graphql } from "#graphql/index.js"; import { useQueryStatusWatcher } from "#hooks/useQueryStatusWatcher.js"; -import { routerAuthCheck } from "#tools/routerAuthCheck.js"; const editMarathonHourDataDocument = graphql(/* GraphQL */ ` query EditMarathonHourData($marathonHourUuid: GlobalId!) { @@ -219,7 +218,6 @@ export const Route = createFileRoute("/marathon/$marathonId/hours/$hourId/")({ context.urqlClient.query(editMarathonHourDataDocument, { marathonHourUuid: hourId, }); - routerAuthCheck(Route, context); }, staticData: { authorizationRules: [ diff --git a/packages/portal/src/routes/marathon/$marathonId/hours/add.tsx b/packages/portal/src/routes/marathon/$marathonId/hours/add.tsx index 9da5207e..12480f50 100644 --- a/packages/portal/src/routes/marathon/$marathonId/hours/add.tsx +++ b/packages/portal/src/routes/marathon/$marathonId/hours/add.tsx @@ -13,7 +13,6 @@ import type { TanAntChildInputProps } from "#elements/components/form/TanAntForm import { TanAntFormItem } from "#elements/components/form/TanAntFormItem.js"; import { graphql } from "#graphql/index.js"; import { useQueryStatusWatcher } from "#hooks/useQueryStatusWatcher.js"; -import { routerAuthCheck } from "#tools/routerAuthCheck.js"; function AddMarathonHourPage() { const [{ fetching, error }, addMarathonHour] = useMutation( @@ -184,9 +183,7 @@ function AddMarathonHourPage() { export const Route = createFileRoute("/marathon/$marathonId/hours/add")({ component: AddMarathonHourPage, - beforeLoad({ context }) { - routerAuthCheck(Route, context); - }, + staticData: { authorizationRules: [ { diff --git a/packages/portal/src/routes/marathon/$marathonId/index.tsx b/packages/portal/src/routes/marathon/$marathonId/index.tsx index 85f6bbb1..8a718be3 100644 --- a/packages/portal/src/routes/marathon/$marathonId/index.tsx +++ b/packages/portal/src/routes/marathon/$marathonId/index.tsx @@ -7,7 +7,6 @@ import { MarathonViewerFragment, } from "#elements/viewers/marathon/MarathonViewer.js"; import { graphql } from "#graphql/index.js"; -import { routerAuthCheck } from "#tools/routerAuthCheck.js"; const marathonPageDocument = graphql( /* GraphQL */ ` @@ -41,7 +40,6 @@ export const Route = createFileRoute("/marathon/$marathonId/")({ context.urqlClient.query(marathonPageDocument, { marathonUuid: marathonId, }); - routerAuthCheck(Route, context); }, staticData: { authorizationRules: [ diff --git a/packages/portal/src/routes/marathon/create.tsx b/packages/portal/src/routes/marathon/create.tsx index bc452bc9..1b8ef007 100644 --- a/packages/portal/src/routes/marathon/create.tsx +++ b/packages/portal/src/routes/marathon/create.tsx @@ -2,7 +2,6 @@ import { createFileRoute } from "@tanstack/react-router"; import { AccessLevel } from "@ukdanceblue/common"; import { CreateMarathonForm } from "#elements/forms/marathon/CreateMarathonForm.js"; -import { routerAuthCheck } from "#tools/routerAuthCheck.js"; export function CreateMarathonPage() { return ( @@ -14,9 +13,7 @@ export function CreateMarathonPage() { export const Route = createFileRoute("/marathon/create")({ component: CreateMarathonPage, - beforeLoad({ context }) { - routerAuthCheck(Route, context); - }, + staticData: { authorizationRules: [ { diff --git a/packages/portal/src/routes/marathon/index.tsx b/packages/portal/src/routes/marathon/index.tsx index 5fc8642f..1c360e4a 100644 --- a/packages/portal/src/routes/marathon/index.tsx +++ b/packages/portal/src/routes/marathon/index.tsx @@ -13,7 +13,6 @@ import { MarathonViewerFragment, } from "#elements/viewers/marathon/MarathonViewer.js"; import { graphql } from "#graphql/index.js"; -import { routerAuthCheck } from "#tools/routerAuthCheck.js"; const marathonOverviewPageDocument = graphql( /* GraphQL */ ` @@ -67,9 +66,7 @@ function MarathonOverviewPage() { export const Route = createFileRoute("/marathon/")({ component: MarathonOverviewPage, - beforeLoad({ context }) { - routerAuthCheck(Route, context); - }, + staticData: { authorizationRules: [ { diff --git a/packages/portal/src/routes/notifications/$notificationId/index.tsx b/packages/portal/src/routes/notifications/$notificationId/index.tsx index cc2a4200..899b6242 100644 --- a/packages/portal/src/routes/notifications/$notificationId/index.tsx +++ b/packages/portal/src/routes/notifications/$notificationId/index.tsx @@ -9,7 +9,6 @@ import { NotificationDeliveriesTable } from "#elements/tables/notification/Notif import { NotificationViewer } from "#elements/viewers/notification/NotificationViewer.js"; import { graphql } from "#graphql/index.js"; import { useQueryStatusWatcher } from "#hooks/useQueryStatusWatcher.js"; -import { routerAuthCheck } from "#tools/routerAuthCheck.js"; const notificationViewerDocument = graphql( /* GraphQL */ ` @@ -61,7 +60,6 @@ export const Route = createFileRoute("/notifications/$notificationId/")({ await context.urqlClient.query(notificationViewerDocument, { uuid: notificationId, }); - routerAuthCheck(Route, context); }, staticData: { authorizationRules: [ diff --git a/packages/portal/src/routes/notifications/$notificationId/manage.tsx b/packages/portal/src/routes/notifications/$notificationId/manage.tsx index cc7c3e6c..2bafe55b 100644 --- a/packages/portal/src/routes/notifications/$notificationId/manage.tsx +++ b/packages/portal/src/routes/notifications/$notificationId/manage.tsx @@ -6,7 +6,6 @@ import { SingleNotificationFragment } from "#documents/notification.ts"; import { ManageNotificationForm } from "#elements/forms/notification/manage/ManageNotificationForm.js"; import { graphql } from "#graphql/index.js"; import { useQueryStatusWatcher } from "#hooks/useQueryStatusWatcher.js"; -import { routerAuthCheck } from "#tools/routerAuthCheck.js"; const notificationManagerDocument = graphql( /* GraphQL */ ` @@ -102,7 +101,6 @@ export const Route = createFileRoute("/notifications/$notificationId/manage")({ await context.urqlClient.query(notificationManagerDocument, { uuid: notificationId, }); - routerAuthCheck(Route, context); }, staticData: { authorizationRules: [ diff --git a/packages/portal/src/routes/notifications/create.tsx b/packages/portal/src/routes/notifications/create.tsx index 5a90470b..b8b65fca 100644 --- a/packages/portal/src/routes/notifications/create.tsx +++ b/packages/portal/src/routes/notifications/create.tsx @@ -5,7 +5,6 @@ import { createFileRoute } from "@tanstack/react-router"; import { AccessLevel } from "@ukdanceblue/common"; import { CreateNotificationForm } from "#elements/forms/notification/create/CreateNotificationForm"; -import { routerAuthCheck } from "#tools/routerAuthCheck.js"; function CreateNotificationPage() { return ( @@ -18,9 +17,7 @@ function CreateNotificationPage() { export const Route = createFileRoute("/notifications/create")({ component: CreateNotificationPage, - beforeLoad({ context }) { - routerAuthCheck(Route, context); - }, + staticData: { authorizationRules: [ { diff --git a/packages/portal/src/routes/notifications/index.tsx b/packages/portal/src/routes/notifications/index.tsx index dc435099..72563047 100644 --- a/packages/portal/src/routes/notifications/index.tsx +++ b/packages/portal/src/routes/notifications/index.tsx @@ -4,7 +4,6 @@ import { AccessLevel } from "@ukdanceblue/common"; import { Button, Flex, Typography } from "antd"; import { NotificationsTable } from "#elements/tables/notification/NotificationsTable.js"; -import { routerAuthCheck } from "#tools/routerAuthCheck.js"; function ListNotificationsPage() { return ( @@ -24,9 +23,7 @@ function ListNotificationsPage() { export const Route = createFileRoute("/notifications/")({ component: ListNotificationsPage, - beforeLoad({ context }) { - routerAuthCheck(Route, context); - }, + staticData: { authorizationRules: [ { diff --git a/packages/portal/src/routes/people/$personId/edit.tsx b/packages/portal/src/routes/people/$personId/edit.tsx index 47b16ee0..0c61d542 100644 --- a/packages/portal/src/routes/people/$personId/edit.tsx +++ b/packages/portal/src/routes/people/$personId/edit.tsx @@ -6,7 +6,6 @@ import { PersonEditorFragment, TeamNameFragment } from "#documents/person.ts"; import { PersonEditor } from "#elements/forms/person/edit/PersonEditor.js"; import { graphql } from "#graphql/index.js"; import { useQueryStatusWatcher } from "#hooks/useQueryStatusWatcher.js"; -import { routerAuthCheck } from "#tools/routerAuthCheck.js"; const viewPersonPageDocument = graphql( /* GraphQL */ ` @@ -53,7 +52,6 @@ export const Route = createFileRoute("/people/$personId/edit")({ component: EditPersonPage, async beforeLoad({ context, params: { personId } }) { await context.urqlClient.query(viewPersonPageDocument, { uuid: personId }); - routerAuthCheck(Route, context); }, staticData: { authorizationRules: [ diff --git a/packages/portal/src/routes/people/$personId/index.tsx b/packages/portal/src/routes/people/$personId/index.tsx index 9894b4c1..bd857b3b 100644 --- a/packages/portal/src/routes/people/$personId/index.tsx +++ b/packages/portal/src/routes/people/$personId/index.tsx @@ -10,7 +10,6 @@ import { import { graphql } from "#graphql/index.js"; import { useLoginState } from "#hooks/useLoginState.js"; import { useQueryStatusWatcher } from "#hooks/useQueryStatusWatcher.js"; -import { routerAuthCheck } from "#tools/routerAuthCheck.js"; const viewPersonPageDocument = graphql( /* GraphQL */ ` @@ -52,7 +51,6 @@ export const Route = createFileRoute("/people/$personId/")({ component: ViewPersonPage, async beforeLoad({ context, params: { personId } }) { await context.urqlClient.query(viewPersonPageDocument, { uuid: personId }); - routerAuthCheck(Route, context); }, staticData: { authorizationRules: [ diff --git a/packages/portal/src/routes/people/bulk.tsx b/packages/portal/src/routes/people/bulk.tsx index 9979ace9..2585f052 100644 --- a/packages/portal/src/routes/people/bulk.tsx +++ b/packages/portal/src/routes/people/bulk.tsx @@ -6,7 +6,6 @@ import { createFileRoute } from "@tanstack/react-router"; import { AccessLevel } from "@ukdanceblue/common"; import { BulkPersonCreator } from "#elements/forms/person/create/BulkPersonCreator.js"; -import { routerAuthCheck } from "#tools/routerAuthCheck.js"; function BulkCreatePersonPage() { return ( @@ -19,9 +18,7 @@ function BulkCreatePersonPage() { export const Route = createFileRoute("/people/bulk")({ component: BulkCreatePersonPage, - beforeLoad({ context }) { - routerAuthCheck(Route, context); - }, + staticData: { authorizationRules: [ { diff --git a/packages/portal/src/routes/people/create.tsx b/packages/portal/src/routes/people/create.tsx index e287e557..51388b67 100644 --- a/packages/portal/src/routes/people/create.tsx +++ b/packages/portal/src/routes/people/create.tsx @@ -2,7 +2,6 @@ import { createFileRoute } from "@tanstack/react-router"; import { AccessLevel } from "@ukdanceblue/common"; import { PersonCreator } from "#elements/forms/person/create/PersonCreator.js"; -import { routerAuthCheck } from "#tools/routerAuthCheck.js"; function CreatePersonPage() { return ( @@ -15,9 +14,7 @@ function CreatePersonPage() { export const Route = createFileRoute("/people/create")({ component: CreatePersonPage, - beforeLoad({ context }) { - routerAuthCheck(Route, context); - }, + staticData: { authorizationRules: [ { diff --git a/packages/portal/src/routes/people/index.tsx b/packages/portal/src/routes/people/index.tsx index 31122877..6d2e3f79 100644 --- a/packages/portal/src/routes/people/index.tsx +++ b/packages/portal/src/routes/people/index.tsx @@ -1,15 +1,13 @@ import { PlusOutlined, UploadOutlined } from "@ant-design/icons"; import { createFileRoute, Link } from "@tanstack/react-router"; -import { AccessLevel } from "@ukdanceblue/common"; +import { AccessLevel, Action } from "@ukdanceblue/common"; import { Button, Flex, Typography } from "antd"; import { PeopleTable } from "#elements/tables/PeopleTable.js"; import { useAuthorizationRequirement } from "#hooks/useLoginState.js"; -import { routerAuthCheck } from "#tools/routerAuthCheck.js"; function ListPeoplePage() { - const canCreate = useAuthorizationRequirement(AccessLevel.Admin); - const canBulkCreate = useAuthorizationRequirement(AccessLevel.SuperAdmin); + const canCreate = useAuthorizationRequirement(Action.Create, "PersonNode"); return ( <> @@ -17,18 +15,18 @@ function ListPeoplePage() { People
{canCreate && ( - - - - )} - {canBulkCreate && ( - - - + <> + + + + + + + )}
@@ -39,9 +37,7 @@ function ListPeoplePage() { export const Route = createFileRoute("/people/")({ component: ListPeoplePage, - beforeLoad({ context }) { - routerAuthCheck(Route, context); - }, + staticData: { authorizationRules: [ { diff --git a/packages/portal/src/routes/teams/$teamId/_layout.tsx b/packages/portal/src/routes/teams/$teamId/_layout.tsx index bc70ee58..fa45b9d8 100644 --- a/packages/portal/src/routes/teams/$teamId/_layout.tsx +++ b/packages/portal/src/routes/teams/$teamId/_layout.tsx @@ -7,7 +7,6 @@ import { useQuery } from "urql"; import { teamPageDocument } from "#documents/team.js"; import { TeamViewer } from "#elements/viewers/team/TeamViewer.js"; import { useQueryStatusWatcher } from "#hooks/useQueryStatusWatcher.js"; -import { routerAuthCheck } from "#tools/routerAuthCheck.js"; function ViewTeamPage() { const { teamId: teamUuid } = Route.useParams(); @@ -40,7 +39,6 @@ export const Route = createFileRoute("/teams/$teamId/_layout")({ await context.urqlClient.query(teamPageDocument, { teamUuid: teamId, }); - routerAuthCheck(Route, context); }, staticData: { authorizationRules: [ diff --git a/packages/portal/src/routes/teams/$teamId/_layout/fundraising.tsx b/packages/portal/src/routes/teams/$teamId/_layout/fundraising.tsx index d9eee2ad..6f7e9235 100644 --- a/packages/portal/src/routes/teams/$teamId/_layout/fundraising.tsx +++ b/packages/portal/src/routes/teams/$teamId/_layout/fundraising.tsx @@ -1,6 +1,7 @@ import { createFileRoute } from "@tanstack/react-router"; import { AccessLevel, + Action, CommitteeIdentifier, CommitteeRole, } from "@ukdanceblue/common"; @@ -16,7 +17,6 @@ import { graphql } from "#graphql/index.js"; import { useListQuery } from "#hooks/useListQuery.js"; import { useAuthorizationRequirement } from "#hooks/useLoginState.js"; import { useQueryStatusWatcher } from "#hooks/useQueryStatusWatcher.js"; -import { routerAuthCheck } from "#tools/routerAuthCheck.js"; const ViewTeamFundraisingDocument = graphql( /* GraphQL */ ` @@ -102,13 +102,8 @@ function ViewTeamFundraising() { useState(false); const canSetSolicitationCode = useAuthorizationRequirement( - { - committeeIdentifier: CommitteeIdentifier.fundraisingCommittee, - minCommitteeRole: CommitteeRole.Coordinator, - }, - { - accessLevel: AccessLevel.Admin, - } + Action.Update, + "SolicitationCodeNode" ); const listQuery = useListQuery( @@ -296,9 +291,7 @@ function ViewTeamFundraising() { export const Route = createFileRoute("/teams/$teamId/_layout/fundraising")({ component: ViewTeamFundraising, - beforeLoad({ context }) { - routerAuthCheck(Route, context); - }, + staticData: { authorizationRules: [ { diff --git a/packages/portal/src/routes/teams/$teamId/_layout/index.tsx b/packages/portal/src/routes/teams/$teamId/_layout/index.tsx index 713a48a1..11c50362 100644 --- a/packages/portal/src/routes/teams/$teamId/_layout/index.tsx +++ b/packages/portal/src/routes/teams/$teamId/_layout/index.tsx @@ -1,14 +1,14 @@ import { createFileRoute, Link } from "@tanstack/react-router"; -import { AccessLevel } from "@ukdanceblue/common"; +import { AccessLevel, Action } from "@ukdanceblue/common"; import { Button, Flex } from "antd"; import { useAuthorizationRequirement } from "#hooks/useLoginState.js"; -import { routerAuthCheck } from "#tools/routerAuthCheck.js"; function ViewTeamPage() { - const canSeePoints = useAuthorizationRequirement({ - accessLevel: AccessLevel.CommitteeChairOrCoordinator, - }); + const canSeePoints = useAuthorizationRequirement( + Action.Read, + "PointEntryNode" + ); return ( @@ -44,7 +44,4 @@ export const Route = createFileRoute("/teams/$teamId/_layout/")({ }, ], }, - beforeLoad({ context }) { - routerAuthCheck(Route, context); - }, }); diff --git a/packages/portal/src/routes/teams/$teamId/_layout/points.tsx b/packages/portal/src/routes/teams/$teamId/_layout/points.tsx index 06932291..1ff11a4b 100644 --- a/packages/portal/src/routes/teams/$teamId/_layout/points.tsx +++ b/packages/portal/src/routes/teams/$teamId/_layout/points.tsx @@ -1,5 +1,5 @@ import { createFileRoute } from "@tanstack/react-router"; -import { AccessLevel, CommitteeIdentifier } from "@ukdanceblue/common"; +import { AccessLevel, Action, CommitteeIdentifier } from "@ukdanceblue/common"; import { Flex } from "antd"; import { useQuery } from "urql"; @@ -8,19 +8,13 @@ import { PointEntryCreator } from "#elements/forms/point-entry/create/PointEntry import { PointEntryTable } from "#elements/tables/point-entry/PointEntryTable.js"; import { useAuthorizationRequirement } from "#hooks/useLoginState.js"; import { useQueryStatusWatcher } from "#hooks/useQueryStatusWatcher.js"; -import { routerAuthCheck } from "#tools/routerAuthCheck.js"; function ViewTeamPoints() { const { teamId: teamUuid } = Route.useParams(); const canAddPoints = useAuthorizationRequirement( - { - committeeIdentifier: CommitteeIdentifier.viceCommittee, - accessLevel: AccessLevel.CommitteeChairOrCoordinator, - }, - { - accessLevel: AccessLevel.Admin, - } + Action.Create, + "PointEntryNode" ); const [{ fetching, data, error }, refetch] = useQuery({ @@ -58,9 +52,7 @@ function ViewTeamPoints() { export const Route = createFileRoute("/teams/$teamId/_layout/points")({ component: ViewTeamPoints, - beforeLoad({ context }) { - routerAuthCheck(Route, context); - }, + staticData: { authorizationRules: [ { diff --git a/packages/portal/src/routes/teams/$teamId/edit.tsx b/packages/portal/src/routes/teams/$teamId/edit.tsx index 383895ab..50740d9f 100644 --- a/packages/portal/src/routes/teams/$teamId/edit.tsx +++ b/packages/portal/src/routes/teams/$teamId/edit.tsx @@ -6,7 +6,6 @@ import { TeamEditorFragment } from "#documents/team.ts"; import { TeamEditor } from "#elements/forms/team/edit/TeamEditor.js"; import { graphql } from "#graphql/index.js"; import { useQueryStatusWatcher } from "#hooks/useQueryStatusWatcher.js"; -import { routerAuthCheck } from "#tools/routerAuthCheck.js"; const viewTeamPageDocument = graphql( /* GraphQL */ ` @@ -45,7 +44,6 @@ export const Route = createFileRoute("/teams/$teamId/edit")({ component: EditTeamPage, async beforeLoad({ context, params: { teamId } }) { await context.urqlClient.query(viewTeamPageDocument, { uuid: teamId }); - routerAuthCheck(Route, context); }, staticData: { authorizationRules: [ diff --git a/packages/portal/src/routes/teams/bulk.tsx b/packages/portal/src/routes/teams/bulk.tsx index f4f6f734..6b3bd3ad 100644 --- a/packages/portal/src/routes/teams/bulk.tsx +++ b/packages/portal/src/routes/teams/bulk.tsx @@ -6,7 +6,6 @@ import { } from "@ukdanceblue/common"; import { BulkTeamCreator } from "#elements/forms/team/create/BulkTeamCreator.js"; -import { routerAuthCheck } from "#tools/routerAuthCheck.js"; function BulkCreateTeamPage() { return ( @@ -19,9 +18,7 @@ function BulkCreateTeamPage() { export const Route = createFileRoute("/teams/bulk")({ component: BulkCreateTeamPage, - beforeLoad({ context }) { - routerAuthCheck(Route, context); - }, + staticData: { authorizationRules: [ { diff --git a/packages/portal/src/routes/teams/create.tsx b/packages/portal/src/routes/teams/create.tsx index 5de4b0fb..9947625e 100644 --- a/packages/portal/src/routes/teams/create.tsx +++ b/packages/portal/src/routes/teams/create.tsx @@ -6,7 +6,6 @@ import { AccessLevel, CommitteeIdentifier } from "@ukdanceblue/common"; import { useMarathon } from "#config/marathonContext.js"; import { TeamCreator } from "#elements/forms/team/create/TeamCreator.js"; -import { routerAuthCheck } from "#tools/routerAuthCheck.js"; function CreateTeamPage() { const selectedMarathon = useMarathon(); @@ -20,9 +19,7 @@ function CreateTeamPage() { export const Route = createFileRoute("/teams/create")({ component: CreateTeamPage, - beforeLoad({ context }) { - routerAuthCheck(Route, context); - }, + staticData: { authorizationRules: [ { diff --git a/packages/portal/src/routes/teams/index.tsx b/packages/portal/src/routes/teams/index.tsx index d554eef1..fdba2f34 100644 --- a/packages/portal/src/routes/teams/index.tsx +++ b/packages/portal/src/routes/teams/index.tsx @@ -2,6 +2,7 @@ import { PlusOutlined, UploadOutlined } from "@ant-design/icons"; import { createFileRoute, Link } from "@tanstack/react-router"; import { AccessLevel, + Action, CommitteeIdentifier, CommitteeRole, SortDirection, @@ -16,7 +17,6 @@ import { graphql } from "#graphql/index"; import { useListQuery } from "#hooks/useListQuery"; import { useAuthorizationRequirement } from "#hooks/useLoginState.js"; import { useQueryStatusWatcher } from "#hooks/useQueryStatusWatcher"; -import { routerAuthCheck } from "#tools/routerAuthCheck.js"; const teamsTableQueryDocument = graphql( /* GraphQL */ ` @@ -83,17 +83,7 @@ export function ListTeamsPage() { <>

Teams

- {useAuthorizationRequirement( - { - accessLevel: AccessLevel.Admin, - }, - { - committeeIdentifiers: [ - CommitteeIdentifier.dancerRelationsCommittee, - ], - minCommitteeRole: CommitteeRole.Coordinator, - } - ) && ( + {useAuthorizationRequirement(Action.Create, "TeamNode") && (
) : ( - router.history.back, - Link, - go: () => refineGoFunction, - parse: () => refineParseFunction, - }} - authProvider={authProvider} - options={{ - projectId: "DqkUbD-wpgLRK-UO3SFV", - title: { - icon: DanceBlue Logo, - text: "DanceBlue Portal", - }, - }} - accessControlProvider={accessControlProvider} - resources={refineResources} - > - + <> + {import.meta.env.MODE === "development" && } - + ); } export function Main() { return ( - - - - - - - - - - - - - + ); } - -function refineGoFunction({ hash, options, query, to, type }: GoConfig) { - router - .navigate({ - to, - search: options?.keepQuery ? router.state.location.search : query, - hash: options?.keepHash ? router.state.location.hash : hash, - replace: type === "replace", - }) - .catch(console.error); -} - -function refineParseFunction(): ReturnType { - const matchesByLength = router.state.matches.toSorted( - ({ fullPath: fullPathA }, { fullPath: fullPathB }) => - String(fullPathB).length - String(fullPathA).length - ); - const longestMatch = matchesByLength[0]; - - let id: string | undefined; - if (longestMatch) { - const idParams = Object.keys(longestMatch.params as object).filter((key) => - key.toLowerCase().endsWith("id") - ); - if (idParams.length === 1) { - id = (longestMatch.params as Record)[ - idParams[0]! - ]; - } - } - - return { - pathname: router.state.location.pathname, - params: longestMatch?.params, - id, - }; -} diff --git a/packages/portal/src/routes/__root.tsx b/packages/portal/src/routes/__root.tsx index 98c57343..df3f7546 100644 --- a/packages/portal/src/routes/__root.tsx +++ b/packages/portal/src/routes/__root.tsx @@ -1,7 +1,19 @@ import { MoonOutlined, SettingOutlined, SunOutlined } from "@ant-design/icons"; -import { AuthPage } from "@refinedev/antd"; -import { useLogin } from "@refinedev/core"; -import { createRootRouteWithContext, Outlet } from "@tanstack/react-router"; +import { AuthPage, useNotificationProvider } from "@refinedev/antd"; +import type { + Action, + GoConfig, + IResourceItem, + ParseFunction, +} from "@refinedev/core"; +import { Refine, useLogin } from "@refinedev/core"; +import { + createRootRouteWithContext, + Link, + Outlet, + useRouter, + useRouterState, +} from "@tanstack/react-router"; import { Button, ConfigProvider, Layout, Menu, notification } from "antd"; import type { useAppProps } from "antd/es/app/context.js"; import { lazy, Suspense, useContext, useState } from "react"; @@ -9,6 +21,14 @@ import type { Client as UrqlClient } from "urql"; import watermark from "#assets/watermark.svg"; import { themeConfigContext } from "#config/antThemeConfig.ts"; +import { authProvider } from "#config/refine/authentication.ts"; +import { accessControlProvider } from "#config/refine/authorization.ts"; +import { dataProvider } from "#config/refine/data.ts"; +import { + findResourceAction, + refineResources, +} from "#config/refine/resources.tsx"; +import { SessionStorageKeys } from "#config/storage.tsx"; import { Sider } from "#elements/components/sider/index.tsx"; import { ConfigModal } from "#elements/singletons/ConfigModal.tsx"; import { refreshLoginState, useLoginState } from "#hooks/useLoginState.js"; @@ -71,53 +91,71 @@ function RootComponent() { return ( <> - ( - DanceBlue Logo - )} - render={({ items, logout }) => ( - + ( + + DanceBlue Logo + + )} + render={({ items, logout }) => ( + <> + {items} + } + onClick={() => setSettinsOpen(true)} + > + Settings + + + ) : ( + + ) + } + onClick={() => setDark(!dark)} + > + {dark ? "Light" : "Dark"} Theme + + {logout} + + )} + /> + + + {sessionStorage.getItem(SessionStorageKeys.Masquerade)?.trim() && ( +
- {items} - } - onClick={() => setSettinsOpen(true)} - > - Settings - - - ) : ( - - ) - } - onClick={() => setDark(!dark)} - > - {dark ? "Light" : "Dark"} Theme - - {logout} - + You are currently masquerading as another user +
)} - /> - - - + + + +
@@ -130,8 +168,37 @@ function RootComponent() { ); } +function RootWithRefine() { + const router = useRouter(); + + return ( + router.history.back, + Link, + go: useRefineGoFunction, + parse: useRefineParseFunction, + }} + authProvider={authProvider} + options={{ + projectId: "DqkUbD-wpgLRK-UO3SFV", + title: { + icon: DanceBlue Logo, + text: "DanceBlue Portal", + }, + }} + accessControlProvider={accessControlProvider} + resources={refineResources} + > + + + ); +} + export const Route = createRootRouteWithContext()({ - component: RootComponent, + component: RootWithRefine, beforeLoad: async ({ context }) => { const loginState = await refreshLoginState(context.urqlClient); loginState.mapErr((error) => @@ -142,3 +209,53 @@ export const Route = createRootRouteWithContext()({ authorizationRules: null, }, }); + +function useRefineGoFunction() { + const router = useRouter(); + return ({ hash, options, query, to, type }: GoConfig) => { + router + .navigate({ + to, + search: options?.keepQuery ? router.state.location.search : query, + hash: options?.keepHash ? router.state.location.hash : hash, + replace: type === "replace", + }) + .catch(console.error); + }; +} + +function useRefineParseFunction(): ParseFunction { + const state = useRouterState(); + return () => { + const matchesByLength = state.matches.toSorted( + ({ fullPath: fullPathA }, { fullPath: fullPathB }) => + String(fullPathB).length - String(fullPathA).length + ); + const longestMatch = matchesByLength[0]; + + let action: Action | undefined; + let resource: IResourceItem | undefined; + let id: string | undefined; + if (longestMatch) { + const idParams = Object.keys(longestMatch.params as object).filter( + (key) => key.toLowerCase().endsWith("id") + ); + if (idParams.length === 1) { + id = (longestMatch.params as Record)[ + idParams[0]! + ]; + } + ({ action, resource } = findResourceAction( + String(longestMatch.fullPath) + )); + } + + return { + pathname: state.location.pathname, + params: longestMatch?.params, + id, + action, + resource, + }; + }; +} diff --git a/packages/portal/src/routes/admin/logs.tsx b/packages/portal/src/routes/admin/logs.tsx index 4a67937e..92586dfa 100644 --- a/packages/portal/src/routes/admin/logs.tsx +++ b/packages/portal/src/routes/admin/logs.tsx @@ -1,5 +1,4 @@ import { createFileRoute } from "@tanstack/react-router"; -import { AccessLevel } from "@ukdanceblue/common"; import { useQuery } from "urql"; import { LogViewer } from "#elements/viewers/admin/LogViewer.js"; @@ -31,11 +30,4 @@ export const Route = createFileRoute("/admin/logs")({ async beforeLoad({ context }) { await context.urqlClient.query(logsPageDocument, {}); }, - staticData: { - authorizationRules: [ - { - accessLevel: AccessLevel.SuperAdmin, - }, - ], - }, }); diff --git a/packages/portal/src/routes/config/index.tsx b/packages/portal/src/routes/config/index.tsx index 781bccb7..f1d610d2 100644 --- a/packages/portal/src/routes/config/index.tsx +++ b/packages/portal/src/routes/config/index.tsx @@ -1,5 +1,4 @@ import { createFileRoute } from "@tanstack/react-router"; -import { AccessLevel } from "@ukdanceblue/common"; import { Button, Collapse, Divider, Flex, Form, Input, Space } from "antd"; import { useState } from "react"; @@ -125,12 +124,4 @@ function ConfigPage() { export const Route = createFileRoute("/config/")({ component: ConfigPage, - - staticData: { - authorizationRules: [ - { - accessLevel: AccessLevel.Admin, - }, - ], - }, }); diff --git a/packages/portal/src/routes/events/$eventId/edit.tsx b/packages/portal/src/routes/events/$eventId/edit.tsx index c04c834f..24634fd5 100644 --- a/packages/portal/src/routes/events/$eventId/edit.tsx +++ b/packages/portal/src/routes/events/$eventId/edit.tsx @@ -1,5 +1,4 @@ import { createFileRoute } from "@tanstack/react-router"; -import { AccessLevel, CommitteeRole } from "@ukdanceblue/common"; import { useQuery } from "urql"; import { EventEditor } from "#elements/forms/event/edit/EventEditor.js"; @@ -44,14 +43,4 @@ export const Route = createFileRoute("/events/$eventId/edit")({ async beforeLoad({ context, params: { eventId } }) { await context.urqlClient.query(viewEventPageDocument, { uuid: eventId }); }, - staticData: { - authorizationRules: [ - { - accessLevel: AccessLevel.Admin, - }, - { - minCommitteeRole: CommitteeRole.Chair, - }, - ], - }, }); diff --git a/packages/portal/src/routes/events/$eventId/index.tsx b/packages/portal/src/routes/events/$eventId/index.tsx index 6b5035b7..3228e5a9 100644 --- a/packages/portal/src/routes/events/$eventId/index.tsx +++ b/packages/portal/src/routes/events/$eventId/index.tsx @@ -1,5 +1,4 @@ import { createFileRoute } from "@tanstack/react-router"; -import { AccessLevel } from "@ukdanceblue/common"; import { useQuery } from "urql"; import { @@ -46,11 +45,4 @@ export const Route = createFileRoute("/events/$eventId/")({ async beforeLoad({ context, params: { eventId } }) { await context.urqlClient.query(viewEventPageDocument, { uuid: eventId }); }, - staticData: { - authorizationRules: [ - { - accessLevel: AccessLevel.CommitteeChairOrCoordinator, - }, - ], - }, }); diff --git a/packages/portal/src/routes/events/create.tsx b/packages/portal/src/routes/events/create.tsx index 149d141a..cc7c5e79 100644 --- a/packages/portal/src/routes/events/create.tsx +++ b/packages/portal/src/routes/events/create.tsx @@ -1,5 +1,4 @@ import { createFileRoute } from "@tanstack/react-router"; -import { AccessLevel, CommitteeRole } from "@ukdanceblue/common"; import { EventCreator } from "#elements/forms/event/create/EventCreator.js"; @@ -15,15 +14,4 @@ function EventsCreate() { export const Route = createFileRoute("/events/create")({ component: EventsCreate, - - staticData: { - authorizationRules: [ - { - accessLevel: AccessLevel.Admin, - }, - { - minCommitteeRole: CommitteeRole.Chair, - }, - ], - }, }); diff --git a/packages/portal/src/routes/events/index.tsx b/packages/portal/src/routes/events/index.tsx index 77d3a652..12f0c5f8 100644 --- a/packages/portal/src/routes/events/index.tsx +++ b/packages/portal/src/routes/events/index.tsx @@ -1,6 +1,5 @@ import { PlusOutlined } from "@ant-design/icons"; import { createFileRoute, Link } from "@tanstack/react-router"; -import { AccessLevel } from "@ukdanceblue/common"; import { Button, Flex, Typography } from "antd"; import { EventsTable } from "#elements/tables/EventsTable.js"; @@ -23,12 +22,4 @@ function Events() { export const Route = createFileRoute("/events/")({ component: Events, - - staticData: { - authorizationRules: [ - { - accessLevel: AccessLevel.CommitteeChairOrCoordinator, - }, - ], - }, }); diff --git a/packages/portal/src/routes/feed/index.tsx b/packages/portal/src/routes/feed/index.tsx index bbb42f80..ef75e49f 100644 --- a/packages/portal/src/routes/feed/index.tsx +++ b/packages/portal/src/routes/feed/index.tsx @@ -1,9 +1,5 @@ import { createFileRoute } from "@tanstack/react-router"; -import { - AccessLevel, - CommitteeRole, - dateTimeFromSomething, -} from "@ukdanceblue/common"; +import { dateTimeFromSomething } from "@ukdanceblue/common"; import { Button, Card, @@ -222,14 +218,4 @@ export const Route = createFileRoute("/feed/")({ async beforeLoad({ context }) { await context.urqlClient.query(feedPageDocument, {}); }, - staticData: { - authorizationRules: [ - { - accessLevel: AccessLevel.Admin, - }, - { - minCommitteeRole: CommitteeRole.Chair, - }, - ], - }, }); diff --git a/packages/portal/src/routes/fundraising/$entryId/edit.tsx b/packages/portal/src/routes/fundraising/$entryId/edit.tsx index 3cd98fda..d3c4a07e 100644 --- a/packages/portal/src/routes/fundraising/$entryId/edit.tsx +++ b/packages/portal/src/routes/fundraising/$entryId/edit.tsx @@ -1,22 +1,9 @@ import { createFileRoute, useParams } from "@tanstack/react-router"; -import { AccessLevel, CommitteeIdentifier } from "@ukdanceblue/common"; import { FundraisingEntryEditor } from "#elements/forms/fundraising-entry/edit/FundraisingEntryEditor.tsx"; export const Route = createFileRoute("/fundraising/$entryId/edit")({ component: RouteComponent, - - staticData: { - authorizationRules: [ - { - accessLevel: AccessLevel.Admin, - }, - { - accessLevel: AccessLevel.CommitteeChairOrCoordinator, - committeeIdentifier: CommitteeIdentifier.fundraisingCommittee, - }, - ], - }, }); function RouteComponent() { diff --git a/packages/portal/src/routes/fundraising/dbfunds.tsx b/packages/portal/src/routes/fundraising/dbfunds.tsx index 448b8afe..429c78c5 100644 --- a/packages/portal/src/routes/fundraising/dbfunds.tsx +++ b/packages/portal/src/routes/fundraising/dbfunds.tsx @@ -1,6 +1,5 @@ import { DownloadOutlined } from "@ant-design/icons"; import { createFileRoute } from "@tanstack/react-router"; -import { AccessLevel, CommitteeIdentifier } from "@ukdanceblue/common"; import { Button, Flex, Table, Typography } from "antd"; import { DateTime } from "luxon"; import { useMemo, useState } from "react"; @@ -226,16 +225,4 @@ function DbFundsViewer() { export const Route = createFileRoute("/fundraising/dbfunds")({ component: DbFundsViewer, - - staticData: { - authorizationRules: [ - { - accessLevel: AccessLevel.Admin, - }, - { - accessLevel: AccessLevel.CommitteeChairOrCoordinator, - committeeIdentifier: CommitteeIdentifier.fundraisingCommittee, - }, - ], - }, }); diff --git a/packages/portal/src/routes/fundraising/ddn/$ddnId/index.tsx b/packages/portal/src/routes/fundraising/ddn/$ddnId/index.tsx index e7822e69..2dffc01b 100644 --- a/packages/portal/src/routes/fundraising/ddn/$ddnId/index.tsx +++ b/packages/portal/src/routes/fundraising/ddn/$ddnId/index.tsx @@ -1,10 +1,5 @@ import { createFileRoute, useParams } from "@tanstack/react-router"; -import { - AccessLevel, - CommitteeIdentifier, - CommitteeRole, - stringifyDDNBatchType, -} from "@ukdanceblue/common"; +import { stringifyDDNBatchType } from "@ukdanceblue/common"; import { Descriptions } from "antd"; import { useQuery } from "urql"; @@ -14,17 +9,6 @@ import { useQueryStatusWatcher } from "#hooks/useQueryStatusWatcher"; export const Route = createFileRoute("/fundraising/ddn/$ddnId/")({ component: RouteComponent, - staticData: { - authorizationRules: [ - { - minCommitteeRole: CommitteeRole.Coordinator, - committeeIdentifiers: [CommitteeIdentifier.fundraisingCommittee], - }, - { - accessLevel: AccessLevel.Admin, - }, - ], - }, }); const ViewDdnFragment = graphql(/* GraphQL */ ` diff --git a/packages/portal/src/routes/fundraising/ddn/index.tsx b/packages/portal/src/routes/fundraising/ddn/index.tsx index e4578927..c73af47e 100644 --- a/packages/portal/src/routes/fundraising/ddn/index.tsx +++ b/packages/portal/src/routes/fundraising/ddn/index.tsx @@ -1,27 +1,12 @@ import { UploadOutlined } from "@ant-design/icons"; import { createFileRoute, Link } from "@tanstack/react-router"; -import { - AccessLevel, - CommitteeIdentifier, - CommitteeRole, -} from "@ukdanceblue/common"; +import {} from "@ukdanceblue/common"; import { Button, Flex } from "antd"; import { DDNTable } from "#elements/tables/fundraising/DDNTable"; export const Route = createFileRoute("/fundraising/ddn/")({ component: RouteComponent, - staticData: { - authorizationRules: [ - { - minCommitteeRole: CommitteeRole.Coordinator, - committeeIdentifiers: [CommitteeIdentifier.fundraisingCommittee], - }, - { - accessLevel: AccessLevel.Admin, - }, - ], - }, }); function RouteComponent() { diff --git a/packages/portal/src/routes/fundraising/ddn/upload.tsx b/packages/portal/src/routes/fundraising/ddn/upload.tsx index 20d8bce4..1471ecc1 100644 --- a/packages/portal/src/routes/fundraising/ddn/upload.tsx +++ b/packages/portal/src/routes/fundraising/ddn/upload.tsx @@ -1,9 +1,4 @@ import { createFileRoute } from "@tanstack/react-router"; -import { - AccessLevel, - CommitteeIdentifier, - CommitteeRole, -} from "@ukdanceblue/common"; import { DDNUploadForm } from "#elements/forms/ddn/DDNUploadForm"; @@ -23,15 +18,4 @@ function DDNSpreadsheetUploader() { export const Route = createFileRoute("/fundraising/ddn/upload")({ component: DDNSpreadsheetUploader, - staticData: { - authorizationRules: [ - { - minCommitteeRole: CommitteeRole.Coordinator, - committeeIdentifiers: [CommitteeIdentifier.fundraisingCommittee], - }, - { - accessLevel: AccessLevel.Admin, - }, - ], - }, }); diff --git a/packages/portal/src/routes/fundraising/index.tsx b/packages/portal/src/routes/fundraising/index.tsx index e0845c7c..d5f5b7fa 100644 --- a/packages/portal/src/routes/fundraising/index.tsx +++ b/packages/portal/src/routes/fundraising/index.tsx @@ -1,6 +1,5 @@ import { BarsOutlined, FileOutlined, UploadOutlined } from "@ant-design/icons"; import { createFileRoute, Link } from "@tanstack/react-router"; -import { AccessLevel, CommitteeIdentifier } from "@ukdanceblue/common"; import { Button, Flex } from "antd"; import { useState } from "react"; import { useQuery } from "urql"; @@ -45,17 +44,6 @@ const ViewTeamFundraisingDocument = graphql( export const Route = createFileRoute("/fundraising/")({ component: RouteComponent, - staticData: { - authorizationRules: [ - { - accessLevel: AccessLevel.Admin, - }, - { - accessLevel: AccessLevel.CommitteeChairOrCoordinator, - committeeIdentifier: CommitteeIdentifier.fundraisingCommittee, - }, - ], - }, }); function RouteComponent() { diff --git a/packages/portal/src/routes/fundraising/solicitation-code/$solicitationCodeId/index.tsx b/packages/portal/src/routes/fundraising/solicitation-code/$solicitationCodeId/index.tsx index 051ede4f..a849d916 100644 --- a/packages/portal/src/routes/fundraising/solicitation-code/$solicitationCodeId/index.tsx +++ b/packages/portal/src/routes/fundraising/solicitation-code/$solicitationCodeId/index.tsx @@ -2,7 +2,6 @@ import { MinusCircleOutlined } from "@ant-design/icons"; import { useForm } from "@refinedev/antd"; import type { HttpError } from "@refinedev/core"; import { createFileRoute, useParams } from "@tanstack/react-router"; -import { AccessLevel, CommitteeIdentifier } from "@ukdanceblue/common"; import { Button, Flex, Form, Input } from "antd"; import { useMutation, useQuery } from "urql"; @@ -29,18 +28,6 @@ export const Route = createFileRoute( "/fundraising/solicitation-code/$solicitationCodeId/" )({ component: RouteComponent, - - staticData: { - authorizationRules: [ - { - accessLevel: AccessLevel.Admin, - }, - { - accessLevel: AccessLevel.CommitteeChairOrCoordinator, - committeeIdentifier: CommitteeIdentifier.fundraisingCommittee, - }, - ], - }, }); const SolicitationCodeDocument = graphql( diff --git a/packages/portal/src/routes/fundraising/solicitation-code/create.tsx b/packages/portal/src/routes/fundraising/solicitation-code/create.tsx index 42dd5be6..b803f61d 100644 --- a/packages/portal/src/routes/fundraising/solicitation-code/create.tsx +++ b/packages/portal/src/routes/fundraising/solicitation-code/create.tsx @@ -2,7 +2,6 @@ import { useForm } from "@refinedev/antd"; import type { HttpError } from "@refinedev/core"; import { useBack } from "@refinedev/core"; import { createFileRoute } from "@tanstack/react-router"; -import { AccessLevel, CommitteeIdentifier } from "@ukdanceblue/common"; import { Button, Flex, Form, Input, InputNumber } from "antd"; import { createSolicitationCodeDocument } from "#documents/solicitationCode.ts"; @@ -10,18 +9,6 @@ import type { ResultOf, VariablesOf } from "#graphql/index"; export const Route = createFileRoute("/fundraising/solicitation-code/create")({ component: RouteComponent, - - staticData: { - authorizationRules: [ - { - accessLevel: AccessLevel.Admin, - }, - { - accessLevel: AccessLevel.CommitteeChairOrCoordinator, - committeeIdentifier: CommitteeIdentifier.fundraisingCommittee, - }, - ], - }, }); function RouteComponent() { diff --git a/packages/portal/src/routes/fundraising/solicitation-code/index.tsx b/packages/portal/src/routes/fundraising/solicitation-code/index.tsx index b2671fd8..02aebb4e 100644 --- a/packages/portal/src/routes/fundraising/solicitation-code/index.tsx +++ b/packages/portal/src/routes/fundraising/solicitation-code/index.tsx @@ -1,24 +1,11 @@ import { PlusOutlined } from "@ant-design/icons"; import { createFileRoute, Link } from "@tanstack/react-router"; -import { AccessLevel, CommitteeIdentifier } from "@ukdanceblue/common"; import { Button, Flex } from "antd"; import { SolicitationCodeTable } from "#elements/tables/fundraising/SolicitationCodeTable"; export const Route = createFileRoute("/fundraising/solicitation-code/")({ component: RouteComponent, - - staticData: { - authorizationRules: [ - { - accessLevel: AccessLevel.Admin, - }, - { - accessLevel: AccessLevel.CommitteeChairOrCoordinator, - committeeIdentifier: CommitteeIdentifier.fundraisingCommittee, - }, - ], - }, }); function RouteComponent() { diff --git a/packages/portal/src/routes/images/$.tsx b/packages/portal/src/routes/images/$.tsx index aa8d6616..99382a8e 100644 --- a/packages/portal/src/routes/images/$.tsx +++ b/packages/portal/src/routes/images/$.tsx @@ -1,6 +1,5 @@ import { PlusOutlined } from "@ant-design/icons"; import { createFileRoute, useNavigate } from "@tanstack/react-router"; -import { AccessLevel } from "@ukdanceblue/common"; import { Button, Flex, Typography } from "antd"; import { useState } from "react"; @@ -44,12 +43,4 @@ function ListImagesPage() { export const Route = createFileRoute("/images/$")({ component: ListImagesPage, - - staticData: { - authorizationRules: [ - { - accessLevel: AccessLevel.CommitteeChairOrCoordinator, - }, - ], - }, }); diff --git a/packages/portal/src/routes/images/index.tsx b/packages/portal/src/routes/images/index.tsx index 0e492fb0..940e4540 100644 --- a/packages/portal/src/routes/images/index.tsx +++ b/packages/portal/src/routes/images/index.tsx @@ -1,6 +1,5 @@ import { PlusOutlined } from "@ant-design/icons"; import { createFileRoute, useNavigate } from "@tanstack/react-router"; -import { AccessLevel } from "@ukdanceblue/common"; import { Button, Flex, Typography } from "antd"; import { useState } from "react"; @@ -9,14 +8,6 @@ import { ImagesTable } from "#elements/tables/ImagesTable.js"; export const Route = createFileRoute("/images/")({ component: RouteComponent, - - staticData: { - authorizationRules: [ - { - accessLevel: AccessLevel.CommitteeChairOrCoordinator, - }, - ], - }, }); function RouteComponent() { diff --git a/packages/portal/src/routes/marathon/$marathonId/edit.tsx b/packages/portal/src/routes/marathon/$marathonId/edit.tsx index 441e1e30..ad9194d4 100644 --- a/packages/portal/src/routes/marathon/$marathonId/edit.tsx +++ b/packages/portal/src/routes/marathon/$marathonId/edit.tsx @@ -1,5 +1,4 @@ import { createFileRoute } from "@tanstack/react-router"; -import { AccessLevel } from "@ukdanceblue/common"; import { EditMarathonForm } from "#elements/forms/marathon/EditMarathonForm.js"; @@ -13,12 +12,4 @@ function EditMarathonPage() { export const Route = createFileRoute("/marathon/$marathonId/edit")({ component: EditMarathonPage, - - staticData: { - authorizationRules: [ - { - accessLevel: AccessLevel.SuperAdmin, - }, - ], - }, }); diff --git a/packages/portal/src/routes/marathon/$marathonId/hours/$hourId/index.tsx b/packages/portal/src/routes/marathon/$marathonId/hours/$hourId/index.tsx index cfc8b0d3..b5445a86 100644 --- a/packages/portal/src/routes/marathon/$marathonId/hours/$hourId/index.tsx +++ b/packages/portal/src/routes/marathon/$marathonId/hours/$hourId/index.tsx @@ -1,7 +1,7 @@ import { useForm } from "@tanstack/react-form"; import { createFileRoute } from "@tanstack/react-router"; import { useNavigate } from "@tanstack/react-router"; -import { AccessLevel, dateTimeFromSomething } from "@ukdanceblue/common"; +import { dateTimeFromSomething } from "@ukdanceblue/common"; import { Editable, useEditor } from "@wysimark/react"; import { Button, Input } from "antd"; import type { DateTime } from "luxon"; @@ -219,11 +219,4 @@ export const Route = createFileRoute("/marathon/$marathonId/hours/$hourId/")({ marathonHourUuid: hourId, }); }, - staticData: { - authorizationRules: [ - { - accessLevel: AccessLevel.CommitteeChairOrCoordinator, - }, - ], - }, }); diff --git a/packages/portal/src/routes/marathon/$marathonId/hours/add.tsx b/packages/portal/src/routes/marathon/$marathonId/hours/add.tsx index 12480f50..688ac5ef 100644 --- a/packages/portal/src/routes/marathon/$marathonId/hours/add.tsx +++ b/packages/portal/src/routes/marathon/$marathonId/hours/add.tsx @@ -1,7 +1,6 @@ import { useForm } from "@tanstack/react-form"; import { createFileRoute } from "@tanstack/react-router"; import { useNavigate } from "@tanstack/react-router"; -import { AccessLevel, CommitteeIdentifier } from "@ukdanceblue/common"; import { Editable, useEditor } from "@wysimark/react"; import { Button, Input } from "antd"; import type { DateTime } from "luxon"; @@ -183,16 +182,4 @@ function AddMarathonHourPage() { export const Route = createFileRoute("/marathon/$marathonId/hours/add")({ component: AddMarathonHourPage, - - staticData: { - authorizationRules: [ - { - accessLevel: AccessLevel.CommitteeChairOrCoordinator, - committeeIdentifier: CommitteeIdentifier.programmingCommittee, - }, - { - accessLevel: AccessLevel.Admin, - }, - ], - }, }); diff --git a/packages/portal/src/routes/marathon/$marathonId/index.tsx b/packages/portal/src/routes/marathon/$marathonId/index.tsx index 8a718be3..072cd86e 100644 --- a/packages/portal/src/routes/marathon/$marathonId/index.tsx +++ b/packages/portal/src/routes/marathon/$marathonId/index.tsx @@ -1,5 +1,4 @@ import { createFileRoute } from "@tanstack/react-router"; -import { AccessLevel } from "@ukdanceblue/common"; import { useQuery } from "urql"; import { @@ -41,11 +40,4 @@ export const Route = createFileRoute("/marathon/$marathonId/")({ marathonUuid: marathonId, }); }, - staticData: { - authorizationRules: [ - { - accessLevel: AccessLevel.CommitteeChairOrCoordinator, - }, - ], - }, }); diff --git a/packages/portal/src/routes/marathon/create.tsx b/packages/portal/src/routes/marathon/create.tsx index 1b8ef007..5c1281c7 100644 --- a/packages/portal/src/routes/marathon/create.tsx +++ b/packages/portal/src/routes/marathon/create.tsx @@ -1,5 +1,4 @@ import { createFileRoute } from "@tanstack/react-router"; -import { AccessLevel } from "@ukdanceblue/common"; import { CreateMarathonForm } from "#elements/forms/marathon/CreateMarathonForm.js"; @@ -13,12 +12,4 @@ export function CreateMarathonPage() { export const Route = createFileRoute("/marathon/create")({ component: CreateMarathonPage, - - staticData: { - authorizationRules: [ - { - accessLevel: AccessLevel.SuperAdmin, - }, - ], - }, }); diff --git a/packages/portal/src/routes/marathon/index.tsx b/packages/portal/src/routes/marathon/index.tsx index 1c360e4a..6dce63b2 100644 --- a/packages/portal/src/routes/marathon/index.tsx +++ b/packages/portal/src/routes/marathon/index.tsx @@ -1,6 +1,5 @@ import { PlusOutlined } from "@ant-design/icons"; import { createFileRoute, Link } from "@tanstack/react-router"; -import { AccessLevel } from "@ukdanceblue/common"; import { Button, Empty, Flex } from "antd"; import { useQuery } from "urql"; @@ -66,12 +65,4 @@ function MarathonOverviewPage() { export const Route = createFileRoute("/marathon/")({ component: MarathonOverviewPage, - - staticData: { - authorizationRules: [ - { - accessLevel: AccessLevel.Committee, - }, - ], - }, }); diff --git a/packages/portal/src/routes/notifications/$notificationId/index.tsx b/packages/portal/src/routes/notifications/$notificationId/index.tsx index 899b6242..bcd3ee23 100644 --- a/packages/portal/src/routes/notifications/$notificationId/index.tsx +++ b/packages/portal/src/routes/notifications/$notificationId/index.tsx @@ -1,6 +1,5 @@ import { SendOutlined } from "@ant-design/icons"; import { createFileRoute, Link } from "@tanstack/react-router"; -import { AccessLevel } from "@ukdanceblue/common"; import { Button, Flex, Typography } from "antd"; import { useQuery } from "urql"; @@ -61,11 +60,4 @@ export const Route = createFileRoute("/notifications/$notificationId/")({ uuid: notificationId, }); }, - staticData: { - authorizationRules: [ - { - accessLevel: AccessLevel.CommitteeChairOrCoordinator, - }, - ], - }, }); diff --git a/packages/portal/src/routes/notifications/$notificationId/manage.tsx b/packages/portal/src/routes/notifications/$notificationId/manage.tsx index 2bafe55b..cd0c02e8 100644 --- a/packages/portal/src/routes/notifications/$notificationId/manage.tsx +++ b/packages/portal/src/routes/notifications/$notificationId/manage.tsx @@ -1,5 +1,4 @@ import { createFileRoute } from "@tanstack/react-router"; -import { AccessLevel } from "@ukdanceblue/common"; import { useQuery } from "urql"; import { SingleNotificationFragment } from "#documents/notification.ts"; @@ -102,11 +101,4 @@ export const Route = createFileRoute("/notifications/$notificationId/manage")({ uuid: notificationId, }); }, - staticData: { - authorizationRules: [ - { - accessLevel: AccessLevel.CommitteeChairOrCoordinator, - }, - ], - }, }); diff --git a/packages/portal/src/routes/notifications/create.tsx b/packages/portal/src/routes/notifications/create.tsx index b8b65fca..a91bcf1b 100644 --- a/packages/portal/src/routes/notifications/create.tsx +++ b/packages/portal/src/routes/notifications/create.tsx @@ -2,7 +2,6 @@ console.log( "This page allows you to create a notification. The outline of the notification will be presented at the top of the page and who it will go to. You can send this to a specific person or group of people or everyone. After clicking create you will go to the “notification overview” page. On this page you will then click manage delivery. This is where you will schedule the notification or just send the notification. " ); import { createFileRoute } from "@tanstack/react-router"; -import { AccessLevel } from "@ukdanceblue/common"; import { CreateNotificationForm } from "#elements/forms/notification/create/CreateNotificationForm"; @@ -17,12 +16,4 @@ function CreateNotificationPage() { export const Route = createFileRoute("/notifications/create")({ component: CreateNotificationPage, - - staticData: { - authorizationRules: [ - { - accessLevel: AccessLevel.CommitteeChairOrCoordinator, - }, - ], - }, }); diff --git a/packages/portal/src/routes/notifications/index.tsx b/packages/portal/src/routes/notifications/index.tsx index 72563047..aeb2dfab 100644 --- a/packages/portal/src/routes/notifications/index.tsx +++ b/packages/portal/src/routes/notifications/index.tsx @@ -1,6 +1,5 @@ import { PlusOutlined } from "@ant-design/icons"; import { createFileRoute, Link } from "@tanstack/react-router"; -import { AccessLevel } from "@ukdanceblue/common"; import { Button, Flex, Typography } from "antd"; import { NotificationsTable } from "#elements/tables/notification/NotificationsTable.js"; @@ -23,12 +22,4 @@ function ListNotificationsPage() { export const Route = createFileRoute("/notifications/")({ component: ListNotificationsPage, - - staticData: { - authorizationRules: [ - { - accessLevel: AccessLevel.CommitteeChairOrCoordinator, - }, - ], - }, }); diff --git a/packages/portal/src/routes/people/$personId/edit.tsx b/packages/portal/src/routes/people/$personId/edit.tsx index 0c61d542..0c45ab24 100644 --- a/packages/portal/src/routes/people/$personId/edit.tsx +++ b/packages/portal/src/routes/people/$personId/edit.tsx @@ -1,5 +1,4 @@ import { createFileRoute } from "@tanstack/react-router"; -import { AccessLevel } from "@ukdanceblue/common"; import { useQuery } from "urql"; import { PersonEditorFragment, TeamNameFragment } from "#documents/person.ts"; @@ -53,11 +52,4 @@ export const Route = createFileRoute("/people/$personId/edit")({ async beforeLoad({ context, params: { personId } }) { await context.urqlClient.query(viewPersonPageDocument, { uuid: personId }); }, - staticData: { - authorizationRules: [ - { - accessLevel: AccessLevel.Admin, - }, - ], - }, }); diff --git a/packages/portal/src/routes/people/$personId/index.tsx b/packages/portal/src/routes/people/$personId/index.tsx index bd857b3b..40ceaafc 100644 --- a/packages/portal/src/routes/people/$personId/index.tsx +++ b/packages/portal/src/routes/people/$personId/index.tsx @@ -1,6 +1,5 @@ import { createFileRoute } from "@tanstack/react-router"; import { useParams } from "@tanstack/react-router"; -import { AccessLevel } from "@ukdanceblue/common"; import { useQuery } from "urql"; import { @@ -52,11 +51,4 @@ export const Route = createFileRoute("/people/$personId/")({ async beforeLoad({ context, params: { personId } }) { await context.urqlClient.query(viewPersonPageDocument, { uuid: personId }); }, - staticData: { - authorizationRules: [ - { - accessLevel: AccessLevel.Committee, - }, - ], - }, }); diff --git a/packages/portal/src/routes/people/bulk.tsx b/packages/portal/src/routes/people/bulk.tsx index 2585f052..72611e40 100644 --- a/packages/portal/src/routes/people/bulk.tsx +++ b/packages/portal/src/routes/people/bulk.tsx @@ -3,7 +3,6 @@ console.log( ); import { createFileRoute } from "@tanstack/react-router"; -import { AccessLevel } from "@ukdanceblue/common"; import { BulkPersonCreator } from "#elements/forms/person/create/BulkPersonCreator.js"; @@ -18,12 +17,4 @@ function BulkCreatePersonPage() { export const Route = createFileRoute("/people/bulk")({ component: BulkCreatePersonPage, - - staticData: { - authorizationRules: [ - { - accessLevel: AccessLevel.SuperAdmin, - }, - ], - }, }); diff --git a/packages/portal/src/routes/people/create.tsx b/packages/portal/src/routes/people/create.tsx index 51388b67..e7bbaa42 100644 --- a/packages/portal/src/routes/people/create.tsx +++ b/packages/portal/src/routes/people/create.tsx @@ -1,5 +1,4 @@ import { createFileRoute } from "@tanstack/react-router"; -import { AccessLevel } from "@ukdanceblue/common"; import { PersonCreator } from "#elements/forms/person/create/PersonCreator.js"; @@ -14,12 +13,4 @@ function CreatePersonPage() { export const Route = createFileRoute("/people/create")({ component: CreatePersonPage, - - staticData: { - authorizationRules: [ - { - accessLevel: AccessLevel.Admin, - }, - ], - }, }); diff --git a/packages/portal/src/routes/people/index.tsx b/packages/portal/src/routes/people/index.tsx index 3b89e72d..a9dd14a5 100644 --- a/packages/portal/src/routes/people/index.tsx +++ b/packages/portal/src/routes/people/index.tsx @@ -1,6 +1,5 @@ import { PlusOutlined, UploadOutlined } from "@ant-design/icons"; import { createFileRoute, Link } from "@tanstack/react-router"; -import { AccessLevel, Action } from "@ukdanceblue/common"; import { Button, Flex, Typography } from "antd"; import { PeopleTable } from "#elements/tables/PeopleTable.js"; @@ -37,12 +36,4 @@ function ListPeoplePage() { export const Route = createFileRoute("/people/")({ component: ListPeoplePage, - - staticData: { - authorizationRules: [ - { - accessLevel: AccessLevel.CommitteeChairOrCoordinator, - }, - ], - }, }); diff --git a/packages/portal/src/routes/teams/$teamId/_layout.tsx b/packages/portal/src/routes/teams/$teamId/_layout.tsx index fa45b9d8..6031ef77 100644 --- a/packages/portal/src/routes/teams/$teamId/_layout.tsx +++ b/packages/portal/src/routes/teams/$teamId/_layout.tsx @@ -1,6 +1,5 @@ import { createFileRoute } from "@tanstack/react-router"; import { Outlet } from "@tanstack/react-router"; -import { AccessLevel } from "@ukdanceblue/common"; import { Flex } from "antd"; import { useQuery } from "urql"; @@ -40,11 +39,4 @@ export const Route = createFileRoute("/teams/$teamId/_layout")({ teamUuid: teamId, }); }, - staticData: { - authorizationRules: [ - { - accessLevel: AccessLevel.UKY, - }, - ], - }, }); diff --git a/packages/portal/src/routes/teams/$teamId/_layout/fundraising.tsx b/packages/portal/src/routes/teams/$teamId/_layout/fundraising.tsx index bd79d21c..5a886997 100644 --- a/packages/portal/src/routes/teams/$teamId/_layout/fundraising.tsx +++ b/packages/portal/src/routes/teams/$teamId/_layout/fundraising.tsx @@ -1,5 +1,4 @@ import { createFileRoute } from "@tanstack/react-router"; -import { AccessLevel, Action } from "@ukdanceblue/common"; import { AutoComplete, Button, Card, Flex, Form, Space } from "antd"; import { useState } from "react"; import { useMutation, useQuery } from "urql"; @@ -286,12 +285,4 @@ function ViewTeamFundraising() { export const Route = createFileRoute("/teams/$teamId/_layout/fundraising")({ component: ViewTeamFundraising, - - staticData: { - authorizationRules: [ - { - accessLevel: AccessLevel.UKY, - }, - ], - }, }); diff --git a/packages/portal/src/routes/teams/$teamId/_layout/index.tsx b/packages/portal/src/routes/teams/$teamId/_layout/index.tsx index 14d54d0b..90ebeeb1 100644 --- a/packages/portal/src/routes/teams/$teamId/_layout/index.tsx +++ b/packages/portal/src/routes/teams/$teamId/_layout/index.tsx @@ -1,5 +1,4 @@ import { createFileRoute, Link } from "@tanstack/react-router"; -import { AccessLevel, Action } from "@ukdanceblue/common"; import { Button, Flex } from "antd"; import { useAuthorizationRequirement } from "#hooks/useLoginState.js"; @@ -34,11 +33,4 @@ function ViewTeamPage() { export const Route = createFileRoute("/teams/$teamId/_layout/")({ component: ViewTeamPage, - staticData: { - authorizationRules: [ - { - accessLevel: AccessLevel.UKY, - }, - ], - }, }); diff --git a/packages/portal/src/routes/teams/$teamId/_layout/points.tsx b/packages/portal/src/routes/teams/$teamId/_layout/points.tsx index 7eabd7b5..0029a0cf 100644 --- a/packages/portal/src/routes/teams/$teamId/_layout/points.tsx +++ b/packages/portal/src/routes/teams/$teamId/_layout/points.tsx @@ -1,5 +1,4 @@ import { createFileRoute } from "@tanstack/react-router"; -import { AccessLevel, Action } from "@ukdanceblue/common"; import { Flex } from "antd"; import { useQuery } from "urql"; @@ -49,12 +48,4 @@ function ViewTeamPoints() { export const Route = createFileRoute("/teams/$teamId/_layout/points")({ component: ViewTeamPoints, - - staticData: { - authorizationRules: [ - { - accessLevel: AccessLevel.Committee, - }, - ], - }, }); diff --git a/packages/portal/src/routes/teams/$teamId/edit.tsx b/packages/portal/src/routes/teams/$teamId/edit.tsx index 50740d9f..d68a6934 100644 --- a/packages/portal/src/routes/teams/$teamId/edit.tsx +++ b/packages/portal/src/routes/teams/$teamId/edit.tsx @@ -1,5 +1,4 @@ import { createFileRoute } from "@tanstack/react-router"; -import { AccessLevel, CommitteeIdentifier } from "@ukdanceblue/common"; import { useQuery } from "urql"; import { TeamEditorFragment } from "#documents/team.ts"; @@ -45,15 +44,4 @@ export const Route = createFileRoute("/teams/$teamId/edit")({ async beforeLoad({ context, params: { teamId } }) { await context.urqlClient.query(viewTeamPageDocument, { uuid: teamId }); }, - staticData: { - authorizationRules: [ - { - accessLevel: AccessLevel.CommitteeChairOrCoordinator, - committeeIdentifier: CommitteeIdentifier.viceCommittee, - }, - { - accessLevel: AccessLevel.Admin, - }, - ], - }, }); diff --git a/packages/portal/src/routes/teams/bulk.tsx b/packages/portal/src/routes/teams/bulk.tsx index 6b3bd3ad..a4b9d806 100644 --- a/packages/portal/src/routes/teams/bulk.tsx +++ b/packages/portal/src/routes/teams/bulk.tsx @@ -1,9 +1,4 @@ import { createFileRoute } from "@tanstack/react-router"; -import { - AccessLevel, - CommitteeIdentifier, - CommitteeRole, -} from "@ukdanceblue/common"; import { BulkTeamCreator } from "#elements/forms/team/create/BulkTeamCreator.js"; @@ -18,16 +13,4 @@ function BulkCreateTeamPage() { export const Route = createFileRoute("/teams/bulk")({ component: BulkCreateTeamPage, - - staticData: { - authorizationRules: [ - { - accessLevel: AccessLevel.SuperAdmin, - }, - { - committeeIdentifier: CommitteeIdentifier.dancerRelationsCommittee, - minCommitteeRole: CommitteeRole.Chair, - }, - ], - }, }); diff --git a/packages/portal/src/routes/teams/create.tsx b/packages/portal/src/routes/teams/create.tsx index 9947625e..6202ed36 100644 --- a/packages/portal/src/routes/teams/create.tsx +++ b/packages/portal/src/routes/teams/create.tsx @@ -2,7 +2,6 @@ console.log( "It's easy… type the name of the team and put spirit. Choose if the team has been a team before or a new team and then save it. You can add members by typing their name in the add member box. At the bottom of the page you will see the options for fundraising and spirit points. This is where you will give the points that they can see and $$$$. The fundraising amounts will be imported from DB funds… team captains will be able to delegate the money to the right person. " ); import { createFileRoute } from "@tanstack/react-router"; -import { AccessLevel, CommitteeIdentifier } from "@ukdanceblue/common"; import { useMarathon } from "#config/marathonContext.js"; import { TeamCreator } from "#elements/forms/team/create/TeamCreator.js"; @@ -19,16 +18,4 @@ function CreateTeamPage() { export const Route = createFileRoute("/teams/create")({ component: CreateTeamPage, - - staticData: { - authorizationRules: [ - { - accessLevel: AccessLevel.CommitteeChairOrCoordinator, - committeeIdentifier: CommitteeIdentifier.viceCommittee, - }, - { - accessLevel: AccessLevel.Admin, - }, - ], - }, }); diff --git a/packages/portal/src/routes/teams/index.tsx b/packages/portal/src/routes/teams/index.tsx index 3c3d5d6e..e4d34c70 100644 --- a/packages/portal/src/routes/teams/index.tsx +++ b/packages/portal/src/routes/teams/index.tsx @@ -1,6 +1,6 @@ import { PlusOutlined, UploadOutlined } from "@ant-design/icons"; import { createFileRoute, Link } from "@tanstack/react-router"; -import { AccessLevel, SortDirection } from "@ukdanceblue/common"; +import { SortDirection } from "@ukdanceblue/common"; import { Button, Flex } from "antd"; import { useQuery } from "urql"; @@ -105,12 +105,4 @@ export function ListTeamsPage() { export const Route = createFileRoute("/teams/")({ component: ListTeamsPage, - - staticData: { - authorizationRules: [ - { - accessLevel: AccessLevel.Committee, - }, - ], - }, }); diff --git a/packages/server/src/lib/graphqlSchema.ts b/packages/server/src/lib/graphqlSchema.ts index 538bbc53..840b057b 100644 --- a/packages/server/src/lib/graphqlSchema.ts +++ b/packages/server/src/lib/graphqlSchema.ts @@ -132,9 +132,10 @@ export default await buildSchema({ } else { [action, , field] = rule; subject = rule[1] as string; - if (!field) { - field = "."; - } + } + + if (!field) { + field = "."; } let id: string | undefined = undefined; @@ -198,9 +199,9 @@ export default await buildSchema({ }, field ); - logger.debug("Checking access control", { - rule: ability.rulesFor(action, subject as never, field), - ok, + logger.trace("Checking access control", { + rule: ability.relevantRuleFor(action, subject as never, field), + authorized: ok, id, action, subject, diff --git a/packages/server/src/lib/logging/standardLogging.ts b/packages/server/src/lib/logging/standardLogging.ts index 59376048..dbeb1b87 100644 --- a/packages/server/src/lib/logging/standardLogging.ts +++ b/packages/server/src/lib/logging/standardLogging.ts @@ -1,5 +1,7 @@ import { Container } from "@freshgum/typedi"; +import { debugStringify } from "@ukdanceblue/common"; import type winston from "winston"; +import type { Logform } from "winston"; import { createLogger, format, transports } from "winston"; import { logDirToken, loggingLevelToken } from "#lib/typediTokens.js"; @@ -44,11 +46,17 @@ export const syslogColors = { const consoleTransport = new transports.Console({ format: format.combine( - format.errors(), - format.splat(), - format.simple(), format.colorize({ colors: syslogColors, + }), + format.printf(({ level, message, ...rest }: Logform.TransformableInfo) => { + const filteredRestEntries = Object.entries(rest).filter( + ([key]) => typeof key !== "symbol" + ); + + return filteredRestEntries.length > 0 + ? `${level}: ${debugStringify(message)} ${debugStringify(Object.fromEntries(filteredRestEntries), true, false)}` + : `${level}: ${debugStringify(message)}`; }) ), }); @@ -59,16 +67,12 @@ const combinedLogTransport = new transports.File({ maxFiles: 3, dirname: logDir, silent: logDir === "TEST", + format: format.json({}), }); export const logger = createLogger({ level: loggingLevel, levels: SyslogLevels, - format: format.combine( - format.splat(), - format.colorize({ level: true, message: false }), - format.json() - ), transports: [combinedLogTransport, consoleTransport], exitOnError: false, }) as StandardLogger; diff --git a/packages/server/src/lib/resolversList.ts b/packages/server/src/lib/resolversList.ts index 9ec179e3..707566cf 100644 --- a/packages/server/src/lib/resolversList.ts +++ b/packages/server/src/lib/resolversList.ts @@ -8,7 +8,6 @@ import { EventResolver } from "#resolvers/EventResolver.js"; import { FeedResolver } from "#resolvers/FeedResolver.js"; import { FundraisingAssignmentResolver } from "#resolvers/FundraisingAssignmentResolver.js"; import { FundraisingEntryResolver } from "#resolvers/FundraisingEntryResolver.js"; -import { SolicitationCodeResolver } from "#resolvers/SolicitationCodeResolver.js"; import { ImageResolver } from "#resolvers/ImageResolver.js"; import { LoginStateResolver } from "#resolvers/LoginState.js"; import { MarathonHourResolver } from "#resolvers/MarathonHourResolver.js"; @@ -23,6 +22,7 @@ import { PersonResolver } from "#resolvers/PersonResolver.js"; import { PointEntryResolver } from "#resolvers/PointEntryResolver.js"; import { PointOpportunityResolver } from "#resolvers/PointOpportunityResolver.js"; import { ReportResolver } from "#resolvers/ReportResolver.js"; +import { SolicitationCodeResolver } from "#resolvers/SolicitationCodeResolver.js"; import { TeamResolver } from "#resolvers/TeamResolver.js"; export const resolversList = [ diff --git a/packages/server/src/repositories/dailyDepartmentNotification/ddnRepositoryUtils.ts b/packages/server/src/repositories/dailyDepartmentNotification/ddnRepositoryUtils.ts index 000a49a4..08ee75af 100644 --- a/packages/server/src/repositories/dailyDepartmentNotification/ddnRepositoryUtils.ts +++ b/packages/server/src/repositories/dailyDepartmentNotification/ddnRepositoryUtils.ts @@ -155,6 +155,7 @@ export function buildDailyDepartmentNotificationWhere( } } } - where.solicitationCode = solicitationCodeWhere; + if (Object.keys(solicitationCodeWhere).length > 0) + where.solicitationCode = solicitationCodeWhere; return where; } diff --git a/packages/server/src/resolvers/TeamResolver.ts b/packages/server/src/resolvers/TeamResolver.ts index 1bb63b47..5a2fdba8 100644 --- a/packages/server/src/resolvers/TeamResolver.ts +++ b/packages/server/src/resolvers/TeamResolver.ts @@ -219,7 +219,7 @@ export class TeamResolver implements CrudResolver { return rows.map((row) => pointEntryModelToResource(row)); } - @AccessControlAuthorized("get", "TeamNode", "fundraisingTotal") + @AccessControlAuthorized("get", "TeamNode", ".fundraisingTotal") @FieldResolver(() => Float, { nullable: true }) async fundraisingTotalAmount( @Root() { id: { id } }: TeamNode From 369299e31c0c494cb48d176a915d6e0412484b5c Mon Sep 17 00:00:00 2001 From: Joshua Tag Howard Date: Sat, 7 Dec 2024 20:56:31 +0000 Subject: [PATCH 08/22] Improve subject type detection --- packages/server/src/lib/graphqlSchema.ts | 32 ++++++++++++++++--- packages/server/src/resolvers/FeedResolver.ts | 2 +- .../server/src/resolvers/PersonResolver.ts | 4 +-- 3 files changed, 30 insertions(+), 8 deletions(-) diff --git a/packages/server/src/lib/graphqlSchema.ts b/packages/server/src/lib/graphqlSchema.ts index 840b057b..99feb039 100644 --- a/packages/server/src/lib/graphqlSchema.ts +++ b/packages/server/src/lib/graphqlSchema.ts @@ -8,6 +8,7 @@ import { } from "@ukdanceblue/common/error"; import { GraphQLList, + GraphQLNonNull, GraphQLObjectType, type GraphQLResolveInfo, } from "graphql"; @@ -177,13 +178,34 @@ export default await buildSchema({ } if (!subject) { - if (returnType instanceof GraphQLObjectType) { - subject = returnType.name; - } else if (returnType instanceof GraphQLList) { - if (returnType.ofType instanceof GraphQLObjectType) { - subject = returnType.ofType.name; + let rt = returnType; + if (rt instanceof GraphQLNonNull) { + rt = rt.ofType; + } + if (rt instanceof GraphQLObjectType) { + if ( + rt + .getInterfaces() + .some(({ name }) => name === "AbstractGraphQLPaginatedResponse") + ) { + const { data } = rt.getFields(); + if (data) { + rt = data.type; + } } } + if (rt instanceof GraphQLNonNull) { + rt = rt.ofType; + } + if (rt instanceof GraphQLList) { + rt = rt.ofType; + } + if (rt instanceof GraphQLNonNull) { + rt = rt.ofType; + } + if (rt instanceof GraphQLObjectType) { + subject = rt.name; + } } if (!subject) { throw new Error( diff --git a/packages/server/src/resolvers/FeedResolver.ts b/packages/server/src/resolvers/FeedResolver.ts index b39a46c5..0126da06 100644 --- a/packages/server/src/resolvers/FeedResolver.ts +++ b/packages/server/src/resolvers/FeedResolver.ts @@ -57,7 +57,7 @@ export class FeedResolver { } @Query(() => [FeedItem], { description: "Get the active feed" }) - @AccessControlAuthorized("readActive") + @AccessControlAuthorized("readActive", "FeedNode") async feed( @Arg("limit", () => Int, { defaultValue: 10, nullable: true }) limit: number diff --git a/packages/server/src/resolvers/PersonResolver.ts b/packages/server/src/resolvers/PersonResolver.ts index 399c2610..98721aaa 100644 --- a/packages/server/src/resolvers/PersonResolver.ts +++ b/packages/server/src/resolvers/PersonResolver.ts @@ -103,7 +103,7 @@ export class PersonResolver ).promise; } - @AccessControlAuthorized("list", "PersonNode") + @AccessControlAuthorized("list") @Query(() => ListPeopleResponse, { name: "people" }) async people( @Args(() => ListPeopleArgs) args: ListPeopleArgs @@ -156,7 +156,7 @@ export class PersonResolver return ctx.authenticatedUser; } - @AccessControlAuthorized("list", "PersonNode") + @AccessControlAuthorized("list") @Query(() => [PersonNode], { name: "searchPeopleByName" }) async searchByName( @Arg("name") name: string From a0ba2650e7dd24a0648581f9f407e5884464c619 Mon Sep 17 00:00:00 2001 From: Joshua Tag Howard Date: Sun, 8 Dec 2024 00:41:12 +0000 Subject: [PATCH 09/22] Some work on events and bbnvolved --- cspell.json | 22 +- packages/common/lib/error/errorCode.ts | 2 + packages/common/lib/error/fetch.ts | 62 + packages/common/lib/error/index.ts | 1 + packages/portal/package.json | 3 + .../src/elements/tables/EventsTable.tsx | 7 - .../elements/viewers/event/EventViewer.tsx | 18 +- packages/server/package.json | 2 + .../migration.sql | 11 + packages/server/prisma/schema/event.prisma | 1 + packages/server/src/jobs/fetchPushReceipts.ts | 4 +- .../server/src/jobs/garbageCollectLogins.ts | 4 +- packages/server/src/jobs/getBBNEvents.ts | 73 + packages/server/src/jobs/housekeeping.ts | 4 +- packages/server/src/jobs/index.ts | 1 + packages/server/src/jobs/syncDbFunds.ts | 4 +- .../lib/external-apis/event/bbnvolvedApi.ts | 42 + .../event/bbnvolvedEventSchema.ts | 72 + .../lib/external-apis/externalUrlToImage.ts | 54 + .../feed}/instagramfeed.ts | 55 +- packages/server/src/lib/fundraising/test.ts | 25 - .../server/src/lib/logging/standardLogging.ts | 27 +- .../notification/ExpoPushReceiptHandler.ts | 3 +- .../src/repositories/event/EventRepository.ts | 96 +- packages/server/src/resolvers/FeedResolver.ts | 2 +- yarn.lock | 4030 ++++++++++++++--- 26 files changed, 3898 insertions(+), 727 deletions(-) create mode 100644 packages/common/lib/error/fetch.ts create mode 100644 packages/server/prisma/migrations/20241207220631_event_remote_id/migration.sql create mode 100644 packages/server/src/jobs/getBBNEvents.ts create mode 100644 packages/server/src/lib/external-apis/event/bbnvolvedApi.ts create mode 100644 packages/server/src/lib/external-apis/event/bbnvolvedEventSchema.ts create mode 100644 packages/server/src/lib/external-apis/externalUrlToImage.ts rename packages/server/src/lib/{feed-api => external-apis/feed}/instagramfeed.ts (82%) delete mode 100644 packages/server/src/lib/fundraising/test.ts diff --git a/cspell.json b/cspell.json index 5e82ad45..e9b5a173 100644 --- a/cspell.json +++ b/cspell.json @@ -17,40 +17,38 @@ ], "words": [ "autoincrement", + "bbnvolved", "catchable", "citext", "codegen", "collapsable", "cooldown", "danceblue", - "ukdanceblue", - "luxon", - "typedi", - "ukdb", - "msal", - "tada", - "xdate", - "skia", - "hstore", - "minifiable", - "refinedev", "datasource", "ddns", "errorable", "freshgum", + "hstore", "jonasmerlin", "linkblue", "luxon", + "minifiable", + "msal", "phonathon", + "refinedev", + "skia", "spinnable", + "tada", "timestamptz", "typedi", "uk", "ukdanceblue", + "ukdb", "uky", "urql", "whatwg", - "wysimark" + "wysimark", + "xdate" ], "allowCompoundWords": true, "ignoreWords": [], diff --git a/packages/common/lib/error/errorCode.ts b/packages/common/lib/error/errorCode.ts index d68a3e8a..a70efbbd 100644 --- a/packages/common/lib/error/errorCode.ts +++ b/packages/common/lib/error/errorCode.ts @@ -36,3 +36,5 @@ export const ExpoPushFailureError = Symbol("ExpoError"); export type ExpoPushFailureError = typeof ExpoPushFailureError; export const LuxonError = Symbol("LuxonError"); export type LuxonError = typeof LuxonError; +export const FetchError = Symbol("FetchError"); +export type FetchError = typeof FetchError; diff --git a/packages/common/lib/error/fetch.ts b/packages/common/lib/error/fetch.ts new file mode 100644 index 00000000..a92b4917 --- /dev/null +++ b/packages/common/lib/error/fetch.ts @@ -0,0 +1,62 @@ +import type { Result } from "ts-results-es"; +import { Err, Ok } from "ts-results-es"; + +import type { BasicError } from "./error.js"; +import { ConcreteError, toBasicError } from "./error.js"; +import { ErrorCode } from "./index.js"; + +export class FetchError extends ConcreteError { + readonly #response: Response; + readonly #url: string | undefined = undefined; + #responseText: string | undefined = undefined; + + constructor(response: Response, url?: string | URL) { + super(); + this.#response = response; + this.#url = url?.toString(); + } + + get message(): string { + return `Fetch failed with status ${this.#response.status}: ${this.#response.statusText}`; + } + + get detailedMessage(): string { + return `Fetch for ${this.#url ?? "unknown URL"} failed with status ${this.#response.status}: ${this.#response.statusText}`; + } + + async getResponseText(): Promise { + if (this.#responseText != null) { + return this.#responseText; + } else if (this.#response.bodyUsed) { + return undefined; + } else { + this.#responseText = await this.#response.clone().text(); + return this.#responseText; + } + } + + readonly expose = false; + + get tag(): ErrorCode.FetchError { + return ErrorCode.FetchError; + } + + static async safeFetch( + request: string | URL | Request, + init?: RequestInit + ): Promise> { + try { + const response = await fetch(request, init); + return !response.ok + ? Err( + new FetchError( + response, + request instanceof Request ? request.url : request + ) + ) + : Ok(response); + } catch (error) { + return Err(toBasicError(error)); + } + } +} diff --git a/packages/common/lib/error/index.ts b/packages/common/lib/error/index.ts index d3d4fecd..816bbd30 100644 --- a/packages/common/lib/error/index.ts +++ b/packages/common/lib/error/index.ts @@ -3,6 +3,7 @@ export * from "./control.js"; export * from "./direct.js"; export * from "./error.js"; export * as ErrorCode from "./errorCode.js"; +export * from "./fetch.js"; export * from "./http.js"; export * from "./luxon.js"; export * from "./option.js"; diff --git a/packages/portal/package.json b/packages/portal/package.json index 3323a490..d872a99c 100644 --- a/packages/portal/package.json +++ b/packages/portal/package.json @@ -41,6 +41,7 @@ "dependencies": { "@ant-design/icons": "^5.5.1", "@casl/ability": "^6.7.2", + "@mdxeditor/editor": "^3.20.0", "@refinedev/antd": "^5.44.0", "@refinedev/core": "^4.56.0", "@refinedev/devtools": "^1.2.10", @@ -60,6 +61,7 @@ "graphql-scalars": "^1.23.0", "lodash.isequal": "^4.5.0", "luxon": "^3.5.0", + "markdown-it": "^14.1.0", "normalize.css": "^8.0.1", "pluralize": "^8.0.0", "rc-picker": "^4.8.2", @@ -86,6 +88,7 @@ "@tanstack/router-plugin": "^1.81.9", "@types/lodash.isequal": "^4.5.8", "@types/luxon": "^3.4.2", + "@types/markdown-it": "^14", "@types/pluralize": "^0.0.33", "@types/react": "~18.3.12", "@types/react-dom": "~18.3.1", diff --git a/packages/portal/src/elements/tables/EventsTable.tsx b/packages/portal/src/elements/tables/EventsTable.tsx index a541c777..b0e8d5b6 100644 --- a/packages/portal/src/elements/tables/EventsTable.tsx +++ b/packages/portal/src/elements/tables/EventsTable.tsx @@ -17,7 +17,6 @@ const EventsTableFragment = graphql(/* GraphQL */ ` fragment EventsTableFragment on EventNode { id title - description occurrences { id interval { @@ -171,7 +170,6 @@ export const EventsTable = () => { field: sort.field as | "uuid" | "title" - | "description" | "occurrenceStart" | "summary", direction: @@ -188,11 +186,6 @@ export const EventsTable = () => { key: "title", sorter: true, }, - { - title: "Description", - dataIndex: "description", - key: "description", - }, { title: "Occurrences", dataIndex: "occurrences", diff --git a/packages/portal/src/elements/viewers/event/EventViewer.tsx b/packages/portal/src/elements/viewers/event/EventViewer.tsx index 04a4a415..9bc85f2a 100644 --- a/packages/portal/src/elements/viewers/event/EventViewer.tsx +++ b/packages/portal/src/elements/viewers/event/EventViewer.tsx @@ -8,11 +8,12 @@ import { Button, Descriptions, Flex, Image, List, Typography } from "antd"; import DescriptionsItem from "antd/es/descriptions/Item.js"; import type { Interval } from "luxon"; import { DateTime } from "luxon"; +import Markdown from "markdown-it"; import { useMemo } from "react"; import { thumbHashToDataURL } from "thumbhash"; import type { FragmentOf } from "#graphql/index.js"; -import { graphql,readFragment } from "#graphql/index.js"; +import { graphql, readFragment } from "#graphql/index.js"; import { useEventDeletePopup } from "../../components/event/EventDeletePopup"; @@ -42,6 +43,8 @@ export const EventViewerFragment = graphql(/* GraphQL */ ` } `); +const markdown = new Markdown({ linkify: true }); + export function EventViewer({ eventFragment, }: { @@ -75,6 +78,11 @@ export function EventViewer({ }, }); + const parsedDescription = useMemo(() => { + if (!eventData?.description) return undefined; + return markdown.render(eventData.description); + }, [eventData?.description]); + if (!eventData) { return ( <> @@ -156,7 +164,7 @@ export function EventViewer({ {eventData.summary} )} - {eventData.description && ( + {eventData.location && ( {eventData.location} @@ -178,9 +186,11 @@ export function EventViewer({ )} - {eventData.description && ( + {parsedDescription && ( - {eventData.description} + +
+ )} diff --git a/packages/server/package.json b/packages/server/package.json index 4f69e4d7..275ac53d 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -85,6 +85,7 @@ "thumbhash": "^0.1.1", "ts-node": "^10.9.2", "ts-results-es": "^4.2.0", + "turndown": "^7.2.0", "type-graphql": "^2.0.0-rc.2", "typescript": "^5.6.3", "utility-types": "^3.11.0", @@ -99,6 +100,7 @@ "@types/multer": "^1.4.12", "@types/pg": "^8.11.10", "@types/serve-static": "^1.15.7", + "@types/turndown": "^5.0.5", "nodemon": "^3.1.7", "prisma": "^5.22.0", "vitest": "^2.1.5" diff --git a/packages/server/prisma/migrations/20241207220631_event_remote_id/migration.sql b/packages/server/prisma/migrations/20241207220631_event_remote_id/migration.sql new file mode 100644 index 00000000..37e52664 --- /dev/null +++ b/packages/server/prisma/migrations/20241207220631_event_remote_id/migration.sql @@ -0,0 +1,11 @@ +/* + Warnings: + + - A unique constraint covering the columns `[remoteId]` on the table `Event` will be added. If there are existing duplicate values, this will fail. + +*/ +-- AlterTable +ALTER TABLE "Event" ADD COLUMN "remoteId" TEXT; + +-- CreateIndex +CREATE UNIQUE INDEX "Event_remoteId_key" ON "Event"("remoteId"); diff --git a/packages/server/prisma/schema/event.prisma b/packages/server/prisma/schema/event.prisma index 2ce8c2fa..e909e68c 100644 --- a/packages/server/prisma/schema/event.prisma +++ b/packages/server/prisma/schema/event.prisma @@ -32,6 +32,7 @@ model Event { summary String? description String? location String? + remoteId String? @unique eventImages EventImage[] eventOccurrences EventOccurrence[] pointOpportunities PointOpportunity[] diff --git a/packages/server/src/jobs/fetchPushReceipts.ts b/packages/server/src/jobs/fetchPushReceipts.ts index e7ad24e3..cd3fb17b 100644 --- a/packages/server/src/jobs/fetchPushReceipts.ts +++ b/packages/server/src/jobs/fetchPushReceipts.ts @@ -18,7 +18,7 @@ export const fetchPushReceipts = new Cron( name: "fetch-push-receipts", paused: true, catch: (error) => { - console.error("Failed to fetch push receipts", error); + logger.error("Failed to fetch push receipts", { error }); }, }, async () => { @@ -29,7 +29,7 @@ export const fetchPushReceipts = new Cron( await jobStateRepository.logCompletedJob(fetchPushReceipts); } catch (error) { - console.error("Failed to fetch push receipts", { error }); + logger.error("Failed to fetch push receipts", { error }); } } ); diff --git a/packages/server/src/jobs/garbageCollectLogins.ts b/packages/server/src/jobs/garbageCollectLogins.ts index ff2aa5ec..d90567d2 100644 --- a/packages/server/src/jobs/garbageCollectLogins.ts +++ b/packages/server/src/jobs/garbageCollectLogins.ts @@ -17,7 +17,7 @@ export const garbageCollectLoginFlowSessions = new Cron( name: "garbage-collect-login-flow-sessions", paused: true, catch: (error) => { - console.error("Failed to garbage collect old login flows", error); + logger.error("Failed to garbage collect old login flows", { error }); }, }, async () => { @@ -30,7 +30,7 @@ export const garbageCollectLoginFlowSessions = new Cron( await jobStateRepository.logCompletedJob(garbageCollectLoginFlowSessions); } catch (error) { - console.error("Failed to garbage collect old login flows", { error }); + logger.error("Failed to garbage collect old login flows", { error }); } } ); diff --git a/packages/server/src/jobs/getBBNEvents.ts b/packages/server/src/jobs/getBBNEvents.ts new file mode 100644 index 00000000..27fd4b73 --- /dev/null +++ b/packages/server/src/jobs/getBBNEvents.ts @@ -0,0 +1,73 @@ +import { Container } from "@freshgum/typedi"; +import { Cron } from "croner"; +import { AsyncResult } from "ts-results-es"; +import TurndownService from "turndown"; + +import { getBbnvolvedEvents } from "#lib/external-apis/event/bbnvolvedApi.js"; +import { logger } from "#logging/standardLogging.js"; +import type { ForeignEvent } from "#repositories/event/EventRepository.js"; +import { EventRepository } from "#repositories/event/EventRepository.js"; +import { JobStateRepository } from "#repositories/JobState.js"; + +const jobStateRepository = Container.get(JobStateRepository); + +/** + * The purpose of this job is to periodically fetch push receipts from Expo which + * are generated after a push notification is sent. This is necessary to determine + * whether each notification was successfully delivered to the target device, if + * an error occurred, or if the device needs to be unsubscribed. + */ +export const getBBNEvents = new Cron( + "0 2 * * *", + { + name: "get-bbnvolved-events", + paused: true, + catch: (error) => { + logger.error("Failed to get new bbnvolved events", error); + }, + }, + async () => { + try { + const turndownService = new TurndownService(); + const eventRepository = Container.get(EventRepository); + + const createdEvents = await new AsyncResult(getBbnvolvedEvents(15)) + .map((events): ForeignEvent[] => + events.map((event) => ({ + id: event.id, + title: event.name, + description: turndownService.turndown(event.description), + imageUrls: event.imagePath + ? [ + new URL( + `https://se-images.campuslabs.com/clink/images/${event.imagePath}?preset=med-w` + ), + ] + : [], + location: event.location, + url: new URL(`https://uky.campuslabs.com/engage/event/${event.id}`), + endsOn: event.endsOn.isValid ? event.endsOn.toISO() : undefined, + startsOn: event.startsOn.isValid + ? event.startsOn.toISO() + : undefined, + })) + ) + .andThen((events) => eventRepository.loadForeignEvents(events)).promise; + + if (createdEvents.isErr()) { + logger.error("Failed to create new events", { + error: createdEvents.error, + }); + return; + } else { + logger.info(`Created ${createdEvents.value.length} new events`); + } + } catch (error) { + logger.error("Failed to get new bbnvolved events", error); + } + } +); + +getBBNEvents.options.startAt = + await jobStateRepository.getNextJobDate(getBBNEvents); +getBBNEvents.resume(); diff --git a/packages/server/src/jobs/housekeeping.ts b/packages/server/src/jobs/housekeeping.ts index 243d73c3..02dd1a51 100644 --- a/packages/server/src/jobs/housekeeping.ts +++ b/packages/server/src/jobs/housekeeping.ts @@ -15,7 +15,7 @@ export const housekeeping = new Cron( name: "housekeeping", paused: true, catch: (error) => { - console.error("Failed to fixup user data", error); + logger.error("Failed to fixup user data", { error }); }, }, async () => { @@ -97,7 +97,7 @@ async function userHousekeeping(prisma: PrismaClient) { await jobStateRepository.logCompletedJob(housekeeping); } catch (error) { - console.error("Failed to clean up user data", { error }); + logger.error("Failed to clean up user data", { error }); } } diff --git a/packages/server/src/jobs/index.ts b/packages/server/src/jobs/index.ts index 8588d3bb..2def4fb8 100644 --- a/packages/server/src/jobs/index.ts +++ b/packages/server/src/jobs/index.ts @@ -2,6 +2,7 @@ import "./fetchPushReceipts.js"; import "./garbageCollectLogins.js"; import "./syncDbFunds.js"; import "./housekeeping.js"; +import "./getBBNEvents.js"; import { Container } from "@freshgum/typedi"; diff --git a/packages/server/src/jobs/syncDbFunds.ts b/packages/server/src/jobs/syncDbFunds.ts index 242569a4..e4d931da 100644 --- a/packages/server/src/jobs/syncDbFunds.ts +++ b/packages/server/src/jobs/syncDbFunds.ts @@ -178,7 +178,7 @@ export const syncDbFunds = new Cron( name: "sync-db-funds", paused: true, catch: (error) => { - console.error("Failed to sync DBFunds", error); + logger.error("Failed to sync DBFunds", { error }); }, }, async () => { @@ -200,7 +200,7 @@ export const syncDbFundsPast = new Cron( name: "sync-db-funds-past", paused: true, catch: (error) => { - console.error("Failed to sync DBFunds for past marathons", error); + logger.error("Failed to sync DBFunds for past marathons", { error }); }, }, async () => { diff --git a/packages/server/src/lib/external-apis/event/bbnvolvedApi.ts b/packages/server/src/lib/external-apis/event/bbnvolvedApi.ts new file mode 100644 index 00000000..f80b4df8 --- /dev/null +++ b/packages/server/src/lib/external-apis/event/bbnvolvedApi.ts @@ -0,0 +1,42 @@ +import type { BasicError } from "@ukdanceblue/common/error"; +import { FetchError } from "@ukdanceblue/common/error"; +import { DateTime } from "luxon"; +import type { Result } from "ts-results-es"; +import { Err, Ok } from "ts-results-es"; +import type { z } from "zod"; + +import { ZodError } from "#error/zod.js"; + +import { bbnvolvedEventSchema } from "./bbnvolvedEventSchema.js"; + +export async function getBbnvolvedEvents( + limit: number, + since: DateTime = DateTime.now().minus({ day: 3 }) +): Promise< + Result< + z.infer["value"], + ZodError | FetchError | BasicError + > +> { + const result = await FetchError.safeFetch( + `https://uky.campuslabs.com/engage/api/discovery/event/search?endsAfter=${encodeURIComponent(since.toISO())}&orderByField=endsOn&orderByDirection=ascending&status=Approved&take=${limit}&query=&skip=0&organizationIds%5B0%5D=192535`, + { + credentials: "include", + headers: { + "Accept": "application/json", + "Accept-Language": "en-US,en;q=0.5", + "Content-Type": "application/json", + }, + method: "GET", + } + ); + if (result.isErr()) { + return result; + } + const json = await result.value.json(); + const parsed = bbnvolvedEventSchema.safeParse(json); + + return !parsed.success + ? Err(new ZodError(parsed.error)) + : Ok(parsed.data.value); +} diff --git a/packages/server/src/lib/external-apis/event/bbnvolvedEventSchema.ts b/packages/server/src/lib/external-apis/event/bbnvolvedEventSchema.ts new file mode 100644 index 00000000..0963fe1c --- /dev/null +++ b/packages/server/src/lib/external-apis/event/bbnvolvedEventSchema.ts @@ -0,0 +1,72 @@ +import { DateTime } from "luxon"; +import { z } from "zod"; + +const eventSchema = z.object({ + id: z.string(), + institutionId: z.number(), + organizationId: z.number(), + organizationIds: z.array(z.string()), + branchId: z.number(), + branchIds: z.array(z.string()), + organizationName: z.string(), + organizationProfilePicture: z.string(), + organizationNames: z.array(z.string()), + name: z.string(), + description: z.string(), + location: z.string(), + startsOn: z.string().transform((value) => DateTime.fromISO(value)), + endsOn: z.string().transform((value) => DateTime.fromISO(value)), + imagePath: z.string().nullable(), + theme: z.string(), + categoryIds: z.array(z.string()), + categoryNames: z.array(z.string()), + benefitNames: z.array(z.string()), + visibility: z.string(), + status: z.string(), + latitude: z.string().nullable(), + longitude: z.string().nullable(), +}); + +export const bbnvolvedEventSchema = z.object({ + // "@odata.count": z.number(), + // "@search.coverage": z.null(), + // "@search.facets": z.object({ + // CategoryIds: z.array( + // z.object({ + // type: z.number(), + // from: z.null(), + // to: z.null(), + // value: z.string(), + // count: z.number(), + // }) + // ), + // BranchId: z.array( + // z.object({ + // type: z.number(), + // from: z.null(), + // to: z.null(), + // value: z.number(), + // count: z.number(), + // }) + // ), + // Theme: z.array( + // z.object({ + // type: z.number(), + // from: z.null(), + // to: z.null(), + // value: z.string(), + // count: z.number(), + // }) + // ), + // BenefitNames: z.array( + // z.object({ + // type: z.number(), + // from: z.null(), + // to: z.null(), + // value: z.string(), + // count: z.number(), + // }) + // ), + // }), + value: z.array(eventSchema), +}); diff --git a/packages/server/src/lib/external-apis/externalUrlToImage.ts b/packages/server/src/lib/external-apis/externalUrlToImage.ts new file mode 100644 index 00000000..c7705a79 --- /dev/null +++ b/packages/server/src/lib/external-apis/externalUrlToImage.ts @@ -0,0 +1,54 @@ +import type { BasicError } from "@ukdanceblue/common/error"; +import { + FetchError, + InvariantError, + toBasicError, +} from "@ukdanceblue/common/error"; +import mime from "mime"; +import sharp from "sharp"; +import type { Result } from "ts-results-es"; +import { Err, Ok } from "ts-results-es"; +import { MIMEType } from "util"; + +import { generateThumbHash } from "#lib/thumbHash.js"; + +export async function externalUrlToImage(url: URL): Promise< + Result< + { + height: number; + width: number; + mimeType: MIMEType; + thumbHash: Buffer; + url: URL; + }, + InvariantError | FetchError | BasicError + > +> { + try { + const imageResp = await FetchError.safeFetch(url); + if (imageResp.isErr()) { + return imageResp; + } + const image = sharp(await imageResp.value.arrayBuffer()); + const { + thumbHash, + width, + height, + metadata: { format }, + } = await generateThumbHash({ image }); + + if (!width || !height) { + return Err(new InvariantError("Could not find dimensions of image")); + } + + return Ok({ + height, + width, + mimeType: new MIMEType((format && mime.getType(format)) ?? "image/jpeg"), + thumbHash: Buffer.from(thumbHash), + url, + }); + } catch (error) { + return Err(toBasicError(error)); + } +} diff --git a/packages/server/src/lib/feed-api/instagramfeed.ts b/packages/server/src/lib/external-apis/feed/instagramfeed.ts similarity index 82% rename from packages/server/src/lib/feed-api/instagramfeed.ts rename to packages/server/src/lib/external-apis/feed/instagramfeed.ts index 8d083860..4c51fcd3 100644 --- a/packages/server/src/lib/feed-api/instagramfeed.ts +++ b/packages/server/src/lib/external-apis/feed/instagramfeed.ts @@ -3,21 +3,21 @@ import { ImageNode, InstagramFeedNode } from "@ukdanceblue/common"; import { BasicError, ConcreteError, + FetchError, InvariantError, } from "@ukdanceblue/common/error"; import { toBasicError } from "@ukdanceblue/common/error"; import { hash } from "crypto"; import { DateTime } from "luxon"; -import mime from "mime"; -import sharp from "sharp"; import { AsyncResult, Result } from "ts-results-es"; import { Err, Ok } from "ts-results-es"; import { z } from "zod"; import { ZodError } from "#error/zod.js"; -import { generateThumbHash } from "#lib/thumbHash.js"; import { instagramApiKeyToken } from "#lib/typediTokens.js"; +import { externalUrlToImage } from "../externalUrlToImage.js"; + const feedSchema = z.object({ data: z.array( z.object({ @@ -117,7 +117,9 @@ export class InsagramApi { async function instagramFeedItemToNode( item: InstagramFeedItem -): Promise> { +): Promise< + Result +> { const image = await instagramMediaUrlToNode(item.media_url); if (image.isErr()) { @@ -142,7 +144,9 @@ const cachedImageNodes: [string, ImageNode][] = []; async function instagramMediaUrlToNode( media_url: string | undefined -): Promise> { +): Promise< + Result +> { if (!media_url) return Ok(undefined); const cached = cachedImageNodes.find(([url]) => url === media_url); @@ -150,32 +154,17 @@ async function instagramMediaUrlToNode( const url = new URL(media_url); - const imageResp = await fetch(url); - const image = sharp(await imageResp.arrayBuffer()); - const { - thumbHash, - width, - height, - metadata: { format }, - } = await generateThumbHash({ image }); - - if (!width || !height) { - return Err(new InvariantError("Could not find dimensions of image")); - } - - const node = ImageNode.init({ - id: hash("sha1", media_url), - height, - width, - mimeType: (format && mime.getType(format)) ?? "image/jpeg", - thumbHash: Buffer.from(thumbHash).toString("base64"), - url, - }); - - if (cachedImageNodes.length >= cachedImageNodesSize) { - cachedImageNodes.shift(); - } - cachedImageNodes.push([media_url, node]); - - return Ok(node); + return new AsyncResult(externalUrlToImage(url)).map((image) => { + const node = ImageNode.init({ + id: hash("sha1", media_url), + ...image, + thumbHash: image.thumbHash.toString("base64"), + mimeType: image.mimeType.toString(), + }); + if (cachedImageNodes.length >= cachedImageNodesSize) { + cachedImageNodes.shift(); + } + cachedImageNodes.push([media_url, node]); + return node; + }).promise; } diff --git a/packages/server/src/lib/fundraising/test.ts b/packages/server/src/lib/fundraising/test.ts deleted file mode 100644 index a9513778..00000000 --- a/packages/server/src/lib/fundraising/test.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Container } from "@freshgum/typedi"; - -import { DBFundsFundraisingProvider } from "./DbFundsProvider.js"; - -const dbfunds = Container.get(DBFundsFundraisingProvider); - -dbfunds - .getTeams("DB24") - .then((result) => - console.table( - result.unwrap().sort(({ identifier: a }, { identifier: b }) => a - b) - ) - ) - // eslint-disable-next-line unicorn/prefer-top-level-await - .catch(console.error); - -dbfunds - .getTeams("DB25") - .then((result) => - console.table( - result.unwrap().sort(({ identifier: a }, { identifier: b }) => a - b) - ) - ) - // eslint-disable-next-line unicorn/prefer-top-level-await - .catch(console.error); diff --git a/packages/server/src/lib/logging/standardLogging.ts b/packages/server/src/lib/logging/standardLogging.ts index dbeb1b87..dd64455d 100644 --- a/packages/server/src/lib/logging/standardLogging.ts +++ b/packages/server/src/lib/logging/standardLogging.ts @@ -1,5 +1,6 @@ import { Container } from "@freshgum/typedi"; import { debugStringify } from "@ukdanceblue/common"; +import { ConcreteError } from "@ukdanceblue/common/error"; import type winston from "winston"; import type { Logform } from "winston"; import { createLogger, format, transports } from "winston"; @@ -49,15 +50,25 @@ const consoleTransport = new transports.Console({ format.colorize({ colors: syslogColors, }), - format.printf(({ level, message, ...rest }: Logform.TransformableInfo) => { - const filteredRestEntries = Object.entries(rest).filter( - ([key]) => typeof key !== "symbol" - ); + format.printf( + ({ level, message, error, ...rest }: Logform.TransformableInfo) => { + if (error instanceof ConcreteError) { + rest.error = `${error.tag.description} - ${error.detailedMessage} - ${error.stack}`; + } else if (error instanceof Error) { + rest.error = `${error.name} - ${error.message} - ${error.stack}`; + } else if (error != null) { + rest.error = debugStringify(error); + } - return filteredRestEntries.length > 0 - ? `${level}: ${debugStringify(message)} ${debugStringify(Object.fromEntries(filteredRestEntries), true, false)}` - : `${level}: ${debugStringify(message)}`; - }) + const filteredRestEntries = Object.entries(rest).filter( + ([key]) => typeof key !== "symbol" + ); + + return filteredRestEntries.length > 0 + ? `${level}: ${debugStringify(message)} ${debugStringify(Object.fromEntries(filteredRestEntries), true, true)}` + : `${level}: ${debugStringify(message)}`; + } + ) ), }); diff --git a/packages/server/src/lib/notification/ExpoPushReceiptHandler.ts b/packages/server/src/lib/notification/ExpoPushReceiptHandler.ts index e8d887f6..64e0896d 100644 --- a/packages/server/src/lib/notification/ExpoPushReceiptHandler.ts +++ b/packages/server/src/lib/notification/ExpoPushReceiptHandler.ts @@ -2,6 +2,7 @@ import { Service } from "@freshgum/typedi"; import type { ExpoPushReceipt } from "expo-server-sdk"; import { Expo } from "expo-server-sdk"; +import { logger } from "#lib/logging/standardLogging.js"; import { DeviceRepository } from "#repositories/device/DeviceRepository.js"; import { NotificationDeliveryRepository } from "#repositories/notificationDelivery/NotificationDeliveryRepository.js"; @@ -54,7 +55,7 @@ export class ExpoPushReceiptHandler { receipts: updateParam, }); } catch (error) { - console.error("Failed to fetch push receipts", error); + logger.error("Failed to fetch push receipts", { error }); } } diff --git a/packages/server/src/repositories/event/EventRepository.ts b/packages/server/src/repositories/event/EventRepository.ts index 9586ec57..08b340c7 100644 --- a/packages/server/src/repositories/event/EventRepository.ts +++ b/packages/server/src/repositories/event/EventRepository.ts @@ -1,5 +1,5 @@ import { Service } from "@freshgum/typedi"; -import { Prisma, PrismaClient } from "@prisma/client"; +import { Event, Prisma, PrismaClient } from "@prisma/client"; import { SortDirection } from "@ukdanceblue/common"; import type { FilterItems } from "#lib/prisma-utils/gqlFilterToPrismaFilter.js"; @@ -57,8 +57,23 @@ export type EventFilters = FilterItems< type UniqueEventParam = { id: number } | { uuid: string }; +import { ConcreteError } from "@ukdanceblue/common/error"; +import { Ok, Result } from "ts-results-es"; + +import { externalUrlToImage } from "#lib/external-apis/externalUrlToImage.js"; import { prismaToken } from "#lib/typediTokens.js"; +export interface ForeignEvent { + id: string; + title: string; + description: string; + location: string; + url: URL; + startsOn?: Date | string; + endsOn?: Date | string; + imageUrls: URL[]; +} + @Service([prismaToken]) export class EventRepository { constructor(private prisma: PrismaClient) {} @@ -191,6 +206,85 @@ export class EventRepository { }); } + loadForeignEvents( + events: ForeignEvent[] + ): Promise> { + const results: Event[] = []; + return this.prisma.$transaction(async (prisma) => { + for (const event of events) { + // eslint-disable-next-line no-await-in-loop + const existingEvent = await prisma.event.findUnique({ + where: { remoteId: event.id }, + }); + + if (existingEvent) { + continue; + } + + const images = Result.all( + // eslint-disable-next-line no-await-in-loop + await Promise.all(event.imageUrls.map(externalUrlToImage)) + ); + + if (images.isErr()) { + return images; + } + + results.push( + // eslint-disable-next-line no-await-in-loop + await prisma.event.create({ + data: { + title: event.title, + description: `${event.description}\n\n[More info](${event.url.href})`, + location: event.location, + remoteId: event.id, + eventOccurrences: + event.startsOn && event.endsOn + ? { + create: { + date: event.startsOn, + endDate: event.endsOn, + }, + } + : undefined, + eventImages: { + create: images + .unwrap() + .map(({ height, mimeType, thumbHash, url, width }) => ({ + image: { + create: { + height, + width, + thumbHash, + file: { + create: { + filename: url.pathname.split("/").at(-1) ?? "", + locationUrl: url.href, + mimeSubtypeName: mimeType.subtype, + mimeTypeName: mimeType.type, + mimeParameters: + mimeType.params.keys.length > 0 + ? { + set: [...mimeType.params.entries()].map( + ([k, v]) => `${k}=${v}` + ), + } + : undefined, + }, + }, + }, + }, + })), + }, + }, + }) + ); + } + + return Ok(results); + }); + } + updateEvent(param: UniqueEventParam, data: Prisma.EventUpdateInput) { try { return this.prisma.event.update({ diff --git a/packages/server/src/resolvers/FeedResolver.ts b/packages/server/src/resolvers/FeedResolver.ts index 0126da06..7ed096e9 100644 --- a/packages/server/src/resolvers/FeedResolver.ts +++ b/packages/server/src/resolvers/FeedResolver.ts @@ -25,7 +25,7 @@ import { } from "type-graphql"; import { FileManager } from "#files/FileManager.js"; -import { InsagramApi } from "#lib/feed-api/instagramfeed.js"; +import { InsagramApi } from "#lib/external-apis/feed/instagramfeed.js"; import { logger } from "#lib/logging/standardLogging.js"; import { feedItemModelToResource } from "#repositories/feed/feedModelToResource.js"; import { FeedRepository } from "#repositories/feed/FeedRepository.js"; diff --git a/yarn.lock b/yarn.lock index b3e3e331..462b7e33 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1736,7 +1736,7 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.10.1, @babel/runtime@npm:^7.10.4, @babel/runtime@npm:^7.11.1, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.16.7, @babel/runtime@npm:^7.18.0, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.18.6, @babel/runtime@npm:^7.20.0, @babel/runtime@npm:^7.20.7, @babel/runtime@npm:^7.21.0, @babel/runtime@npm:^7.22.5, @babel/runtime@npm:^7.23.2, @babel/runtime@npm:^7.23.6, @babel/runtime@npm:^7.23.9, @babel/runtime@npm:^7.24.4, @babel/runtime@npm:^7.24.7, @babel/runtime@npm:^7.24.8, @babel/runtime@npm:^7.25.0, @babel/runtime@npm:^7.25.7, @babel/runtime@npm:^7.6.2, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7": +"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.10.1, @babel/runtime@npm:^7.10.4, @babel/runtime@npm:^7.11.1, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.14.8, @babel/runtime@npm:^7.16.7, @babel/runtime@npm:^7.18.0, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.18.6, @babel/runtime@npm:^7.20.0, @babel/runtime@npm:^7.20.7, @babel/runtime@npm:^7.21.0, @babel/runtime@npm:^7.22.5, @babel/runtime@npm:^7.23.2, @babel/runtime@npm:^7.23.6, @babel/runtime@npm:^7.23.9, @babel/runtime@npm:^7.24.4, @babel/runtime@npm:^7.24.7, @babel/runtime@npm:^7.24.8, @babel/runtime@npm:^7.25.0, @babel/runtime@npm:^7.25.7, @babel/runtime@npm:^7.6.2, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7": version: 7.26.0 resolution: "@babel/runtime@npm:7.26.0" dependencies: @@ -1797,6 +1797,458 @@ __metadata: languageName: node linkType: hard +"@codemirror/autocomplete@npm:^6.0.0, @codemirror/autocomplete@npm:^6.3.2, @codemirror/autocomplete@npm:^6.4.0, @codemirror/autocomplete@npm:^6.7.1": + version: 6.18.3 + resolution: "@codemirror/autocomplete@npm:6.18.3" + dependencies: + "@codemirror/language": "npm:^6.0.0" + "@codemirror/state": "npm:^6.0.0" + "@codemirror/view": "npm:^6.17.0" + "@lezer/common": "npm:^1.0.0" + peerDependencies: + "@codemirror/language": ^6.0.0 + "@codemirror/state": ^6.0.0 + "@codemirror/view": ^6.0.0 + "@lezer/common": ^1.0.0 + checksum: 10/a9a684cfc4a5d5d7293993a2480c9f3a5c270b2b4a2f192d9179b4458bb30f9f299912915a1d919320c1281c8e15507cfbc76e792f42f304e5f333e4019f5723 + languageName: node + linkType: hard + +"@codemirror/commands@npm:^6.0.0, @codemirror/commands@npm:^6.1.3": + version: 6.7.1 + resolution: "@codemirror/commands@npm:6.7.1" + dependencies: + "@codemirror/language": "npm:^6.0.0" + "@codemirror/state": "npm:^6.4.0" + "@codemirror/view": "npm:^6.27.0" + "@lezer/common": "npm:^1.1.0" + checksum: 10/d742bc5976f7bab81695b9fc10d103b7cc9933fce3cc90fdd8e1d060ff480efd65d8fac3ee17f500869d323ea00c4da200d022619888da15fd65aa40e0958c93 + languageName: node + linkType: hard + +"@codemirror/lang-angular@npm:^0.1.0": + version: 0.1.3 + resolution: "@codemirror/lang-angular@npm:0.1.3" + dependencies: + "@codemirror/lang-html": "npm:^6.0.0" + "@codemirror/lang-javascript": "npm:^6.1.2" + "@codemirror/language": "npm:^6.0.0" + "@lezer/common": "npm:^1.2.0" + "@lezer/highlight": "npm:^1.0.0" + "@lezer/lr": "npm:^1.3.3" + checksum: 10/d214c71c94923c21f84103d4e5c91bcb3a43ff48eef6ca2b0eb90786bf7817daf50a3e3535e6a287ff2facec3fa615f98170c6e33dfda21aaeefb85b318caace + languageName: node + linkType: hard + +"@codemirror/lang-cpp@npm:^6.0.0": + version: 6.0.2 + resolution: "@codemirror/lang-cpp@npm:6.0.2" + dependencies: + "@codemirror/language": "npm:^6.0.0" + "@lezer/cpp": "npm:^1.0.0" + checksum: 10/bb9eba482cca80037ce30c7b193cf45eff19ccbb773764fddf2071756468ecc25aa53c777c943635054f89095b0247b9b50c339e107e41e68d34d12a7295f9a9 + languageName: node + linkType: hard + +"@codemirror/lang-css@npm:^6.0.0, @codemirror/lang-css@npm:^6.0.1, @codemirror/lang-css@npm:^6.2.0": + version: 6.3.1 + resolution: "@codemirror/lang-css@npm:6.3.1" + dependencies: + "@codemirror/autocomplete": "npm:^6.0.0" + "@codemirror/language": "npm:^6.0.0" + "@codemirror/state": "npm:^6.0.0" + "@lezer/common": "npm:^1.0.2" + "@lezer/css": "npm:^1.1.7" + checksum: 10/709994b0a787fe06ebac7a47c6a6a92c9680fe2b4479bbe2a72b27ad4d863953ad64a61b36f15098d00bd9a655bc9b3a3ecf2877354351ff873a01186fb38386 + languageName: node + linkType: hard + +"@codemirror/lang-go@npm:^6.0.0": + version: 6.0.1 + resolution: "@codemirror/lang-go@npm:6.0.1" + dependencies: + "@codemirror/autocomplete": "npm:^6.0.0" + "@codemirror/language": "npm:^6.6.0" + "@codemirror/state": "npm:^6.0.0" + "@lezer/common": "npm:^1.0.0" + "@lezer/go": "npm:^1.0.0" + checksum: 10/6e361bddb35683b225e1367807f598044b861c6858c9a011227fb73a872735985141746b3c410dcd8ef11b4c0e54819e720c5e663201a6a5e69ba8a9519fa287 + languageName: node + linkType: hard + +"@codemirror/lang-html@npm:^6.0.0, @codemirror/lang-html@npm:^6.4.0": + version: 6.4.9 + resolution: "@codemirror/lang-html@npm:6.4.9" + dependencies: + "@codemirror/autocomplete": "npm:^6.0.0" + "@codemirror/lang-css": "npm:^6.0.0" + "@codemirror/lang-javascript": "npm:^6.0.0" + "@codemirror/language": "npm:^6.4.0" + "@codemirror/state": "npm:^6.0.0" + "@codemirror/view": "npm:^6.17.0" + "@lezer/common": "npm:^1.0.0" + "@lezer/css": "npm:^1.1.0" + "@lezer/html": "npm:^1.3.0" + checksum: 10/db4288a9e87613a54836313cd48df7e11450b885c64b5a6c054d057d33501ee1501cf72d246eb588131b90574e5471509f00944fc5b55bacc83750f1321a58e1 + languageName: node + linkType: hard + +"@codemirror/lang-java@npm:^6.0.0": + version: 6.0.1 + resolution: "@codemirror/lang-java@npm:6.0.1" + dependencies: + "@codemirror/language": "npm:^6.0.0" + "@lezer/java": "npm:^1.0.0" + checksum: 10/4679104683cbffcd224ac04c7e5d144b787494697b26470b07017259035b7bb3fa62609d9a61bfbc566f1756d9f972f9f26d96a3c1362dd48881c1172f9a914d + languageName: node + linkType: hard + +"@codemirror/lang-javascript@npm:^6.0.0, @codemirror/lang-javascript@npm:^6.1.2": + version: 6.2.2 + resolution: "@codemirror/lang-javascript@npm:6.2.2" + dependencies: + "@codemirror/autocomplete": "npm:^6.0.0" + "@codemirror/language": "npm:^6.6.0" + "@codemirror/lint": "npm:^6.0.0" + "@codemirror/state": "npm:^6.0.0" + "@codemirror/view": "npm:^6.17.0" + "@lezer/common": "npm:^1.0.0" + "@lezer/javascript": "npm:^1.0.0" + checksum: 10/eac2e57a7a595cf0c93afd4bb42034902230c73e5525554ba925bad12aa544ca58014c017466288a2b34f1684d6efa5537507ed8b57e276d02665c2821c7a9d6 + languageName: node + linkType: hard + +"@codemirror/lang-json@npm:^6.0.0": + version: 6.0.1 + resolution: "@codemirror/lang-json@npm:6.0.1" + dependencies: + "@codemirror/language": "npm:^6.0.0" + "@lezer/json": "npm:^1.0.0" + checksum: 10/7ce35d345bf9b2f5d96e2502a9693c8b2e74981ccf3a7a20da48e405c2bd6067b39acfd9b31fe3bbb5f9f28ccdde5ff7c52253c6d5b3be84b29df6d5db0b3b9b + languageName: node + linkType: hard + +"@codemirror/lang-less@npm:^6.0.0": + version: 6.0.2 + resolution: "@codemirror/lang-less@npm:6.0.2" + dependencies: + "@codemirror/lang-css": "npm:^6.2.0" + "@codemirror/language": "npm:^6.0.0" + "@lezer/common": "npm:^1.2.0" + "@lezer/highlight": "npm:^1.0.0" + "@lezer/lr": "npm:^1.0.0" + checksum: 10/233aa03e0bfb57e4f23fdb2a8d52ad3143a52553895b97cb2901f66d59fc40246b525a21b7be72d873efa98783f6da5f4e8e042da442b092fab9eb63c69524c8 + languageName: node + linkType: hard + +"@codemirror/lang-liquid@npm:^6.0.0": + version: 6.2.2 + resolution: "@codemirror/lang-liquid@npm:6.2.2" + dependencies: + "@codemirror/autocomplete": "npm:^6.0.0" + "@codemirror/lang-html": "npm:^6.0.0" + "@codemirror/language": "npm:^6.0.0" + "@codemirror/state": "npm:^6.0.0" + "@codemirror/view": "npm:^6.0.0" + "@lezer/common": "npm:^1.0.0" + "@lezer/highlight": "npm:^1.0.0" + "@lezer/lr": "npm:^1.3.1" + checksum: 10/0f009449542053126c18cdf4689c140087701f435c797d28f97dbcea2cb42c655481d21fbe9439a08963bf7e533b4b4a2cefdeca430494b33ac9930764c94f04 + languageName: node + linkType: hard + +"@codemirror/lang-markdown@npm:^6.0.0, @codemirror/lang-markdown@npm:^6.2.3": + version: 6.3.1 + resolution: "@codemirror/lang-markdown@npm:6.3.1" + dependencies: + "@codemirror/autocomplete": "npm:^6.7.1" + "@codemirror/lang-html": "npm:^6.0.0" + "@codemirror/language": "npm:^6.3.0" + "@codemirror/state": "npm:^6.0.0" + "@codemirror/view": "npm:^6.0.0" + "@lezer/common": "npm:^1.2.1" + "@lezer/markdown": "npm:^1.0.0" + checksum: 10/e13de98d651b2f95521aa742c4da9d6a0b9160968dcd618776ad7a088ba7cfb927f935aa1c0d76fc8cc313d3a811e215322978714d59a4de3e33e06fa6fa2067 + languageName: node + linkType: hard + +"@codemirror/lang-php@npm:^6.0.0": + version: 6.0.1 + resolution: "@codemirror/lang-php@npm:6.0.1" + dependencies: + "@codemirror/lang-html": "npm:^6.0.0" + "@codemirror/language": "npm:^6.0.0" + "@codemirror/state": "npm:^6.0.0" + "@lezer/common": "npm:^1.0.0" + "@lezer/php": "npm:^1.0.0" + checksum: 10/f8613ca25e4f447eea1d5514d3c3345fe78639112df1fb75f0a6933da671902911815a82eab70df3279db0bf04cdb673186c7cec3beabe152ef69d3794d14196 + languageName: node + linkType: hard + +"@codemirror/lang-python@npm:^6.0.0": + version: 6.1.6 + resolution: "@codemirror/lang-python@npm:6.1.6" + dependencies: + "@codemirror/autocomplete": "npm:^6.3.2" + "@codemirror/language": "npm:^6.8.0" + "@codemirror/state": "npm:^6.0.0" + "@lezer/common": "npm:^1.2.1" + "@lezer/python": "npm:^1.1.4" + checksum: 10/a0a893d5947a58eaf09e0b4b6b76184755e23db3a56030c3a8a80d52ec568bd06e8099a4b3453ee985e7f60b9e323d72ed2e10403032440ac4e8869cf0405b10 + languageName: node + linkType: hard + +"@codemirror/lang-rust@npm:^6.0.0": + version: 6.0.1 + resolution: "@codemirror/lang-rust@npm:6.0.1" + dependencies: + "@codemirror/language": "npm:^6.0.0" + "@lezer/rust": "npm:^1.0.0" + checksum: 10/8a439944cb22159b0b3465ca4fa4294c69843219d5d30e278ae6df8e48f30a7a9256129723c025ec9b5e694d31a3560fb004300b125ffcd81c22d13825845170 + languageName: node + linkType: hard + +"@codemirror/lang-sass@npm:^6.0.0": + version: 6.0.2 + resolution: "@codemirror/lang-sass@npm:6.0.2" + dependencies: + "@codemirror/lang-css": "npm:^6.2.0" + "@codemirror/language": "npm:^6.0.0" + "@codemirror/state": "npm:^6.0.0" + "@lezer/common": "npm:^1.0.2" + "@lezer/sass": "npm:^1.0.0" + checksum: 10/de5c72f62714960961b616c8fe25b4f127906b0c05ec2e8e633d0b896fb2da979732cad0f51b1c0bc058494db5447664234b7910a3345fb5080d8222d4011818 + languageName: node + linkType: hard + +"@codemirror/lang-sql@npm:^6.0.0": + version: 6.8.0 + resolution: "@codemirror/lang-sql@npm:6.8.0" + dependencies: + "@codemirror/autocomplete": "npm:^6.0.0" + "@codemirror/language": "npm:^6.0.0" + "@codemirror/state": "npm:^6.0.0" + "@lezer/common": "npm:^1.2.0" + "@lezer/highlight": "npm:^1.0.0" + "@lezer/lr": "npm:^1.0.0" + checksum: 10/a226e6b8dc2ada3720acb65dc615b76754fde9fd85bfbaf7dcf49115802b9e031d251fa26ab6d97dbd04f777c631a2fe0622b8fca503a9fda1e727e94e2d68e9 + languageName: node + linkType: hard + +"@codemirror/lang-vue@npm:^0.1.1": + version: 0.1.3 + resolution: "@codemirror/lang-vue@npm:0.1.3" + dependencies: + "@codemirror/lang-html": "npm:^6.0.0" + "@codemirror/lang-javascript": "npm:^6.1.2" + "@codemirror/language": "npm:^6.0.0" + "@lezer/common": "npm:^1.2.0" + "@lezer/highlight": "npm:^1.0.0" + "@lezer/lr": "npm:^1.3.1" + checksum: 10/f08c04c47671de0b65568cbc0f9e517c7c443bd097eb0ca63ecdc1b81f842dbfd0b81b208ee5c31e3293ebc377b4b271c89cf18bf04bb898ecac538df22aff3a + languageName: node + linkType: hard + +"@codemirror/lang-wast@npm:^6.0.0": + version: 6.0.2 + resolution: "@codemirror/lang-wast@npm:6.0.2" + dependencies: + "@codemirror/language": "npm:^6.0.0" + "@lezer/common": "npm:^1.2.0" + "@lezer/highlight": "npm:^1.0.0" + "@lezer/lr": "npm:^1.0.0" + checksum: 10/c7f9820191ca8d8877b38a6205f7e46721c0deeb552c3f234a7166bfd4581e071718cbf395749f271bd5c0da879285a7829a5843c052dd52e8a4e41509777e35 + languageName: node + linkType: hard + +"@codemirror/lang-xml@npm:^6.0.0": + version: 6.1.0 + resolution: "@codemirror/lang-xml@npm:6.1.0" + dependencies: + "@codemirror/autocomplete": "npm:^6.0.0" + "@codemirror/language": "npm:^6.4.0" + "@codemirror/state": "npm:^6.0.0" + "@codemirror/view": "npm:^6.0.0" + "@lezer/common": "npm:^1.0.0" + "@lezer/xml": "npm:^1.0.0" + checksum: 10/f5e54668c30efbb8a78a51e49ccec92a06931f2b98dce35c90be94ded30da02dac525124ce3c40f65c7b071f8db72d16b10a5a1795ccbf10e69939c0a9c1cac8 + languageName: node + linkType: hard + +"@codemirror/lang-yaml@npm:^6.0.0": + version: 6.1.1 + resolution: "@codemirror/lang-yaml@npm:6.1.1" + dependencies: + "@codemirror/autocomplete": "npm:^6.0.0" + "@codemirror/language": "npm:^6.0.0" + "@codemirror/state": "npm:^6.0.0" + "@lezer/common": "npm:^1.2.0" + "@lezer/highlight": "npm:^1.2.0" + "@lezer/yaml": "npm:^1.0.0" + checksum: 10/b751727c5fed180e28718a3ec8277835e62e2e760ed18f3a1594062e80b7b241c02aed189d63443e50216db3eb688a3dbe7baa634bdacf3909937fcf341b414b + languageName: node + linkType: hard + +"@codemirror/language-data@npm:^6.5.1": + version: 6.5.1 + resolution: "@codemirror/language-data@npm:6.5.1" + dependencies: + "@codemirror/lang-angular": "npm:^0.1.0" + "@codemirror/lang-cpp": "npm:^6.0.0" + "@codemirror/lang-css": "npm:^6.0.0" + "@codemirror/lang-go": "npm:^6.0.0" + "@codemirror/lang-html": "npm:^6.0.0" + "@codemirror/lang-java": "npm:^6.0.0" + "@codemirror/lang-javascript": "npm:^6.0.0" + "@codemirror/lang-json": "npm:^6.0.0" + "@codemirror/lang-less": "npm:^6.0.0" + "@codemirror/lang-liquid": "npm:^6.0.0" + "@codemirror/lang-markdown": "npm:^6.0.0" + "@codemirror/lang-php": "npm:^6.0.0" + "@codemirror/lang-python": "npm:^6.0.0" + "@codemirror/lang-rust": "npm:^6.0.0" + "@codemirror/lang-sass": "npm:^6.0.0" + "@codemirror/lang-sql": "npm:^6.0.0" + "@codemirror/lang-vue": "npm:^0.1.1" + "@codemirror/lang-wast": "npm:^6.0.0" + "@codemirror/lang-xml": "npm:^6.0.0" + "@codemirror/lang-yaml": "npm:^6.0.0" + "@codemirror/language": "npm:^6.0.0" + "@codemirror/legacy-modes": "npm:^6.4.0" + checksum: 10/a46f4015da2c213ebe7cc685b5c657b92a340e3f12ed26a9f8c3a04cf3e46063c86141c6f9dca4faeaaf6de068b4e797d40c9a572ca317a122f84ad7ceb5e684 + languageName: node + linkType: hard + +"@codemirror/language@npm:^6.0.0, @codemirror/language@npm:^6.3.0, @codemirror/language@npm:^6.3.2, @codemirror/language@npm:^6.4.0, @codemirror/language@npm:^6.6.0, @codemirror/language@npm:^6.8.0": + version: 6.10.6 + resolution: "@codemirror/language@npm:6.10.6" + dependencies: + "@codemirror/state": "npm:^6.0.0" + "@codemirror/view": "npm:^6.23.0" + "@lezer/common": "npm:^1.1.0" + "@lezer/highlight": "npm:^1.0.0" + "@lezer/lr": "npm:^1.0.0" + style-mod: "npm:^4.0.0" + checksum: 10/7fc7019ae1ed956b40dd4c6bacd96558d4f5477a072d4ae73eace5faddf0c898cfa3bb75c8881a28f6e683179086a576a25e6d6c619c67b1de2d1ceb88287212 + languageName: node + linkType: hard + +"@codemirror/legacy-modes@npm:^6.4.0": + version: 6.4.2 + resolution: "@codemirror/legacy-modes@npm:6.4.2" + dependencies: + "@codemirror/language": "npm:^6.0.0" + checksum: 10/2d32742b6fb457aad8bc3de4d8019ef305000038e488ed7a564d62e1c1f5d8c45fade2bd4fdadf1b6e80401ef5694b2b48a86c5a955e06c81cfa6b33504a14fb + languageName: node + linkType: hard + +"@codemirror/lint@npm:^6.0.0": + version: 6.8.4 + resolution: "@codemirror/lint@npm:6.8.4" + dependencies: + "@codemirror/state": "npm:^6.0.0" + "@codemirror/view": "npm:^6.35.0" + crelt: "npm:^1.0.5" + checksum: 10/401ead0591d88d31d1bf6527d4caba26e0deb7b49382dfbb8c712037d858047b0699fa2c15831a07db928194549eea9b942004fee42f334b34ff5973c7dbec58 + languageName: node + linkType: hard + +"@codemirror/merge@npm:^6.4.0": + version: 6.7.4 + resolution: "@codemirror/merge@npm:6.7.4" + dependencies: + "@codemirror/language": "npm:^6.0.0" + "@codemirror/state": "npm:^6.0.0" + "@codemirror/view": "npm:^6.17.0" + "@lezer/highlight": "npm:^1.0.0" + style-mod: "npm:^4.1.0" + checksum: 10/75b6874d59e9df560e1b56d701ee992ab47275e8928bd7b883f2d285821fbd21492b3295d3994471d42e6f7a111b4ffb6a267380a8e178be19fec3f4dcb3083d + languageName: node + linkType: hard + +"@codemirror/search@npm:^6.0.0": + version: 6.5.8 + resolution: "@codemirror/search@npm:6.5.8" + dependencies: + "@codemirror/state": "npm:^6.0.0" + "@codemirror/view": "npm:^6.0.0" + crelt: "npm:^1.0.5" + checksum: 10/1389fa4e05da72b621ae10ff00d4ef0d23b08a3a7ac48f6e0b867429da7b80036bd7a6feef1a54f5b4c4af9fc4277f754a313a858fbfc12ccdfa48e9bf300cef + languageName: node + linkType: hard + +"@codemirror/state@npm:^6.0.0, @codemirror/state@npm:^6.2.0, @codemirror/state@npm:^6.4.0": + version: 6.4.1 + resolution: "@codemirror/state@npm:6.4.1" + checksum: 10/a9ec56c7d7d52034ce8ebea3a9a4d216b9e972d701b32b5000e56c97790d0d46af129aeba0b80bed36648b4024b3ba3e4910cf5bfed11de4a9e89252e0707a70 + languageName: node + linkType: hard + +"@codemirror/view@npm:^6.0.0, @codemirror/view@npm:^6.17.0, @codemirror/view@npm:^6.23.0, @codemirror/view@npm:^6.27.0, @codemirror/view@npm:^6.35.0, @codemirror/view@npm:^6.7.1": + version: 6.35.2 + resolution: "@codemirror/view@npm:6.35.2" + dependencies: + "@codemirror/state": "npm:^6.4.0" + style-mod: "npm:^4.1.0" + w3c-keyname: "npm:^2.2.4" + checksum: 10/0bd38d2fbedf168f2fbb0142b94b8491102f5e193d56f0b0ae1ab2cfdc265c7187295fcaad4e76903be1bf052c612666a1cdd3115b2bdf62b1b202a280b7c455 + languageName: node + linkType: hard + +"@codesandbox/nodebox@npm:0.1.8": + version: 0.1.8 + resolution: "@codesandbox/nodebox@npm:0.1.8" + dependencies: + outvariant: "npm:^1.4.0" + strict-event-emitter: "npm:^0.4.3" + checksum: 10/3de00d306ae0236524c436b8640c442a4541f0eb696820aa5f6fc66018dc66431d8d78edd9305986aa2877befca292a3906bb4c6af2ecba5f15dbd587d1c916d + languageName: node + linkType: hard + +"@codesandbox/sandpack-client@npm:^2.19.8": + version: 2.19.8 + resolution: "@codesandbox/sandpack-client@npm:2.19.8" + dependencies: + "@codesandbox/nodebox": "npm:0.1.8" + buffer: "npm:^6.0.3" + dequal: "npm:^2.0.2" + mime-db: "npm:^1.52.0" + outvariant: "npm:1.4.0" + static-browser-server: "npm:1.0.3" + checksum: 10/7913fa4a7bbaf925f4ab0ad1969a4da1f52c093b1b71229dd8ecf3f50462bd004fcf3fddccdb15225b55737305d3f6727a187353f12cb39a75336fa7229eb4dd + languageName: node + linkType: hard + +"@codesandbox/sandpack-react@npm:^2.10.0": + version: 2.19.10 + resolution: "@codesandbox/sandpack-react@npm:2.19.10" + dependencies: + "@codemirror/autocomplete": "npm:^6.4.0" + "@codemirror/commands": "npm:^6.1.3" + "@codemirror/lang-css": "npm:^6.0.1" + "@codemirror/lang-html": "npm:^6.4.0" + "@codemirror/lang-javascript": "npm:^6.1.2" + "@codemirror/language": "npm:^6.3.2" + "@codemirror/state": "npm:^6.2.0" + "@codemirror/view": "npm:^6.7.1" + "@codesandbox/sandpack-client": "npm:^2.19.8" + "@lezer/highlight": "npm:^1.1.3" + "@react-hook/intersection-observer": "npm:^3.1.1" + "@stitches/core": "npm:^1.2.6" + anser: "npm:^2.1.1" + clean-set: "npm:^1.1.2" + dequal: "npm:^2.0.2" + escape-carriage: "npm:^1.3.1" + lz-string: "npm:^1.4.4" + react-devtools-inline: "npm:4.4.0" + react-is: "npm:^17.0.2" + peerDependencies: + react: ^16.8.0 || ^17 || ^18 + react-dom: ^16.8.0 || ^17 || ^18 + checksum: 10/13ec978c6decc0712f6701e3b2db3047ff0d83dbbaeafae25eecddfaa7e3148c0da4463aac7defe2101c311de132b6f31edf67047e0ae1df96db3619bbc27439 + languageName: node + linkType: hard + "@colors/colors@npm:1.5.0": version: 1.5.0 resolution: "@colors/colors@npm:1.5.0" @@ -3228,6 +3680,44 @@ __metadata: languageName: node linkType: hard +"@floating-ui/core@npm:^1.6.0": + version: 1.6.8 + resolution: "@floating-ui/core@npm:1.6.8" + dependencies: + "@floating-ui/utils": "npm:^0.2.8" + checksum: 10/87d52989c3d2cc80373bc153b7a40814db3206ce7d0b2a2bdfb63e2ff39ffb8b999b1b0ccf28e548000ebf863bf16e2bed45eab4c4d287a5dbe974ef22368d82 + languageName: node + linkType: hard + +"@floating-ui/dom@npm:^1.0.0": + version: 1.6.12 + resolution: "@floating-ui/dom@npm:1.6.12" + dependencies: + "@floating-ui/core": "npm:^1.6.0" + "@floating-ui/utils": "npm:^0.2.8" + checksum: 10/5c8e5fdcd3843140a606ab6dc6c12ad740f44e66b898966ef877393faaede0bbe14586e1049e2c2f08856437da8847e884a2762e78275fefa65a5a9cd71e580d + languageName: node + linkType: hard + +"@floating-ui/react-dom@npm:^2.0.0": + version: 2.1.2 + resolution: "@floating-ui/react-dom@npm:2.1.2" + dependencies: + "@floating-ui/dom": "npm:^1.0.0" + peerDependencies: + react: ">=16.8.0" + react-dom: ">=16.8.0" + checksum: 10/2a67dc8499674e42ff32c7246bded185bb0fdd492150067caf9568569557ac4756a67787421d8604b0f241e5337de10762aee270d9aeef106d078a0ff13596c4 + languageName: node + linkType: hard + +"@floating-ui/utils@npm:^0.2.8": + version: 0.2.8 + resolution: "@floating-ui/utils@npm:0.2.8" + checksum: 10/3e3ea3b2de06badc4baebdf358b3dbd77ccd9474a257a6ef237277895943db2acbae756477ec64de65a2a1436d94aea3107129a1feeef6370675bf2b161c1abc + languageName: node + linkType: hard + "@formatjs/ecma402-abstract@npm:2.2.3": version: 2.2.3 resolution: "@formatjs/ecma402-abstract@npm:2.2.3" @@ -4485,925 +4975,2114 @@ __metadata: languageName: node linkType: hard -"@nicolo-ribaudo/chokidar-2@npm:2.1.8-no-fsevents.3": - version: 2.1.8-no-fsevents.3 - resolution: "@nicolo-ribaudo/chokidar-2@npm:2.1.8-no-fsevents.3" - checksum: 10/c6e83af3b5051a3f6562649ff8fe37de9934a4cc02138678ed1badbd13ed3334f7ae5f63f2bbc3432210f6b245f082ac97e9b2afe0c13730c9838b295658c185 +"@lexical/clipboard@npm:0.17.1, @lexical/clipboard@npm:^0.17.1": + version: 0.17.1 + resolution: "@lexical/clipboard@npm:0.17.1" + dependencies: + "@lexical/html": "npm:0.17.1" + "@lexical/list": "npm:0.17.1" + "@lexical/selection": "npm:0.17.1" + "@lexical/utils": "npm:0.17.1" + lexical: "npm:0.17.1" + checksum: 10/fc7770415f0a0a98a1a1436b6ec115199cb60e4658cc7ee74d19e1546f5c57a8f3ea759bfc61e252812bf7512607548b9c6973de47518955213982cc2915299e languageName: node linkType: hard -"@nodelib/fs.scandir@npm:2.1.5": - version: 2.1.5 - resolution: "@nodelib/fs.scandir@npm:2.1.5" +"@lexical/code@npm:0.17.1": + version: 0.17.1 + resolution: "@lexical/code@npm:0.17.1" dependencies: - "@nodelib/fs.stat": "npm:2.0.5" - run-parallel: "npm:^1.1.9" - checksum: 10/6ab2a9b8a1d67b067922c36f259e3b3dfd6b97b219c540877a4944549a4d49ea5ceba5663905ab5289682f1f3c15ff441d02f0447f620a42e1cb5e1937174d4b + "@lexical/utils": "npm:0.17.1" + lexical: "npm:0.17.1" + prismjs: "npm:^1.27.0" + checksum: 10/8e3fb180eb7b99f85d438faec87c5f120c7da0099f5f024e85fb153dd697aaadb3edfdade4ec942cafa9238adc6770a9d313c80672edda3270b59a9e2e1a6afb languageName: node linkType: hard -"@nodelib/fs.scandir@npm:3.0.0": - version: 3.0.0 - resolution: "@nodelib/fs.scandir@npm:3.0.0" +"@lexical/devtools-core@npm:0.17.1": + version: 0.17.1 + resolution: "@lexical/devtools-core@npm:0.17.1" dependencies: - "@nodelib/fs.stat": "npm:3.0.0" - run-parallel: "npm:^1.2.0" - checksum: 10/5d4e48148b046afbdd7202e6484fb246412abfce6c3bec004eab663abf582e471a17d63c28b9f6638ff385a69f67c7abc4786d28e2de6b8d177687fe3b1c5a9e + "@lexical/html": "npm:0.17.1" + "@lexical/link": "npm:0.17.1" + "@lexical/mark": "npm:0.17.1" + "@lexical/table": "npm:0.17.1" + "@lexical/utils": "npm:0.17.1" + lexical: "npm:0.17.1" + peerDependencies: + react: ">=17.x" + react-dom: ">=17.x" + checksum: 10/dce5570e8e30e24a87d26ab1ce0cc2305f8920c2e27e648b157b62f5b58e827fe0d7ca5733e0aab90249b9329d315c400a2bd68c4ae2c111bd724dada4f919db languageName: node linkType: hard -"@nodelib/fs.stat@npm:2.0.5, @nodelib/fs.stat@npm:^2.0.2": - version: 2.0.5 - resolution: "@nodelib/fs.stat@npm:2.0.5" - checksum: 10/012480b5ca9d97bff9261571dbbec7bbc6033f69cc92908bc1ecfad0792361a5a1994bc48674b9ef76419d056a03efadfce5a6cf6dbc0a36559571a7a483f6f0 +"@lexical/dragon@npm:0.17.1": + version: 0.17.1 + resolution: "@lexical/dragon@npm:0.17.1" + dependencies: + lexical: "npm:0.17.1" + checksum: 10/6f60690b2a231f67a7202ec0e3d890907db0539c4cc23ef335048935aeb44ae6691b58b4b95419b67bf0176eb18130904f8e43719a1f031c9cb96b64d8c52adf languageName: node linkType: hard -"@nodelib/fs.stat@npm:3.0.0": - version: 3.0.0 - resolution: "@nodelib/fs.stat@npm:3.0.0" - checksum: 10/93a93e19b64d0275b5120bed2cf85da4c5804014de1bdac6e9933b835b1cb9f88252dc990b148076bec034fc757bdd97d74cf5d99bc9f895e0f925aeabe7dbcf +"@lexical/hashtag@npm:0.17.1": + version: 0.17.1 + resolution: "@lexical/hashtag@npm:0.17.1" + dependencies: + "@lexical/utils": "npm:0.17.1" + lexical: "npm:0.17.1" + checksum: 10/76d753fe2455307be8dc5a952db99e1dce964cece62cf6c0ed10eedd84a1bdb89fb288462d00db8da422faa43e423bb9dc22012703df01bd1f3c92f2c10e95a1 languageName: node linkType: hard -"@nodelib/fs.walk@npm:^1.2.3": - version: 1.2.8 - resolution: "@nodelib/fs.walk@npm:1.2.8" +"@lexical/history@npm:0.17.1": + version: 0.17.1 + resolution: "@lexical/history@npm:0.17.1" dependencies: - "@nodelib/fs.scandir": "npm:2.1.5" - fastq: "npm:^1.6.0" - checksum: 10/40033e33e96e97d77fba5a238e4bba4487b8284678906a9f616b5579ddaf868a18874c0054a75402c9fbaaa033a25ceae093af58c9c30278e35c23c9479e79b0 + "@lexical/utils": "npm:0.17.1" + lexical: "npm:0.17.1" + checksum: 10/fab81ae638188802fa21d96a7effda4e35a204e559a2894cad7a2bfcd655d3c0641da87bcd0d900ab8af3fc843c75a5d984bbc2ab964400373926d21f6ff4bb4 languageName: node linkType: hard -"@nodelib/fs.walk@npm:^2.0.0": - version: 2.0.0 - resolution: "@nodelib/fs.walk@npm:2.0.0" +"@lexical/html@npm:0.17.1": + version: 0.17.1 + resolution: "@lexical/html@npm:0.17.1" dependencies: - "@nodelib/fs.scandir": "npm:3.0.0" - fastq: "npm:^1.15.0" - checksum: 10/0b343472c9a8feb0f928292a4990206e69117cc0b124522bbf576b04e1233d8f04575923a3badde17d74c2b5b3092fa705fad63fa612bf8fd7378b1d908d7061 + "@lexical/selection": "npm:0.17.1" + "@lexical/utils": "npm:0.17.1" + lexical: "npm:0.17.1" + checksum: 10/a5f2afb36d62ada1d7e76cece63c59067b3732978332f7b257215353c9f8c2a6c3c6bf772e57b212141b099c3293e8da1c48b60bc33f017159990a200959f048 languageName: node linkType: hard -"@npmcli/agent@npm:^2.0.0": - version: 2.2.2 - resolution: "@npmcli/agent@npm:2.2.2" +"@lexical/link@npm:0.17.1, @lexical/link@npm:^0.17.1": + version: 0.17.1 + resolution: "@lexical/link@npm:0.17.1" dependencies: - agent-base: "npm:^7.1.0" - http-proxy-agent: "npm:^7.0.0" - https-proxy-agent: "npm:^7.0.1" - lru-cache: "npm:^10.0.1" - socks-proxy-agent: "npm:^8.0.3" - checksum: 10/96fc0036b101bae5032dc2a4cd832efb815ce9b33f9ee2f29909ee49d96a0026b3565f73c507a69eb8603f5cb32e0ae45a70cab1e2655990a4e06ae99f7f572a + "@lexical/utils": "npm:0.17.1" + lexical: "npm:0.17.1" + checksum: 10/8315f2371ce9be817d614dfdaefecc74169a5ebfdc092d0b614edcb0bca93dfe687b322d3116ea1a8f5c2fcfc1beae768640b082e8342d7744521ec579bfb259 languageName: node linkType: hard -"@npmcli/fs@npm:^3.1.0": - version: 3.1.1 - resolution: "@npmcli/fs@npm:3.1.1" +"@lexical/list@npm:0.17.1, @lexical/list@npm:^0.17.1": + version: 0.17.1 + resolution: "@lexical/list@npm:0.17.1" dependencies: - semver: "npm:^7.3.5" - checksum: 10/1e0e04087049b24b38bc0b30d87a9388ee3ca1d3fdfc347c2f77d84fcfe6a51f250bc57ba2c1f614d7e4285c6c62bf8c769bc19aa0949ea39e5b043ee023b0bd + "@lexical/utils": "npm:0.17.1" + lexical: "npm:0.17.1" + checksum: 10/3915774470d3b8ac432ac58da1a91f31fbc82a7b0facc80f790b798207c777dea818b66a1bd19312f5e41c55d4d025a313083878ce941955fa3dd0c1ba0ed0c4 languageName: node linkType: hard -"@npmcli/git@npm:^5.0.0": - version: 5.0.8 - resolution: "@npmcli/git@npm:5.0.8" +"@lexical/mark@npm:0.17.1": + version: 0.17.1 + resolution: "@lexical/mark@npm:0.17.1" dependencies: - "@npmcli/promise-spawn": "npm:^7.0.0" - ini: "npm:^4.1.3" - lru-cache: "npm:^10.0.1" - npm-pick-manifest: "npm:^9.0.0" - proc-log: "npm:^4.0.0" - promise-inflight: "npm:^1.0.1" - promise-retry: "npm:^2.0.1" - semver: "npm:^7.3.5" - which: "npm:^4.0.0" - checksum: 10/e6f94175fb9dde13d84849b29b32ffb4c4df968822cc85df2aebfca13bf8ca76f33b1d281911f5bcddc95bccba2f9e795669c736a38de4d9c76efb5047ffb4fb + "@lexical/utils": "npm:0.17.1" + lexical: "npm:0.17.1" + checksum: 10/b955a4ee0af6364af934d5af00b339bbdf3ce6b0787ab730334a4276428b23ac0e27bef167a504594d640fc7e69d8bad65830362f4f5be1f165dec753f7d44f2 languageName: node linkType: hard -"@npmcli/package-json@npm:^5.2.0": - version: 5.2.1 - resolution: "@npmcli/package-json@npm:5.2.1" +"@lexical/markdown@npm:0.17.1, @lexical/markdown@npm:^0.17.1": + version: 0.17.1 + resolution: "@lexical/markdown@npm:0.17.1" dependencies: - "@npmcli/git": "npm:^5.0.0" - glob: "npm:^10.2.2" - hosted-git-info: "npm:^7.0.0" - json-parse-even-better-errors: "npm:^3.0.0" - normalize-package-data: "npm:^6.0.0" - proc-log: "npm:^4.0.0" - semver: "npm:^7.5.3" - checksum: 10/304a819b93f79a6e0e56cb371961a66d2db72142e310d545ecbbbe4d917025a30601aa8e63a5f0cc28f0fe281c116bdaf79b334619b105a1d027a2b769ecd137 + "@lexical/code": "npm:0.17.1" + "@lexical/link": "npm:0.17.1" + "@lexical/list": "npm:0.17.1" + "@lexical/rich-text": "npm:0.17.1" + "@lexical/text": "npm:0.17.1" + "@lexical/utils": "npm:0.17.1" + lexical: "npm:0.17.1" + checksum: 10/9d281abf263ff972708509daa733676ca801d26abd9f82f825c629b25255425460c64406203fb0ef8af65c3f39f2204350c728d82b94c2e6761cf10175018057 languageName: node linkType: hard -"@npmcli/promise-spawn@npm:^7.0.0": - version: 7.0.2 - resolution: "@npmcli/promise-spawn@npm:7.0.2" +"@lexical/offset@npm:0.17.1": + version: 0.17.1 + resolution: "@lexical/offset@npm:0.17.1" dependencies: - which: "npm:^4.0.0" - checksum: 10/94cbbbeeb20342026c3b68fc8eb09e1600b7645d4e509f2588ef5ea7cff977eb01e628cc8e014595d04a6af4b4bc5c467c950a8135920f39f7c7b57fba43f4e9 + lexical: "npm:0.17.1" + checksum: 10/a16764b4981235e3dab228aabc50dcbbe85b06c160b1cb909565235a2439987cea30b73a98e5a4a67248a71f291ac21f31af3e751fcb655e481f5f7c8b8d3297 languageName: node linkType: hard -"@openspacelabs/react-native-zoomable-view@npm:2.2.0": - version: 2.2.0 - resolution: "@openspacelabs/react-native-zoomable-view@npm:2.2.0" +"@lexical/overflow@npm:0.17.1": + version: 0.17.1 + resolution: "@lexical/overflow@npm:0.17.1" dependencies: - prop-types: "npm:^15.7.2" - peerDependencies: - react: ">=16.8.0" - react-native: ">=0.54.0" - checksum: 10/46ff36b5d9250a0aa06b760b0393e0a7403b654c85db49fc639bb9286cc67d8b1c380f3f8102e59c8895e934ae2f6ccc5b3213fb480eafceb078509c9a13405b + lexical: "npm:0.17.1" + checksum: 10/b0b8b817bd05ff10844e6c42d643dc0f6f5f55b0fe186b8804e24422da245a2bff9c8518f873fe41094059643d82519e1e4a472a7f39d705b2a81e6de76e5501 languageName: node linkType: hard -"@opentelemetry/api-logs@npm:0.52.1": - version: 0.52.1 - resolution: "@opentelemetry/api-logs@npm:0.52.1" +"@lexical/plain-text@npm:0.17.1, @lexical/plain-text@npm:^0.17.1": + version: 0.17.1 + resolution: "@lexical/plain-text@npm:0.17.1" dependencies: - "@opentelemetry/api": "npm:^1.0.0" - checksum: 10/7515667a41a38014ffda70674c0b77c9c68417cde9f8ce8840e675308b4431f99d879e8d347f1b08486561617f914c07ee704ad6ed8a6522dabc3a81ac39dc88 + "@lexical/clipboard": "npm:0.17.1" + "@lexical/selection": "npm:0.17.1" + "@lexical/utils": "npm:0.17.1" + lexical: "npm:0.17.1" + checksum: 10/6fece239785512d2537d3b7614b0532ad00064ad3dd8bab26256b25a8ecfbe2561bfc1dc169f5fce7adf95de9abe2650612282d97d2453a1706f6f0bf2ee08d5 languageName: node linkType: hard -"@opentelemetry/api-logs@npm:0.53.0": - version: 0.53.0 - resolution: "@opentelemetry/api-logs@npm:0.53.0" +"@lexical/react@npm:^0.17.1": + version: 0.17.1 + resolution: "@lexical/react@npm:0.17.1" dependencies: - "@opentelemetry/api": "npm:^1.0.0" - checksum: 10/347b4554d6ee01afb29bd39e8f9cbbccd80abb0883fe6a84e3bcce8ab4dbfe357a2729246d2f66de0de6272846fd1bb2d71e286e18ad2690d9e7f46f02f00f73 + "@lexical/clipboard": "npm:0.17.1" + "@lexical/code": "npm:0.17.1" + "@lexical/devtools-core": "npm:0.17.1" + "@lexical/dragon": "npm:0.17.1" + "@lexical/hashtag": "npm:0.17.1" + "@lexical/history": "npm:0.17.1" + "@lexical/link": "npm:0.17.1" + "@lexical/list": "npm:0.17.1" + "@lexical/mark": "npm:0.17.1" + "@lexical/markdown": "npm:0.17.1" + "@lexical/overflow": "npm:0.17.1" + "@lexical/plain-text": "npm:0.17.1" + "@lexical/rich-text": "npm:0.17.1" + "@lexical/selection": "npm:0.17.1" + "@lexical/table": "npm:0.17.1" + "@lexical/text": "npm:0.17.1" + "@lexical/utils": "npm:0.17.1" + "@lexical/yjs": "npm:0.17.1" + lexical: "npm:0.17.1" + react-error-boundary: "npm:^3.1.4" + peerDependencies: + react: ">=17.x" + react-dom: ">=17.x" + checksum: 10/11c706d801cbe8c495b198615fbce5cda602c9a8bc647b53e0c58f53c7f235da5a0289794b756d2f2d7fb28953575989ff504a02b969fa24a526454939b42863 languageName: node linkType: hard -"@opentelemetry/api-logs@npm:0.54.2": - version: 0.54.2 - resolution: "@opentelemetry/api-logs@npm:0.54.2" +"@lexical/rich-text@npm:0.17.1, @lexical/rich-text@npm:^0.17.1": + version: 0.17.1 + resolution: "@lexical/rich-text@npm:0.17.1" dependencies: - "@opentelemetry/api": "npm:^1.3.0" - checksum: 10/97d887be03ca4a2e69574cc9160464bda00f2a167cc850656ade44b6690a75855d9334983b73827dc44c3672958bc478197f261eae11c2ac68a6df9260c9c3df - languageName: node - linkType: hard - -"@opentelemetry/api@npm:^1.0.0, @opentelemetry/api@npm:^1.3.0, @opentelemetry/api@npm:^1.8, @opentelemetry/api@npm:^1.9.0": - version: 1.9.0 - resolution: "@opentelemetry/api@npm:1.9.0" - checksum: 10/a607f0eef971893c4f2ee2a4c2069aade6ec3e84e2a1f5c2aac19f65c5d9eeea41aa72db917c1029faafdd71789a1a040bdc18f40d63690e22ccae5d7070f194 + "@lexical/clipboard": "npm:0.17.1" + "@lexical/selection": "npm:0.17.1" + "@lexical/utils": "npm:0.17.1" + lexical: "npm:0.17.1" + checksum: 10/23f72e5294faf9b05434f1fbce7722fe05682600d615c814cc3301c439ba584596cc8f00c85b57a2978aef90d17b23dbe6a7a2d229a04082f9bc825c8157a5c1 languageName: node linkType: hard -"@opentelemetry/context-async-hooks@npm:^1.25.1": - version: 1.27.0 - resolution: "@opentelemetry/context-async-hooks@npm:1.27.0" - peerDependencies: - "@opentelemetry/api": ">=1.0.0 <1.10.0" - checksum: 10/a72fdf5754f6e6d829b81031afe1a8e48a66bb02b13014e05c3fbb9c31fc736f7d303b0bb3491d200ce951582fe04a2d1c6246359683e8fe1b544929d5fd16c5 +"@lexical/selection@npm:0.17.1, @lexical/selection@npm:^0.17.1": + version: 0.17.1 + resolution: "@lexical/selection@npm:0.17.1" + dependencies: + lexical: "npm:0.17.1" + checksum: 10/131badf5c19ddc0f3ba07d3c7d4dc9d208a2472e12fb27f4086a68ae042020ae027f01bcb176f21b94a3a0156171c655892c600257e5b27e6a07e48d8283186b languageName: node linkType: hard -"@opentelemetry/core@npm:1.26.0": - version: 1.26.0 - resolution: "@opentelemetry/core@npm:1.26.0" +"@lexical/table@npm:0.17.1": + version: 0.17.1 + resolution: "@lexical/table@npm:0.17.1" dependencies: - "@opentelemetry/semantic-conventions": "npm:1.27.0" - peerDependencies: - "@opentelemetry/api": ">=1.0.0 <1.10.0" - checksum: 10/474b6bcf42cd2825d56f915eb0d6e6cdcb37777a11fc2618fc2fa50754f4b9b5df23944f3aab186cb3ab930db5c3a81efa3183362802314a966930110346e6a4 + "@lexical/utils": "npm:0.17.1" + lexical: "npm:0.17.1" + checksum: 10/95d781e73d2c5924cf94bddb431e5bf00efb6b805fac873b3709fbb1de6f3aac98cd92d0916a10b3c940b3d8df49d04f091ffbb1a872b5665957cb810b3de040 languageName: node linkType: hard -"@opentelemetry/core@npm:1.27.0, @opentelemetry/core@npm:^1.1.0, @opentelemetry/core@npm:^1.25.1, @opentelemetry/core@npm:^1.8.0": - version: 1.27.0 - resolution: "@opentelemetry/core@npm:1.27.0" +"@lexical/text@npm:0.17.1": + version: 0.17.1 + resolution: "@lexical/text@npm:0.17.1" dependencies: - "@opentelemetry/semantic-conventions": "npm:1.27.0" - peerDependencies: - "@opentelemetry/api": ">=1.0.0 <1.10.0" - checksum: 10/2e64f35f7f8a53c035eb7e2335c73a6bca0f12a0d45cd8171646492d5efb73f82fb29aae77f34b2d6e93498b38172dee8e5cf769727c44ac08be0d5b21da7512 + lexical: "npm:0.17.1" + checksum: 10/ba2e7a7191a2ca1fc7b8889e0eabb8a7d4633d391b11f53a45b1bfbe9930a787ccf6baf29826bd03fa55f8f83eebf4f20592417c00e97ddf21810afd2690b8db languageName: node linkType: hard -"@opentelemetry/instrumentation-amqplib@npm:^0.43.0": - version: 0.43.0 - resolution: "@opentelemetry/instrumentation-amqplib@npm:0.43.0" +"@lexical/utils@npm:0.17.1, @lexical/utils@npm:^0.17.1": + version: 0.17.1 + resolution: "@lexical/utils@npm:0.17.1" dependencies: - "@opentelemetry/core": "npm:^1.8.0" - "@opentelemetry/instrumentation": "npm:^0.54.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/5d632e1b1ee8ac6a596aed90aa9e0fe5c9b0a5e1dd34d2bb209bf19227d945f535d07e6d03a2325ae1e90858923535a356745ef6580723531e532923b178d039 + "@lexical/list": "npm:0.17.1" + "@lexical/selection": "npm:0.17.1" + "@lexical/table": "npm:0.17.1" + lexical: "npm:0.17.1" + checksum: 10/da22cabe156580a8c18b998979bd891afc23a7a6c90cc4eb3406a38ebbed1c741fcd72e81d8eaad4dc937771a4d6a9841f51d7d4b219eb1665bcd81dc03909c0 languageName: node linkType: hard -"@opentelemetry/instrumentation-connect@npm:0.40.0": - version: 0.40.0 - resolution: "@opentelemetry/instrumentation-connect@npm:0.40.0" +"@lexical/yjs@npm:0.17.1": + version: 0.17.1 + resolution: "@lexical/yjs@npm:0.17.1" dependencies: - "@opentelemetry/core": "npm:^1.8.0" - "@opentelemetry/instrumentation": "npm:^0.54.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" - "@types/connect": "npm:3.4.36" + "@lexical/offset": "npm:0.17.1" + lexical: "npm:0.17.1" peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/31d6adb3fbc04d4e831730562f57f8c54c6844e5214a31c70d5e855b7363202822d320cca603132ff9e4b4a597ecd4dcfb32ca2725cf5cd226fa239bf8fcd779 + yjs: ">=13.5.22" + checksum: 10/d339a7b1ddd75c7bea6909b1c0961a856145d61947f244857160ad23023d9c4fade4e873aff60dc3dc8093006212a1b610f31c208cc558ba02ec18f77792da42 languageName: node linkType: hard -"@opentelemetry/instrumentation-dataloader@npm:0.12.0": - version: 0.12.0 - resolution: "@opentelemetry/instrumentation-dataloader@npm:0.12.0" - dependencies: - "@opentelemetry/instrumentation": "npm:^0.53.0" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/d560b519a6be6572a3bd3707f2035f4e1f8e50b95eee109ee138b9ebfadd1ec7bca288aeabb54e8299746eae9457001162dac6ccd92af5ba7449301e0bb139bd +"@lezer/common@npm:^1.0.0, @lezer/common@npm:^1.0.2, @lezer/common@npm:^1.1.0, @lezer/common@npm:^1.2.0, @lezer/common@npm:^1.2.1": + version: 1.2.3 + resolution: "@lezer/common@npm:1.2.3" + checksum: 10/dad24e353e4e67d88b203191361ca1dff26c01c2b7b4ae829b668a1d115929334d077217367683e39180c0556510ed2066ea8ddba2b079be7c08a7152208cc87 languageName: node linkType: hard -"@opentelemetry/instrumentation-express@npm:0.44.0": - version: 0.44.0 - resolution: "@opentelemetry/instrumentation-express@npm:0.44.0" +"@lezer/cpp@npm:^1.0.0": + version: 1.1.2 + resolution: "@lezer/cpp@npm:1.1.2" dependencies: - "@opentelemetry/core": "npm:^1.8.0" - "@opentelemetry/instrumentation": "npm:^0.54.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/a2ae344c1c2b8346f6957dfadbe4c789a0abf08a5dbcd424c41b320faa5b72d9a399406041792ab6a18093b428958b067d3c66dcd492d9cc5d97a17347d3f88a + "@lezer/common": "npm:^1.2.0" + "@lezer/highlight": "npm:^1.0.0" + "@lezer/lr": "npm:^1.0.0" + checksum: 10/118cb42a0462047d40fa4d8a02a014f2571bafa186d6b59899db9a271951551e4ef507d528da01f6067e81af8cc056a3426a0dac81f4492ed9c1b845511ad8b1 languageName: node linkType: hard -"@opentelemetry/instrumentation-fastify@npm:0.41.0": - version: 0.41.0 - resolution: "@opentelemetry/instrumentation-fastify@npm:0.41.0" +"@lezer/css@npm:^1.1.0, @lezer/css@npm:^1.1.7": + version: 1.1.9 + resolution: "@lezer/css@npm:1.1.9" dependencies: - "@opentelemetry/core": "npm:^1.8.0" - "@opentelemetry/instrumentation": "npm:^0.54.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/6f1af8af8b4ef213a1edde1ba14bc59635078d8953562bc48c431b42503ca91e6a837076093682eb8765a9490b61afdce4e33f966d19c4e436e4c1ffacb70ea1 + "@lezer/common": "npm:^1.2.0" + "@lezer/highlight": "npm:^1.0.0" + "@lezer/lr": "npm:^1.0.0" + checksum: 10/bcc277bb6b806961ebfd03e98faf8e5870dd7da2eb565c43922ac378d926b49f479309b28cc824fd4e17baac2c8952966807d06fbe2a45d53e9a3f1518134729 languageName: node linkType: hard -"@opentelemetry/instrumentation-fs@npm:0.16.0": - version: 0.16.0 - resolution: "@opentelemetry/instrumentation-fs@npm:0.16.0" +"@lezer/go@npm:^1.0.0": + version: 1.0.0 + resolution: "@lezer/go@npm:1.0.0" dependencies: - "@opentelemetry/core": "npm:^1.8.0" - "@opentelemetry/instrumentation": "npm:^0.54.0" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/01ac3a8c488a85cbd63e8cdb62e4ab228af569c05d731c4615ff90a4fe699e2e619b626d6838f03e7aaeb715a695d6e45a5ba4c5a976e748c04276719924efb9 + "@lezer/common": "npm:^1.2.0" + "@lezer/highlight": "npm:^1.0.0" + "@lezer/lr": "npm:^1.0.0" + checksum: 10/3a7a7be931308852261e69f741e5a8edbb731aa53ba9287a103dfd66572894fd26c33c9b6f48df123352fbcf8d937a1fa482a5d7aaec37e402f0443bd99c060e languageName: node linkType: hard -"@opentelemetry/instrumentation-generic-pool@npm:0.39.0": - version: 0.39.0 - resolution: "@opentelemetry/instrumentation-generic-pool@npm:0.39.0" +"@lezer/highlight@npm:^1.0.0, @lezer/highlight@npm:^1.1.3, @lezer/highlight@npm:^1.2.0": + version: 1.2.1 + resolution: "@lezer/highlight@npm:1.2.1" dependencies: - "@opentelemetry/instrumentation": "npm:^0.53.0" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/37b476cdddaf3fa2f83a340dcd6949e70cbead45cf747a953099fdb422cb0e89fd52017d0ca01e74283e5af4caa788eb4d163f81e4f21e6ba8e89d0a0dbc99c5 + "@lezer/common": "npm:^1.0.0" + checksum: 10/fec3082419ee87fb265039b680fbac6796f862d8e3042dcb860e8c5a34291503a74927302b568ff1a626f0d2b5cf8dae02a51cfd200084eb329e5fd1236c3163 languageName: node linkType: hard -"@opentelemetry/instrumentation-graphql@npm:0.44.0": - version: 0.44.0 - resolution: "@opentelemetry/instrumentation-graphql@npm:0.44.0" +"@lezer/html@npm:^1.3.0": + version: 1.3.10 + resolution: "@lezer/html@npm:1.3.10" dependencies: - "@opentelemetry/instrumentation": "npm:^0.54.0" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/fca5234a9adf5bee2b7a0613372e9f5b8cfc4c64243ec84b5fdbae149b1e15ca7398b5eb8c755c8cf82816c7963b080dd6e85d1403e80057dc87ebf81498699a + "@lezer/common": "npm:^1.2.0" + "@lezer/highlight": "npm:^1.0.0" + "@lezer/lr": "npm:^1.0.0" + checksum: 10/b69f796492c0a2c000ebba88e1b1f0dc0885ed3392c11b46a9d7c638da1a325d6a288375fbeec52d0d05437b801302299e0e57359f95a494db1e251b3d66d29c languageName: node linkType: hard -"@opentelemetry/instrumentation-hapi@npm:0.41.0": - version: 0.41.0 - resolution: "@opentelemetry/instrumentation-hapi@npm:0.41.0" +"@lezer/java@npm:^1.0.0": + version: 1.1.3 + resolution: "@lezer/java@npm:1.1.3" dependencies: - "@opentelemetry/core": "npm:^1.8.0" - "@opentelemetry/instrumentation": "npm:^0.53.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/5025db3e785476757947915e9512d454f565eabc883757d7a122e134f3cb2e5d418142f916e5ab4b2db2bfb9c59ab105f602c19af268442ae07106b5b547fa64 + "@lezer/common": "npm:^1.2.0" + "@lezer/highlight": "npm:^1.0.0" + "@lezer/lr": "npm:^1.0.0" + checksum: 10/52865205f67c9d00630c72028d2d6bbb734da04f80e6febe6edb9bbd3ba55a3c0d6cfbdfa7db0ccf7e090b59040047aa9b752ff2d4ab714f3a0139c4d45e0f80 languageName: node linkType: hard -"@opentelemetry/instrumentation-http@npm:0.53.0": - version: 0.53.0 - resolution: "@opentelemetry/instrumentation-http@npm:0.53.0" +"@lezer/javascript@npm:^1.0.0": + version: 1.4.21 + resolution: "@lezer/javascript@npm:1.4.21" dependencies: - "@opentelemetry/core": "npm:1.26.0" - "@opentelemetry/instrumentation": "npm:0.53.0" - "@opentelemetry/semantic-conventions": "npm:1.27.0" - semver: "npm:^7.5.2" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/c00e71f7a5a03723bf13e55e74dcc8e44d61b87fc38c50821fa6bf86a09d3eca68a62a4ccc6f35e70a6529c36d134eca77889852869d7a5a9b2af73f3fb5f097 + "@lezer/common": "npm:^1.2.0" + "@lezer/highlight": "npm:^1.1.3" + "@lezer/lr": "npm:^1.3.0" + checksum: 10/7f8b1f469103e74dc2c39e7e75e6cc670e4cf6f48b5317e2a0e267521c9924641e8de41c6e740af8cc919f5c7e03c0a97fc2f261486c96f1625c3e3bbb23b80a languageName: node linkType: hard -"@opentelemetry/instrumentation-ioredis@npm:0.43.0": - version: 0.43.0 - resolution: "@opentelemetry/instrumentation-ioredis@npm:0.43.0" +"@lezer/json@npm:^1.0.0": + version: 1.0.2 + resolution: "@lezer/json@npm:1.0.2" dependencies: - "@opentelemetry/instrumentation": "npm:^0.53.0" - "@opentelemetry/redis-common": "npm:^0.36.2" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/fa405f521134a375c3ae1894d39da2a62bd021695fbc6a28d7efe61202d9a3b895047cf59353d6773e5d8528aea24a63841110ba48800132f5aac47615603c10 + "@lezer/common": "npm:^1.2.0" + "@lezer/highlight": "npm:^1.0.0" + "@lezer/lr": "npm:^1.0.0" + checksum: 10/f899d13765d95599c9199fc3404cb57969031dc40ce07de30f4e648979153966581f0bee02e2f8f70463b0a5322206a97c2fe8d5d14f218888c72a6dcedf90ef languageName: node linkType: hard -"@opentelemetry/instrumentation-kafkajs@npm:0.4.0": - version: 0.4.0 - resolution: "@opentelemetry/instrumentation-kafkajs@npm:0.4.0" +"@lezer/lr@npm:^1.0.0, @lezer/lr@npm:^1.1.0, @lezer/lr@npm:^1.3.0, @lezer/lr@npm:^1.3.1, @lezer/lr@npm:^1.3.3, @lezer/lr@npm:^1.4.0": + version: 1.4.2 + resolution: "@lezer/lr@npm:1.4.2" dependencies: - "@opentelemetry/instrumentation": "npm:^0.54.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/e5abcbbf2a458c3754d8a5790cf364384c84f51929ec66973ae1390020ef945a4be3d42db214a6738362a9d319e03ad6df0abc9470b2107568728d1e42f7ea94 + "@lezer/common": "npm:^1.0.0" + checksum: 10/f7b505906c8d8df14c07866553cf3dae1e065b1da8b28fbb4193fd67ab8d187eb45f92759e29a2cfe4283296f0aa864b38a0a91708ecfc3e24b8f662d626e0c6 languageName: node linkType: hard -"@opentelemetry/instrumentation-knex@npm:0.41.0": - version: 0.41.0 - resolution: "@opentelemetry/instrumentation-knex@npm:0.41.0" +"@lezer/markdown@npm:^1.0.0": + version: 1.3.2 + resolution: "@lezer/markdown@npm:1.3.2" dependencies: - "@opentelemetry/instrumentation": "npm:^0.54.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/273dbaf08f5256e2f8390b7846532baba6f4f9f39593c01d7fc756af346906b21214ac7b7007d4b7b7c2279acda4180b9ac89bc0e40befc1ccec80a64e2b75cb + "@lezer/common": "npm:^1.0.0" + "@lezer/highlight": "npm:^1.0.0" + checksum: 10/f234f3d9eb2217a89828a7299b0e1b00c3d3ee3f4ab6251735895ad065080a7f59711a4a116a022f2b03ca0a2a72e7e2709f211e0729b5a800753dec9b3ae2d8 languageName: node linkType: hard -"@opentelemetry/instrumentation-koa@npm:0.43.0": - version: 0.43.0 - resolution: "@opentelemetry/instrumentation-koa@npm:0.43.0" +"@lezer/php@npm:^1.0.0": + version: 1.0.2 + resolution: "@lezer/php@npm:1.0.2" dependencies: - "@opentelemetry/core": "npm:^1.8.0" - "@opentelemetry/instrumentation": "npm:^0.53.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/b494196962c0840651e5fdec7350a8d9f443ee9e682e4c20c8b47ed82c6c34875adc7fd467ac04c3838edbf14bf79aafddb889f2755fc1957f27275a08442e83 + "@lezer/common": "npm:^1.2.0" + "@lezer/highlight": "npm:^1.0.0" + "@lezer/lr": "npm:^1.1.0" + checksum: 10/aa48ba3348ed5ba29a6988eb073ec9ccb286f84988058942619306537c55561edccbd4c3f1f9b53add3445f21e11ef943630a10864e437dce8c44808ebabd69a languageName: node linkType: hard -"@opentelemetry/instrumentation-lru-memoizer@npm:0.40.0": - version: 0.40.0 - resolution: "@opentelemetry/instrumentation-lru-memoizer@npm:0.40.0" +"@lezer/python@npm:^1.1.4": + version: 1.1.15 + resolution: "@lezer/python@npm:1.1.15" dependencies: - "@opentelemetry/instrumentation": "npm:^0.53.0" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/07bb795faedb0c01bf7dd2cc660431b2303fd1f3a904b3fcc06eb601fde94653f8391a40ccf101a391893187a68381ab6ea8a284118fff328d32b130fac2ea6c + "@lezer/common": "npm:^1.2.0" + "@lezer/highlight": "npm:^1.0.0" + "@lezer/lr": "npm:^1.0.0" + checksum: 10/fab171329e37dc5fda1fc43d61a3b96cd9ccab2c5ec9628a6a7d63f75c03bbdf1e6b7027b94ff729f64cec9167469bb1e67d68d4708a6d25e84396c277f84fd2 languageName: node linkType: hard -"@opentelemetry/instrumentation-mongodb@npm:0.48.0": - version: 0.48.0 - resolution: "@opentelemetry/instrumentation-mongodb@npm:0.48.0" +"@lezer/rust@npm:^1.0.0": + version: 1.0.2 + resolution: "@lezer/rust@npm:1.0.2" dependencies: - "@opentelemetry/instrumentation": "npm:^0.54.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/84b6cef3d80086a05783c211cbfb62bf9ef59c9b17dcfd943fb13570eb2fe45c91dd07823a2aa81e2f81e3689a20cf0d02c6f765d4f9a1af67b75b44cd0293c9 + "@lezer/common": "npm:^1.2.0" + "@lezer/highlight": "npm:^1.0.0" + "@lezer/lr": "npm:^1.0.0" + checksum: 10/ea6152f65e42afed45e780c4adc87e19e33586de6790ac8dd2012309f22721f87454e6bfee1690e222ce30436ae380270e93cdfc9eb8cdda52930d86e1eb0c07 languageName: node linkType: hard -"@opentelemetry/instrumentation-mongoose@npm:0.42.0": - version: 0.42.0 - resolution: "@opentelemetry/instrumentation-mongoose@npm:0.42.0" +"@lezer/sass@npm:^1.0.0": + version: 1.0.7 + resolution: "@lezer/sass@npm:1.0.7" dependencies: - "@opentelemetry/core": "npm:^1.8.0" - "@opentelemetry/instrumentation": "npm:^0.53.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/58c3ba89ce43830451dcc105a2ebf352b296cf6b1b8f6194ac69c1fa39c18e50ee0092f8e514a27046cf35e0ade391425f7adf0e6e6b1fd8dbbec2b01f393be2 + "@lezer/common": "npm:^1.2.0" + "@lezer/highlight": "npm:^1.0.0" + "@lezer/lr": "npm:^1.0.0" + checksum: 10/a7531cbf0766d3875480c24ae8929dd56053d71f9eaef8815e6f468e2252e66e38236a3f89395463e4cee43230dc35351386bedc6138cf1a19a44a64ce58a372 languageName: node linkType: hard -"@opentelemetry/instrumentation-mysql2@npm:0.41.0": - version: 0.41.0 - resolution: "@opentelemetry/instrumentation-mysql2@npm:0.41.0" +"@lezer/xml@npm:^1.0.0": + version: 1.0.5 + resolution: "@lezer/xml@npm:1.0.5" dependencies: - "@opentelemetry/instrumentation": "npm:^0.53.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" - "@opentelemetry/sql-common": "npm:^0.40.1" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/40f48b3f87bda347db2332020f0880223f49a894e0312d03e1f86aa48b8335b6db65955ea775b8bec2a687672bdbd9c0997294acdd4cf51765da0e22e1d98a35 + "@lezer/common": "npm:^1.2.0" + "@lezer/highlight": "npm:^1.0.0" + "@lezer/lr": "npm:^1.0.0" + checksum: 10/65cdfd22bd05e22d27a16a53716ad21d0385462b3a3cd2c18a2300cec4b939402f70c1105c4602b392db67b87de4daa56d8903bd28a26e88a25ea5a66002740d languageName: node linkType: hard -"@opentelemetry/instrumentation-mysql@npm:0.41.0": - version: 0.41.0 - resolution: "@opentelemetry/instrumentation-mysql@npm:0.41.0" - dependencies: - "@opentelemetry/instrumentation": "npm:^0.53.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" - "@types/mysql": "npm:2.15.26" +"@lezer/yaml@npm:^1.0.0": + version: 1.0.3 + resolution: "@lezer/yaml@npm:1.0.3" + dependencies: + "@lezer/common": "npm:^1.2.0" + "@lezer/highlight": "npm:^1.0.0" + "@lezer/lr": "npm:^1.4.0" + checksum: 10/6697b964403dc5dec9186732c5997675e5140ef5dddc8371dd28fa194d8431d8a7d5f18670be47b81a0b4ad6cbfe82e4f7c9c6f06e6f763bd100f7a38908baf5 + languageName: node + linkType: hard + +"@mdxeditor/editor@npm:^3.20.0": + version: 3.20.0 + resolution: "@mdxeditor/editor@npm:3.20.0" + dependencies: + "@codemirror/lang-markdown": "npm:^6.2.3" + "@codemirror/language-data": "npm:^6.5.1" + "@codemirror/merge": "npm:^6.4.0" + "@codemirror/state": "npm:^6.4.0" + "@codemirror/view": "npm:^6.23.0" + "@codesandbox/sandpack-react": "npm:^2.10.0" + "@lexical/clipboard": "npm:^0.17.1" + "@lexical/link": "npm:^0.17.1" + "@lexical/list": "npm:^0.17.1" + "@lexical/markdown": "npm:^0.17.1" + "@lexical/plain-text": "npm:^0.17.1" + "@lexical/react": "npm:^0.17.1" + "@lexical/rich-text": "npm:^0.17.1" + "@lexical/selection": "npm:^0.17.1" + "@lexical/utils": "npm:^0.17.1" + "@mdxeditor/gurx": "npm:^1.1.4" + "@radix-ui/colors": "npm:^3.0.0" + "@radix-ui/react-dialog": "npm:^1.0.5" + "@radix-ui/react-icons": "npm:^1.3.0" + "@radix-ui/react-popover": "npm:^1.0.7" + "@radix-ui/react-select": "npm:^2.0.0" + "@radix-ui/react-toggle-group": "npm:^1.0.4" + "@radix-ui/react-toolbar": "npm:^1.0.4" + "@radix-ui/react-tooltip": "npm:^1.0.7" + classnames: "npm:^2.3.2" + cm6-theme-basic-light: "npm:^0.2.0" + codemirror: "npm:^6.0.1" + downshift: "npm:^7.6.0" + js-yaml: "npm:4.1.0" + lexical: "npm:^0.17.1" + mdast-util-directive: "npm:^3.0.0" + mdast-util-from-markdown: "npm:^2.0.0" + mdast-util-frontmatter: "npm:^2.0.1" + mdast-util-gfm-strikethrough: "npm:^2.0.0" + mdast-util-gfm-table: "npm:^2.0.0" + mdast-util-gfm-task-list-item: "npm:^2.0.0" + mdast-util-mdx: "npm:^3.0.0" + mdast-util-mdx-jsx: "npm:^3.0.0" + mdast-util-to-markdown: "npm:^2.1.0" + micromark-extension-directive: "npm:^3.0.0" + micromark-extension-frontmatter: "npm:^2.0.0" + micromark-extension-gfm-strikethrough: "npm:^2.0.0" + micromark-extension-gfm-table: "npm:^2.0.0" + micromark-extension-gfm-task-list-item: "npm:^2.0.1" + micromark-extension-mdx-jsx: "npm:^3.0.0" + micromark-extension-mdx-md: "npm:^2.0.0" + micromark-extension-mdxjs: "npm:^3.0.0" + micromark-factory-space: "npm:^2.0.0" + micromark-util-character: "npm:^2.0.1" + micromark-util-symbol: "npm:^2.0.0" + react-hook-form: "npm:^7.44.2" + unidiff: "npm:^1.0.2" peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/20ff56edc0b74cf8be2dd5960e210a6c20568169af5768fd78bb33f5a626e271fe2ac6cf7ad0e9629ff932a18feac04db99fffa3c867b27c679523dd2f4570d3 + react: ">= 18 || >= 19" + react-dom: ">= 18 || >= 19" + checksum: 10/c447fdc3ec14056462078cd7137c2f1f602705ac2411ab80acda126604e4effd0eb7a05f8910ebbb2ed11a5a8d87e8181bbc3c5760fd44abd71b4efffbf8b245 languageName: node linkType: hard -"@opentelemetry/instrumentation-nestjs-core@npm:0.40.0": - version: 0.40.0 - resolution: "@opentelemetry/instrumentation-nestjs-core@npm:0.40.0" - dependencies: - "@opentelemetry/instrumentation": "npm:^0.53.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" +"@mdxeditor/gurx@npm:^1.1.4": + version: 1.2.3 + resolution: "@mdxeditor/gurx@npm:1.2.3" peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/421f3e18c651b74383d5cd6a231431ecda3e49262f934dca27bf2272fe58334cbe2acf2f62ce5d82c0893d6f899e2921dfc6a6f78ab27f84a35bd8bfb77df9e4 + react: ">= 18 || >= 19" + react-dom: ">= 18 || >= 19" + checksum: 10/ea637a60566c7d98abb57c747c7e1086fd1e73dae58b18c3c2c7d5f0e1f4a98308bc1f52ba410b6b8bb32af28d60334e900f2eaad0a6ca04c0442ba3931345ec languageName: node linkType: hard -"@opentelemetry/instrumentation-pg@npm:0.44.0": - version: 0.44.0 - resolution: "@opentelemetry/instrumentation-pg@npm:0.44.0" - dependencies: - "@opentelemetry/instrumentation": "npm:^0.53.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" - "@opentelemetry/sql-common": "npm:^0.40.1" - "@types/pg": "npm:8.6.1" - "@types/pg-pool": "npm:2.0.6" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/d902682a3630ff1ef392624165b46a2b4fe0fd696f42a588030f2c4ba73ccd2631792cf6b122bad0dfddb929044b96c285f63517704e7ccaf699a77150f5f3d9 +"@mixmark-io/domino@npm:^2.2.0": + version: 2.2.0 + resolution: "@mixmark-io/domino@npm:2.2.0" + checksum: 10/839624ba6baab655c4f7393e8b8561516849926651e02f40484729b9869436b1e077906810bcac0bba4762448512d3ebd2f6d9b463d8ab0d5f54d75ca5306519 languageName: node linkType: hard -"@opentelemetry/instrumentation-redis-4@npm:0.42.0": - version: 0.42.0 - resolution: "@opentelemetry/instrumentation-redis-4@npm:0.42.0" - dependencies: - "@opentelemetry/instrumentation": "npm:^0.53.0" - "@opentelemetry/redis-common": "npm:^0.36.2" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/d5ff240b826525cdc9935ab2885f65ea5c5d77ad31e9ee8142e6840b1c1603db025370b67fb828580a242fe7ff815d1335ff3845c48d8b94070f3683f71b0898 +"@nicolo-ribaudo/chokidar-2@npm:2.1.8-no-fsevents.3": + version: 2.1.8-no-fsevents.3 + resolution: "@nicolo-ribaudo/chokidar-2@npm:2.1.8-no-fsevents.3" + checksum: 10/c6e83af3b5051a3f6562649ff8fe37de9934a4cc02138678ed1badbd13ed3334f7ae5f63f2bbc3432210f6b245f082ac97e9b2afe0c13730c9838b295658c185 languageName: node linkType: hard -"@opentelemetry/instrumentation-tedious@npm:0.15.0": - version: 0.15.0 - resolution: "@opentelemetry/instrumentation-tedious@npm:0.15.0" +"@nodelib/fs.scandir@npm:2.1.5": + version: 2.1.5 + resolution: "@nodelib/fs.scandir@npm:2.1.5" dependencies: - "@opentelemetry/instrumentation": "npm:^0.54.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" - "@types/tedious": "npm:^4.0.14" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/adbc5a444a28c4732aafd8a8742e1ffea100325a91005b8417cd50625952c537842f3d6f6c9c29aa468e0b5d850b285fa43617cde0bcbc463da207b38d0eee08 + "@nodelib/fs.stat": "npm:2.0.5" + run-parallel: "npm:^1.1.9" + checksum: 10/6ab2a9b8a1d67b067922c36f259e3b3dfd6b97b219c540877a4944549a4d49ea5ceba5663905ab5289682f1f3c15ff441d02f0447f620a42e1cb5e1937174d4b languageName: node linkType: hard -"@opentelemetry/instrumentation-undici@npm:0.6.0": - version: 0.6.0 - resolution: "@opentelemetry/instrumentation-undici@npm:0.6.0" +"@nodelib/fs.scandir@npm:3.0.0": + version: 3.0.0 + resolution: "@nodelib/fs.scandir@npm:3.0.0" dependencies: - "@opentelemetry/core": "npm:^1.8.0" - "@opentelemetry/instrumentation": "npm:^0.53.0" - peerDependencies: - "@opentelemetry/api": ^1.7.0 - checksum: 10/97291ecca9ff936dc4a418b380542f4dbb1f891692df44292dd61dc9e39aa1c347b70666cda5c30fbd78969d3b6ea602a6bafb30566b65eec0e00bcac459b2c4 + "@nodelib/fs.stat": "npm:3.0.0" + run-parallel: "npm:^1.2.0" + checksum: 10/5d4e48148b046afbdd7202e6484fb246412abfce6c3bec004eab663abf582e471a17d63c28b9f6638ff385a69f67c7abc4786d28e2de6b8d177687fe3b1c5a9e languageName: node linkType: hard -"@opentelemetry/instrumentation@npm:0.53.0, @opentelemetry/instrumentation@npm:^0.53.0": - version: 0.53.0 - resolution: "@opentelemetry/instrumentation@npm:0.53.0" +"@nodelib/fs.stat@npm:2.0.5, @nodelib/fs.stat@npm:^2.0.2": + version: 2.0.5 + resolution: "@nodelib/fs.stat@npm:2.0.5" + checksum: 10/012480b5ca9d97bff9261571dbbec7bbc6033f69cc92908bc1ecfad0792361a5a1994bc48674b9ef76419d056a03efadfce5a6cf6dbc0a36559571a7a483f6f0 + languageName: node + linkType: hard + +"@nodelib/fs.stat@npm:3.0.0": + version: 3.0.0 + resolution: "@nodelib/fs.stat@npm:3.0.0" + checksum: 10/93a93e19b64d0275b5120bed2cf85da4c5804014de1bdac6e9933b835b1cb9f88252dc990b148076bec034fc757bdd97d74cf5d99bc9f895e0f925aeabe7dbcf + languageName: node + linkType: hard + +"@nodelib/fs.walk@npm:^1.2.3": + version: 1.2.8 + resolution: "@nodelib/fs.walk@npm:1.2.8" dependencies: - "@opentelemetry/api-logs": "npm:0.53.0" - "@types/shimmer": "npm:^1.2.0" - import-in-the-middle: "npm:^1.8.1" - require-in-the-middle: "npm:^7.1.1" - semver: "npm:^7.5.2" - shimmer: "npm:^1.2.1" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/4b994c8568a503a15655cba249b1dbdef3f67dfda37938abba6267ba75b6d72a9aa276be4b0c8874e86f98ab89d92877e1874e0565a7e67f062c43dfcbbb16a5 + "@nodelib/fs.scandir": "npm:2.1.5" + fastq: "npm:^1.6.0" + checksum: 10/40033e33e96e97d77fba5a238e4bba4487b8284678906a9f616b5579ddaf868a18874c0054a75402c9fbaaa033a25ceae093af58c9c30278e35c23c9479e79b0 languageName: node linkType: hard -"@opentelemetry/instrumentation@npm:^0.49 || ^0.50 || ^0.51 || ^0.52.0": - version: 0.52.1 - resolution: "@opentelemetry/instrumentation@npm:0.52.1" +"@nodelib/fs.walk@npm:^2.0.0": + version: 2.0.0 + resolution: "@nodelib/fs.walk@npm:2.0.0" dependencies: - "@opentelemetry/api-logs": "npm:0.52.1" - "@types/shimmer": "npm:^1.0.2" - import-in-the-middle: "npm:^1.8.1" - require-in-the-middle: "npm:^7.1.1" - semver: "npm:^7.5.2" - shimmer: "npm:^1.2.1" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/87761bd593f2b905d88d0531a3a2a7f4b0186334ae413b4c172a86bd4de0fd6d2f906a1bfd9dd7bd172a228a44fa7a680f5802a1570dfe2fadad0768e80bd7a8 + "@nodelib/fs.scandir": "npm:3.0.0" + fastq: "npm:^1.15.0" + checksum: 10/0b343472c9a8feb0f928292a4990206e69117cc0b124522bbf576b04e1233d8f04575923a3badde17d74c2b5b3092fa705fad63fa612bf8fd7378b1d908d7061 languageName: node linkType: hard -"@opentelemetry/instrumentation@npm:^0.54.0": - version: 0.54.2 - resolution: "@opentelemetry/instrumentation@npm:0.54.2" +"@npmcli/agent@npm:^2.0.0": + version: 2.2.2 + resolution: "@npmcli/agent@npm:2.2.2" dependencies: - "@opentelemetry/api-logs": "npm:0.54.2" - "@types/shimmer": "npm:^1.2.0" - import-in-the-middle: "npm:^1.8.1" - require-in-the-middle: "npm:^7.1.1" - semver: "npm:^7.5.2" - shimmer: "npm:^1.2.1" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/1c570fb2e55d2ea7dcc45103afb53ffc331efb675dc404783639c0ed4c93e4e0fa04751672f75ca2a633ca03943e520cf802ee0291e79fa33be54a097af46fc6 + agent-base: "npm:^7.1.0" + http-proxy-agent: "npm:^7.0.0" + https-proxy-agent: "npm:^7.0.1" + lru-cache: "npm:^10.0.1" + socks-proxy-agent: "npm:^8.0.3" + checksum: 10/96fc0036b101bae5032dc2a4cd832efb815ce9b33f9ee2f29909ee49d96a0026b3565f73c507a69eb8603f5cb32e0ae45a70cab1e2655990a4e06ae99f7f572a languageName: node linkType: hard -"@opentelemetry/redis-common@npm:^0.36.2": - version: 0.36.2 - resolution: "@opentelemetry/redis-common@npm:0.36.2" - checksum: 10/e7f610f79c95bab9156a9831162c7b55b94ab43c5e47ecb9efcc10c08a236395fdd54b6bb018da981e6641bac9da6fda1b50636fb49db584e87d988750d255e1 +"@npmcli/fs@npm:^3.1.0": + version: 3.1.1 + resolution: "@npmcli/fs@npm:3.1.1" + dependencies: + semver: "npm:^7.3.5" + checksum: 10/1e0e04087049b24b38bc0b30d87a9388ee3ca1d3fdfc347c2f77d84fcfe6a51f250bc57ba2c1f614d7e4285c6c62bf8c769bc19aa0949ea39e5b043ee023b0bd languageName: node linkType: hard -"@opentelemetry/resources@npm:1.27.0, @opentelemetry/resources@npm:^1.26.0": - version: 1.27.0 - resolution: "@opentelemetry/resources@npm:1.27.0" +"@npmcli/git@npm:^5.0.0": + version: 5.0.8 + resolution: "@npmcli/git@npm:5.0.8" dependencies: - "@opentelemetry/core": "npm:1.27.0" - "@opentelemetry/semantic-conventions": "npm:1.27.0" - peerDependencies: - "@opentelemetry/api": ">=1.0.0 <1.10.0" - checksum: 10/654141ea65854bba84c22eeecc5af0054f14462f2664f36ac1ad8a170404e3218fccb98cafaaff4ec45e85523230e58eafbf222c25d00de8a60141ce77a34bbf + "@npmcli/promise-spawn": "npm:^7.0.0" + ini: "npm:^4.1.3" + lru-cache: "npm:^10.0.1" + npm-pick-manifest: "npm:^9.0.0" + proc-log: "npm:^4.0.0" + promise-inflight: "npm:^1.0.1" + promise-retry: "npm:^2.0.1" + semver: "npm:^7.3.5" + which: "npm:^4.0.0" + checksum: 10/e6f94175fb9dde13d84849b29b32ffb4c4df968822cc85df2aebfca13bf8ca76f33b1d281911f5bcddc95bccba2f9e795669c736a38de4d9c76efb5047ffb4fb languageName: node linkType: hard -"@opentelemetry/sdk-trace-base@npm:^1.22, @opentelemetry/sdk-trace-base@npm:^1.26.0": - version: 1.27.0 - resolution: "@opentelemetry/sdk-trace-base@npm:1.27.0" +"@npmcli/package-json@npm:^5.2.0": + version: 5.2.1 + resolution: "@npmcli/package-json@npm:5.2.1" dependencies: - "@opentelemetry/core": "npm:1.27.0" - "@opentelemetry/resources": "npm:1.27.0" - "@opentelemetry/semantic-conventions": "npm:1.27.0" - peerDependencies: - "@opentelemetry/api": ">=1.0.0 <1.10.0" - checksum: 10/e0023dedbf5a50265729dd5467be0504f04f6b43d4cc4b914a9a8c082cca1aec9250a1f31c615f3d4ad03124af6e0ba1e14b39fe745d14291198f2a0990edc9c + "@npmcli/git": "npm:^5.0.0" + glob: "npm:^10.2.2" + hosted-git-info: "npm:^7.0.0" + json-parse-even-better-errors: "npm:^3.0.0" + normalize-package-data: "npm:^6.0.0" + proc-log: "npm:^4.0.0" + semver: "npm:^7.5.3" + checksum: 10/304a819b93f79a6e0e56cb371961a66d2db72142e310d545ecbbbe4d917025a30601aa8e63a5f0cc28f0fe281c116bdaf79b334619b105a1d027a2b769ecd137 languageName: node linkType: hard -"@opentelemetry/semantic-conventions@npm:1.27.0, @opentelemetry/semantic-conventions@npm:^1.27.0": - version: 1.27.0 - resolution: "@opentelemetry/semantic-conventions@npm:1.27.0" - checksum: 10/98166522f299e2fe3d43376adbdeb92679b75ebb172e2a3c4c71f2942bd91585e9537618efbbae6dc08177699e5719368edf66d7e69e8636f360b85217bbdbe1 +"@npmcli/promise-spawn@npm:^7.0.0": + version: 7.0.2 + resolution: "@npmcli/promise-spawn@npm:7.0.2" + dependencies: + which: "npm:^4.0.0" + checksum: 10/94cbbbeeb20342026c3b68fc8eb09e1600b7645d4e509f2588ef5ea7cff977eb01e628cc8e014595d04a6af4b4bc5c467c950a8135920f39f7c7b57fba43f4e9 languageName: node linkType: hard -"@opentelemetry/sql-common@npm:^0.40.1": - version: 0.40.1 - resolution: "@opentelemetry/sql-common@npm:0.40.1" +"@open-draft/deferred-promise@npm:^2.1.0": + version: 2.2.0 + resolution: "@open-draft/deferred-promise@npm:2.2.0" + checksum: 10/bc3bb1668a555bb87b33383cafcf207d9561e17d2ca0d9e61b7ce88e82b66e36a333d3676c1d39eb5848022c03c8145331fcdc828ba297f88cb1de9c5cef6c19 + languageName: node + linkType: hard + +"@openspacelabs/react-native-zoomable-view@npm:2.2.0": + version: 2.2.0 + resolution: "@openspacelabs/react-native-zoomable-view@npm:2.2.0" dependencies: - "@opentelemetry/core": "npm:^1.1.0" + prop-types: "npm:^15.7.2" peerDependencies: - "@opentelemetry/api": ^1.1.0 - checksum: 10/f887b4135be56c9ef6e29f040c9f75f34709e38c11897d59d284d7e73175a2dd2c6267c18061144e81a0045fc461b7813769db2e49c42a8d6becc58b1456d55c + react: ">=16.8.0" + react-native: ">=0.54.0" + checksum: 10/46ff36b5d9250a0aa06b760b0393e0a7403b654c85db49fc639bb9286cc67d8b1c380f3f8102e59c8895e934ae2f6ccc5b3213fb480eafceb078509c9a13405b languageName: node linkType: hard -"@parcel/watcher-android-arm64@npm:2.5.0": - version: 2.5.0 - resolution: "@parcel/watcher-android-arm64@npm:2.5.0" - conditions: os=android & cpu=arm64 +"@opentelemetry/api-logs@npm:0.52.1": + version: 0.52.1 + resolution: "@opentelemetry/api-logs@npm:0.52.1" + dependencies: + "@opentelemetry/api": "npm:^1.0.0" + checksum: 10/7515667a41a38014ffda70674c0b77c9c68417cde9f8ce8840e675308b4431f99d879e8d347f1b08486561617f914c07ee704ad6ed8a6522dabc3a81ac39dc88 languageName: node linkType: hard -"@parcel/watcher-darwin-arm64@npm:2.5.0": - version: 2.5.0 - resolution: "@parcel/watcher-darwin-arm64@npm:2.5.0" - conditions: os=darwin & cpu=arm64 +"@opentelemetry/api-logs@npm:0.53.0": + version: 0.53.0 + resolution: "@opentelemetry/api-logs@npm:0.53.0" + dependencies: + "@opentelemetry/api": "npm:^1.0.0" + checksum: 10/347b4554d6ee01afb29bd39e8f9cbbccd80abb0883fe6a84e3bcce8ab4dbfe357a2729246d2f66de0de6272846fd1bb2d71e286e18ad2690d9e7f46f02f00f73 languageName: node linkType: hard -"@parcel/watcher-darwin-x64@npm:2.5.0": - version: 2.5.0 - resolution: "@parcel/watcher-darwin-x64@npm:2.5.0" - conditions: os=darwin & cpu=x64 +"@opentelemetry/api-logs@npm:0.54.2": + version: 0.54.2 + resolution: "@opentelemetry/api-logs@npm:0.54.2" + dependencies: + "@opentelemetry/api": "npm:^1.3.0" + checksum: 10/97d887be03ca4a2e69574cc9160464bda00f2a167cc850656ade44b6690a75855d9334983b73827dc44c3672958bc478197f261eae11c2ac68a6df9260c9c3df languageName: node linkType: hard -"@parcel/watcher-freebsd-x64@npm:2.5.0": - version: 2.5.0 - resolution: "@parcel/watcher-freebsd-x64@npm:2.5.0" - conditions: os=freebsd & cpu=x64 +"@opentelemetry/api@npm:^1.0.0, @opentelemetry/api@npm:^1.3.0, @opentelemetry/api@npm:^1.8, @opentelemetry/api@npm:^1.9.0": + version: 1.9.0 + resolution: "@opentelemetry/api@npm:1.9.0" + checksum: 10/a607f0eef971893c4f2ee2a4c2069aade6ec3e84e2a1f5c2aac19f65c5d9eeea41aa72db917c1029faafdd71789a1a040bdc18f40d63690e22ccae5d7070f194 languageName: node linkType: hard -"@parcel/watcher-linux-arm-glibc@npm:2.5.0": - version: 2.5.0 - resolution: "@parcel/watcher-linux-arm-glibc@npm:2.5.0" - conditions: os=linux & cpu=arm & libc=glibc +"@opentelemetry/context-async-hooks@npm:^1.25.1": + version: 1.27.0 + resolution: "@opentelemetry/context-async-hooks@npm:1.27.0" + peerDependencies: + "@opentelemetry/api": ">=1.0.0 <1.10.0" + checksum: 10/a72fdf5754f6e6d829b81031afe1a8e48a66bb02b13014e05c3fbb9c31fc736f7d303b0bb3491d200ce951582fe04a2d1c6246359683e8fe1b544929d5fd16c5 + languageName: node + linkType: hard + +"@opentelemetry/core@npm:1.26.0": + version: 1.26.0 + resolution: "@opentelemetry/core@npm:1.26.0" + dependencies: + "@opentelemetry/semantic-conventions": "npm:1.27.0" + peerDependencies: + "@opentelemetry/api": ">=1.0.0 <1.10.0" + checksum: 10/474b6bcf42cd2825d56f915eb0d6e6cdcb37777a11fc2618fc2fa50754f4b9b5df23944f3aab186cb3ab930db5c3a81efa3183362802314a966930110346e6a4 + languageName: node + linkType: hard + +"@opentelemetry/core@npm:1.27.0, @opentelemetry/core@npm:^1.1.0, @opentelemetry/core@npm:^1.25.1, @opentelemetry/core@npm:^1.8.0": + version: 1.27.0 + resolution: "@opentelemetry/core@npm:1.27.0" + dependencies: + "@opentelemetry/semantic-conventions": "npm:1.27.0" + peerDependencies: + "@opentelemetry/api": ">=1.0.0 <1.10.0" + checksum: 10/2e64f35f7f8a53c035eb7e2335c73a6bca0f12a0d45cd8171646492d5efb73f82fb29aae77f34b2d6e93498b38172dee8e5cf769727c44ac08be0d5b21da7512 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-amqplib@npm:^0.43.0": + version: 0.43.0 + resolution: "@opentelemetry/instrumentation-amqplib@npm:0.43.0" + dependencies: + "@opentelemetry/core": "npm:^1.8.0" + "@opentelemetry/instrumentation": "npm:^0.54.0" + "@opentelemetry/semantic-conventions": "npm:^1.27.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/5d632e1b1ee8ac6a596aed90aa9e0fe5c9b0a5e1dd34d2bb209bf19227d945f535d07e6d03a2325ae1e90858923535a356745ef6580723531e532923b178d039 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-connect@npm:0.40.0": + version: 0.40.0 + resolution: "@opentelemetry/instrumentation-connect@npm:0.40.0" + dependencies: + "@opentelemetry/core": "npm:^1.8.0" + "@opentelemetry/instrumentation": "npm:^0.54.0" + "@opentelemetry/semantic-conventions": "npm:^1.27.0" + "@types/connect": "npm:3.4.36" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/31d6adb3fbc04d4e831730562f57f8c54c6844e5214a31c70d5e855b7363202822d320cca603132ff9e4b4a597ecd4dcfb32ca2725cf5cd226fa239bf8fcd779 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-dataloader@npm:0.12.0": + version: 0.12.0 + resolution: "@opentelemetry/instrumentation-dataloader@npm:0.12.0" + dependencies: + "@opentelemetry/instrumentation": "npm:^0.53.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/d560b519a6be6572a3bd3707f2035f4e1f8e50b95eee109ee138b9ebfadd1ec7bca288aeabb54e8299746eae9457001162dac6ccd92af5ba7449301e0bb139bd + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-express@npm:0.44.0": + version: 0.44.0 + resolution: "@opentelemetry/instrumentation-express@npm:0.44.0" + dependencies: + "@opentelemetry/core": "npm:^1.8.0" + "@opentelemetry/instrumentation": "npm:^0.54.0" + "@opentelemetry/semantic-conventions": "npm:^1.27.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/a2ae344c1c2b8346f6957dfadbe4c789a0abf08a5dbcd424c41b320faa5b72d9a399406041792ab6a18093b428958b067d3c66dcd492d9cc5d97a17347d3f88a + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-fastify@npm:0.41.0": + version: 0.41.0 + resolution: "@opentelemetry/instrumentation-fastify@npm:0.41.0" + dependencies: + "@opentelemetry/core": "npm:^1.8.0" + "@opentelemetry/instrumentation": "npm:^0.54.0" + "@opentelemetry/semantic-conventions": "npm:^1.27.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/6f1af8af8b4ef213a1edde1ba14bc59635078d8953562bc48c431b42503ca91e6a837076093682eb8765a9490b61afdce4e33f966d19c4e436e4c1ffacb70ea1 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-fs@npm:0.16.0": + version: 0.16.0 + resolution: "@opentelemetry/instrumentation-fs@npm:0.16.0" + dependencies: + "@opentelemetry/core": "npm:^1.8.0" + "@opentelemetry/instrumentation": "npm:^0.54.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/01ac3a8c488a85cbd63e8cdb62e4ab228af569c05d731c4615ff90a4fe699e2e619b626d6838f03e7aaeb715a695d6e45a5ba4c5a976e748c04276719924efb9 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-generic-pool@npm:0.39.0": + version: 0.39.0 + resolution: "@opentelemetry/instrumentation-generic-pool@npm:0.39.0" + dependencies: + "@opentelemetry/instrumentation": "npm:^0.53.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/37b476cdddaf3fa2f83a340dcd6949e70cbead45cf747a953099fdb422cb0e89fd52017d0ca01e74283e5af4caa788eb4d163f81e4f21e6ba8e89d0a0dbc99c5 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-graphql@npm:0.44.0": + version: 0.44.0 + resolution: "@opentelemetry/instrumentation-graphql@npm:0.44.0" + dependencies: + "@opentelemetry/instrumentation": "npm:^0.54.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/fca5234a9adf5bee2b7a0613372e9f5b8cfc4c64243ec84b5fdbae149b1e15ca7398b5eb8c755c8cf82816c7963b080dd6e85d1403e80057dc87ebf81498699a + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-hapi@npm:0.41.0": + version: 0.41.0 + resolution: "@opentelemetry/instrumentation-hapi@npm:0.41.0" + dependencies: + "@opentelemetry/core": "npm:^1.8.0" + "@opentelemetry/instrumentation": "npm:^0.53.0" + "@opentelemetry/semantic-conventions": "npm:^1.27.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/5025db3e785476757947915e9512d454f565eabc883757d7a122e134f3cb2e5d418142f916e5ab4b2db2bfb9c59ab105f602c19af268442ae07106b5b547fa64 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-http@npm:0.53.0": + version: 0.53.0 + resolution: "@opentelemetry/instrumentation-http@npm:0.53.0" + dependencies: + "@opentelemetry/core": "npm:1.26.0" + "@opentelemetry/instrumentation": "npm:0.53.0" + "@opentelemetry/semantic-conventions": "npm:1.27.0" + semver: "npm:^7.5.2" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/c00e71f7a5a03723bf13e55e74dcc8e44d61b87fc38c50821fa6bf86a09d3eca68a62a4ccc6f35e70a6529c36d134eca77889852869d7a5a9b2af73f3fb5f097 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-ioredis@npm:0.43.0": + version: 0.43.0 + resolution: "@opentelemetry/instrumentation-ioredis@npm:0.43.0" + dependencies: + "@opentelemetry/instrumentation": "npm:^0.53.0" + "@opentelemetry/redis-common": "npm:^0.36.2" + "@opentelemetry/semantic-conventions": "npm:^1.27.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/fa405f521134a375c3ae1894d39da2a62bd021695fbc6a28d7efe61202d9a3b895047cf59353d6773e5d8528aea24a63841110ba48800132f5aac47615603c10 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-kafkajs@npm:0.4.0": + version: 0.4.0 + resolution: "@opentelemetry/instrumentation-kafkajs@npm:0.4.0" + dependencies: + "@opentelemetry/instrumentation": "npm:^0.54.0" + "@opentelemetry/semantic-conventions": "npm:^1.27.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/e5abcbbf2a458c3754d8a5790cf364384c84f51929ec66973ae1390020ef945a4be3d42db214a6738362a9d319e03ad6df0abc9470b2107568728d1e42f7ea94 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-knex@npm:0.41.0": + version: 0.41.0 + resolution: "@opentelemetry/instrumentation-knex@npm:0.41.0" + dependencies: + "@opentelemetry/instrumentation": "npm:^0.54.0" + "@opentelemetry/semantic-conventions": "npm:^1.27.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/273dbaf08f5256e2f8390b7846532baba6f4f9f39593c01d7fc756af346906b21214ac7b7007d4b7b7c2279acda4180b9ac89bc0e40befc1ccec80a64e2b75cb + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-koa@npm:0.43.0": + version: 0.43.0 + resolution: "@opentelemetry/instrumentation-koa@npm:0.43.0" + dependencies: + "@opentelemetry/core": "npm:^1.8.0" + "@opentelemetry/instrumentation": "npm:^0.53.0" + "@opentelemetry/semantic-conventions": "npm:^1.27.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/b494196962c0840651e5fdec7350a8d9f443ee9e682e4c20c8b47ed82c6c34875adc7fd467ac04c3838edbf14bf79aafddb889f2755fc1957f27275a08442e83 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-lru-memoizer@npm:0.40.0": + version: 0.40.0 + resolution: "@opentelemetry/instrumentation-lru-memoizer@npm:0.40.0" + dependencies: + "@opentelemetry/instrumentation": "npm:^0.53.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/07bb795faedb0c01bf7dd2cc660431b2303fd1f3a904b3fcc06eb601fde94653f8391a40ccf101a391893187a68381ab6ea8a284118fff328d32b130fac2ea6c + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-mongodb@npm:0.48.0": + version: 0.48.0 + resolution: "@opentelemetry/instrumentation-mongodb@npm:0.48.0" + dependencies: + "@opentelemetry/instrumentation": "npm:^0.54.0" + "@opentelemetry/semantic-conventions": "npm:^1.27.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/84b6cef3d80086a05783c211cbfb62bf9ef59c9b17dcfd943fb13570eb2fe45c91dd07823a2aa81e2f81e3689a20cf0d02c6f765d4f9a1af67b75b44cd0293c9 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-mongoose@npm:0.42.0": + version: 0.42.0 + resolution: "@opentelemetry/instrumentation-mongoose@npm:0.42.0" + dependencies: + "@opentelemetry/core": "npm:^1.8.0" + "@opentelemetry/instrumentation": "npm:^0.53.0" + "@opentelemetry/semantic-conventions": "npm:^1.27.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/58c3ba89ce43830451dcc105a2ebf352b296cf6b1b8f6194ac69c1fa39c18e50ee0092f8e514a27046cf35e0ade391425f7adf0e6e6b1fd8dbbec2b01f393be2 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-mysql2@npm:0.41.0": + version: 0.41.0 + resolution: "@opentelemetry/instrumentation-mysql2@npm:0.41.0" + dependencies: + "@opentelemetry/instrumentation": "npm:^0.53.0" + "@opentelemetry/semantic-conventions": "npm:^1.27.0" + "@opentelemetry/sql-common": "npm:^0.40.1" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/40f48b3f87bda347db2332020f0880223f49a894e0312d03e1f86aa48b8335b6db65955ea775b8bec2a687672bdbd9c0997294acdd4cf51765da0e22e1d98a35 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-mysql@npm:0.41.0": + version: 0.41.0 + resolution: "@opentelemetry/instrumentation-mysql@npm:0.41.0" + dependencies: + "@opentelemetry/instrumentation": "npm:^0.53.0" + "@opentelemetry/semantic-conventions": "npm:^1.27.0" + "@types/mysql": "npm:2.15.26" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/20ff56edc0b74cf8be2dd5960e210a6c20568169af5768fd78bb33f5a626e271fe2ac6cf7ad0e9629ff932a18feac04db99fffa3c867b27c679523dd2f4570d3 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-nestjs-core@npm:0.40.0": + version: 0.40.0 + resolution: "@opentelemetry/instrumentation-nestjs-core@npm:0.40.0" + dependencies: + "@opentelemetry/instrumentation": "npm:^0.53.0" + "@opentelemetry/semantic-conventions": "npm:^1.27.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/421f3e18c651b74383d5cd6a231431ecda3e49262f934dca27bf2272fe58334cbe2acf2f62ce5d82c0893d6f899e2921dfc6a6f78ab27f84a35bd8bfb77df9e4 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-pg@npm:0.44.0": + version: 0.44.0 + resolution: "@opentelemetry/instrumentation-pg@npm:0.44.0" + dependencies: + "@opentelemetry/instrumentation": "npm:^0.53.0" + "@opentelemetry/semantic-conventions": "npm:^1.27.0" + "@opentelemetry/sql-common": "npm:^0.40.1" + "@types/pg": "npm:8.6.1" + "@types/pg-pool": "npm:2.0.6" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/d902682a3630ff1ef392624165b46a2b4fe0fd696f42a588030f2c4ba73ccd2631792cf6b122bad0dfddb929044b96c285f63517704e7ccaf699a77150f5f3d9 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-redis-4@npm:0.42.0": + version: 0.42.0 + resolution: "@opentelemetry/instrumentation-redis-4@npm:0.42.0" + dependencies: + "@opentelemetry/instrumentation": "npm:^0.53.0" + "@opentelemetry/redis-common": "npm:^0.36.2" + "@opentelemetry/semantic-conventions": "npm:^1.27.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/d5ff240b826525cdc9935ab2885f65ea5c5d77ad31e9ee8142e6840b1c1603db025370b67fb828580a242fe7ff815d1335ff3845c48d8b94070f3683f71b0898 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-tedious@npm:0.15.0": + version: 0.15.0 + resolution: "@opentelemetry/instrumentation-tedious@npm:0.15.0" + dependencies: + "@opentelemetry/instrumentation": "npm:^0.54.0" + "@opentelemetry/semantic-conventions": "npm:^1.27.0" + "@types/tedious": "npm:^4.0.14" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/adbc5a444a28c4732aafd8a8742e1ffea100325a91005b8417cd50625952c537842f3d6f6c9c29aa468e0b5d850b285fa43617cde0bcbc463da207b38d0eee08 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-undici@npm:0.6.0": + version: 0.6.0 + resolution: "@opentelemetry/instrumentation-undici@npm:0.6.0" + dependencies: + "@opentelemetry/core": "npm:^1.8.0" + "@opentelemetry/instrumentation": "npm:^0.53.0" + peerDependencies: + "@opentelemetry/api": ^1.7.0 + checksum: 10/97291ecca9ff936dc4a418b380542f4dbb1f891692df44292dd61dc9e39aa1c347b70666cda5c30fbd78969d3b6ea602a6bafb30566b65eec0e00bcac459b2c4 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation@npm:0.53.0, @opentelemetry/instrumentation@npm:^0.53.0": + version: 0.53.0 + resolution: "@opentelemetry/instrumentation@npm:0.53.0" + dependencies: + "@opentelemetry/api-logs": "npm:0.53.0" + "@types/shimmer": "npm:^1.2.0" + import-in-the-middle: "npm:^1.8.1" + require-in-the-middle: "npm:^7.1.1" + semver: "npm:^7.5.2" + shimmer: "npm:^1.2.1" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/4b994c8568a503a15655cba249b1dbdef3f67dfda37938abba6267ba75b6d72a9aa276be4b0c8874e86f98ab89d92877e1874e0565a7e67f062c43dfcbbb16a5 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation@npm:^0.49 || ^0.50 || ^0.51 || ^0.52.0": + version: 0.52.1 + resolution: "@opentelemetry/instrumentation@npm:0.52.1" + dependencies: + "@opentelemetry/api-logs": "npm:0.52.1" + "@types/shimmer": "npm:^1.0.2" + import-in-the-middle: "npm:^1.8.1" + require-in-the-middle: "npm:^7.1.1" + semver: "npm:^7.5.2" + shimmer: "npm:^1.2.1" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/87761bd593f2b905d88d0531a3a2a7f4b0186334ae413b4c172a86bd4de0fd6d2f906a1bfd9dd7bd172a228a44fa7a680f5802a1570dfe2fadad0768e80bd7a8 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation@npm:^0.54.0": + version: 0.54.2 + resolution: "@opentelemetry/instrumentation@npm:0.54.2" + dependencies: + "@opentelemetry/api-logs": "npm:0.54.2" + "@types/shimmer": "npm:^1.2.0" + import-in-the-middle: "npm:^1.8.1" + require-in-the-middle: "npm:^7.1.1" + semver: "npm:^7.5.2" + shimmer: "npm:^1.2.1" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/1c570fb2e55d2ea7dcc45103afb53ffc331efb675dc404783639c0ed4c93e4e0fa04751672f75ca2a633ca03943e520cf802ee0291e79fa33be54a097af46fc6 + languageName: node + linkType: hard + +"@opentelemetry/redis-common@npm:^0.36.2": + version: 0.36.2 + resolution: "@opentelemetry/redis-common@npm:0.36.2" + checksum: 10/e7f610f79c95bab9156a9831162c7b55b94ab43c5e47ecb9efcc10c08a236395fdd54b6bb018da981e6641bac9da6fda1b50636fb49db584e87d988750d255e1 + languageName: node + linkType: hard + +"@opentelemetry/resources@npm:1.27.0, @opentelemetry/resources@npm:^1.26.0": + version: 1.27.0 + resolution: "@opentelemetry/resources@npm:1.27.0" + dependencies: + "@opentelemetry/core": "npm:1.27.0" + "@opentelemetry/semantic-conventions": "npm:1.27.0" + peerDependencies: + "@opentelemetry/api": ">=1.0.0 <1.10.0" + checksum: 10/654141ea65854bba84c22eeecc5af0054f14462f2664f36ac1ad8a170404e3218fccb98cafaaff4ec45e85523230e58eafbf222c25d00de8a60141ce77a34bbf + languageName: node + linkType: hard + +"@opentelemetry/sdk-trace-base@npm:^1.22, @opentelemetry/sdk-trace-base@npm:^1.26.0": + version: 1.27.0 + resolution: "@opentelemetry/sdk-trace-base@npm:1.27.0" + dependencies: + "@opentelemetry/core": "npm:1.27.0" + "@opentelemetry/resources": "npm:1.27.0" + "@opentelemetry/semantic-conventions": "npm:1.27.0" + peerDependencies: + "@opentelemetry/api": ">=1.0.0 <1.10.0" + checksum: 10/e0023dedbf5a50265729dd5467be0504f04f6b43d4cc4b914a9a8c082cca1aec9250a1f31c615f3d4ad03124af6e0ba1e14b39fe745d14291198f2a0990edc9c + languageName: node + linkType: hard + +"@opentelemetry/semantic-conventions@npm:1.27.0, @opentelemetry/semantic-conventions@npm:^1.27.0": + version: 1.27.0 + resolution: "@opentelemetry/semantic-conventions@npm:1.27.0" + checksum: 10/98166522f299e2fe3d43376adbdeb92679b75ebb172e2a3c4c71f2942bd91585e9537618efbbae6dc08177699e5719368edf66d7e69e8636f360b85217bbdbe1 + languageName: node + linkType: hard + +"@opentelemetry/sql-common@npm:^0.40.1": + version: 0.40.1 + resolution: "@opentelemetry/sql-common@npm:0.40.1" + dependencies: + "@opentelemetry/core": "npm:^1.1.0" + peerDependencies: + "@opentelemetry/api": ^1.1.0 + checksum: 10/f887b4135be56c9ef6e29f040c9f75f34709e38c11897d59d284d7e73175a2dd2c6267c18061144e81a0045fc461b7813769db2e49c42a8d6becc58b1456d55c + languageName: node + linkType: hard + +"@parcel/watcher-android-arm64@npm:2.5.0": + version: 2.5.0 + resolution: "@parcel/watcher-android-arm64@npm:2.5.0" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@parcel/watcher-darwin-arm64@npm:2.5.0": + version: 2.5.0 + resolution: "@parcel/watcher-darwin-arm64@npm:2.5.0" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@parcel/watcher-darwin-x64@npm:2.5.0": + version: 2.5.0 + resolution: "@parcel/watcher-darwin-x64@npm:2.5.0" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@parcel/watcher-freebsd-x64@npm:2.5.0": + version: 2.5.0 + resolution: "@parcel/watcher-freebsd-x64@npm:2.5.0" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@parcel/watcher-linux-arm-glibc@npm:2.5.0": + version: 2.5.0 + resolution: "@parcel/watcher-linux-arm-glibc@npm:2.5.0" + conditions: os=linux & cpu=arm & libc=glibc + languageName: node + linkType: hard + +"@parcel/watcher-linux-arm-musl@npm:2.5.0": + version: 2.5.0 + resolution: "@parcel/watcher-linux-arm-musl@npm:2.5.0" + conditions: os=linux & cpu=arm & libc=musl + languageName: node + linkType: hard + +"@parcel/watcher-linux-arm64-glibc@npm:2.5.0": + version: 2.5.0 + resolution: "@parcel/watcher-linux-arm64-glibc@npm:2.5.0" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@parcel/watcher-linux-arm64-musl@npm:2.5.0": + version: 2.5.0 + resolution: "@parcel/watcher-linux-arm64-musl@npm:2.5.0" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@parcel/watcher-linux-x64-glibc@npm:2.5.0": + version: 2.5.0 + resolution: "@parcel/watcher-linux-x64-glibc@npm:2.5.0" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@parcel/watcher-linux-x64-musl@npm:2.5.0": + version: 2.5.0 + resolution: "@parcel/watcher-linux-x64-musl@npm:2.5.0" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@parcel/watcher-win32-arm64@npm:2.5.0": + version: 2.5.0 + resolution: "@parcel/watcher-win32-arm64@npm:2.5.0" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@parcel/watcher-win32-ia32@npm:2.5.0": + version: 2.5.0 + resolution: "@parcel/watcher-win32-ia32@npm:2.5.0" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@parcel/watcher-win32-x64@npm:2.5.0": + version: 2.5.0 + resolution: "@parcel/watcher-win32-x64@npm:2.5.0" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@parcel/watcher@npm:^2.5.0": + version: 2.5.0 + resolution: "@parcel/watcher@npm:2.5.0" + dependencies: + "@parcel/watcher-android-arm64": "npm:2.5.0" + "@parcel/watcher-darwin-arm64": "npm:2.5.0" + "@parcel/watcher-darwin-x64": "npm:2.5.0" + "@parcel/watcher-freebsd-x64": "npm:2.5.0" + "@parcel/watcher-linux-arm-glibc": "npm:2.5.0" + "@parcel/watcher-linux-arm-musl": "npm:2.5.0" + "@parcel/watcher-linux-arm64-glibc": "npm:2.5.0" + "@parcel/watcher-linux-arm64-musl": "npm:2.5.0" + "@parcel/watcher-linux-x64-glibc": "npm:2.5.0" + "@parcel/watcher-linux-x64-musl": "npm:2.5.0" + "@parcel/watcher-win32-arm64": "npm:2.5.0" + "@parcel/watcher-win32-ia32": "npm:2.5.0" + "@parcel/watcher-win32-x64": "npm:2.5.0" + detect-libc: "npm:^1.0.3" + is-glob: "npm:^4.0.3" + micromatch: "npm:^4.0.5" + node-addon-api: "npm:^7.0.0" + node-gyp: "npm:latest" + dependenciesMeta: + "@parcel/watcher-android-arm64": + optional: true + "@parcel/watcher-darwin-arm64": + optional: true + "@parcel/watcher-darwin-x64": + optional: true + "@parcel/watcher-freebsd-x64": + optional: true + "@parcel/watcher-linux-arm-glibc": + optional: true + "@parcel/watcher-linux-arm-musl": + optional: true + "@parcel/watcher-linux-arm64-glibc": + optional: true + "@parcel/watcher-linux-arm64-musl": + optional: true + "@parcel/watcher-linux-x64-glibc": + optional: true + "@parcel/watcher-linux-x64-musl": + optional: true + "@parcel/watcher-win32-arm64": + optional: true + "@parcel/watcher-win32-ia32": + optional: true + "@parcel/watcher-win32-x64": + optional: true + checksum: 10/1e28b1aa9a63456ebfa7af3e41297d088bd31d9e32548604f4f26ed96c5808f4330cd515062e879c24a9eaab7894066c8a3951ee30b59e7cbe6786ab2c790dae + languageName: node + linkType: hard + +"@pkgjs/parseargs@npm:^0.11.0": + version: 0.11.0 + resolution: "@pkgjs/parseargs@npm:0.11.0" + checksum: 10/115e8ceeec6bc69dff2048b35c0ab4f8bbee12d8bb6c1f4af758604586d802b6e669dcb02dda61d078de42c2b4ddce41b3d9e726d7daa6b4b850f4adbf7333ff + languageName: node + linkType: hard + +"@polka/url@npm:^1.0.0-next.24": + version: 1.0.0-next.28 + resolution: "@polka/url@npm:1.0.0-next.28" + checksum: 10/7402aaf1de781d0eb0870d50cbcd394f949aee11b38a267a5c3b4e3cfee117e920693e6e93ce24c87ae2d477a59634f39d9edde8e86471cae756839b07c79af7 + languageName: node + linkType: hard + +"@portive/api-types@npm:^9.0.0": + version: 9.0.0 + resolution: "@portive/api-types@npm:9.0.0" + dependencies: + "@thesunny/assert-type": "npm:^0.1.13" + superstruct: "npm:^0.15.4" + checksum: 10/5b63a96614ce6bb3c0d087475a96a62a3d4cdc064676bb01f7e1bd0a016db63e36a6a1c567d59b1fd198e46d4c445120286fa046c3d8ddc76ff09118b4eb6166 + languageName: node + linkType: hard + +"@portive/client@npm:^10.0.3": + version: 10.0.3 + resolution: "@portive/client@npm:10.0.3" + dependencies: + "@portive/api-types": "npm:^9.0.0" + axios: "npm:^0.27.2" + resolvable-value: "npm:^1.0.2" + checksum: 10/31c497f241583b33541abfb63a93f02d2f2eae5ce1d5ae6221cc5f622927fc25e3fd63560da69bddf46e4a47c7f386056d9722ec9eb55400af205b91205ee0b0 + languageName: node + linkType: hard + +"@prisma/client@npm:^5.22.0": + version: 5.22.0 + resolution: "@prisma/client@npm:5.22.0" + peerDependencies: + prisma: "*" + peerDependenciesMeta: + prisma: + optional: true + checksum: 10/991d7410ded2d6c824303ac222ae94df4f1bf42240dda97c710cfee3a3bc59fef65c73e1ee55177e38d12f4d7dfc0a047748f98b4e3026dddd65f462684cd2d1 + languageName: node + linkType: hard + +"@prisma/debug@npm:5.22.0": + version: 5.22.0 + resolution: "@prisma/debug@npm:5.22.0" + checksum: 10/e4c425fde57f83c1a3df44d3f9088684a3e1d764022d2378f981209e57b7f421bc773d7f4fc23daa3936aa3e3772fa99fef81d2f2ec9673269f06efe822e3b53 + languageName: node + linkType: hard + +"@prisma/engines-version@npm:5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2": + version: 5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2 + resolution: "@prisma/engines-version@npm:5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2" + checksum: 10/e9e3563d75482591ca482dfc26afaf8a727796f48bfcc9b550652ca9c66176f5841944d00f8a4cb60a7fd58b117d8e28b1c6b84fc7316123738f7290c91c291d + languageName: node + linkType: hard + +"@prisma/engines@npm:5.22.0": + version: 5.22.0 + resolution: "@prisma/engines@npm:5.22.0" + dependencies: + "@prisma/debug": "npm:5.22.0" + "@prisma/engines-version": "npm:5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2" + "@prisma/fetch-engine": "npm:5.22.0" + "@prisma/get-platform": "npm:5.22.0" + checksum: 10/887381d8be0acc713159ef70cdc5c4b2ca5af6f3a5d2f6ae73b63c6cf31b74e5c41a9c2dbccb02b1a49bbda19a3d0a75cd1bcc86d76e98991c3a3978e61b622b + languageName: node + linkType: hard + +"@prisma/fetch-engine@npm:5.22.0": + version: 5.22.0 + resolution: "@prisma/fetch-engine@npm:5.22.0" + dependencies: + "@prisma/debug": "npm:5.22.0" + "@prisma/engines-version": "npm:5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2" + "@prisma/get-platform": "npm:5.22.0" + checksum: 10/512ce722e582a72429c90d438a63c730b230faa1235300215ab6cd1c5a0dfb2aa63704a66ee72c57222d9c094477ea198b1c154b9779dcab78ebf36e09f79161 + languageName: node + linkType: hard + +"@prisma/get-platform@npm:5.22.0": + version: 5.22.0 + resolution: "@prisma/get-platform@npm:5.22.0" + dependencies: + "@prisma/debug": "npm:5.22.0" + checksum: 10/15db9f38933a59a1b0f3a1d6284a50261803677516d6eedec2f7caaa1767c1c6e94e6465aca9239766b1cc363002476da21f7eeab55cc5329937d6d6d57a5f67 + languageName: node + linkType: hard + +"@prisma/instrumentation@npm:5.19.1": + version: 5.19.1 + resolution: "@prisma/instrumentation@npm:5.19.1" + dependencies: + "@opentelemetry/api": "npm:^1.8" + "@opentelemetry/instrumentation": "npm:^0.49 || ^0.50 || ^0.51 || ^0.52.0" + "@opentelemetry/sdk-trace-base": "npm:^1.22" + checksum: 10/62029ace33406901d1dfee136d4ae83b51d5787fbcdb104378edc890310e1989a0b0c95c1eb28fe8bfc314565aebee48189aebee600486859383d8981993045b + languageName: node + linkType: hard + +"@protobufjs/aspromise@npm:^1.1.1, @protobufjs/aspromise@npm:^1.1.2": + version: 1.1.2 + resolution: "@protobufjs/aspromise@npm:1.1.2" + checksum: 10/8a938d84fe4889411296db66b29287bd61ea3c14c2d23e7a8325f46a2b8ce899857c5f038d65d7641805e6c1d06b495525c7faf00c44f85a7ee6476649034969 + languageName: node + linkType: hard + +"@protobufjs/base64@npm:^1.1.2": + version: 1.1.2 + resolution: "@protobufjs/base64@npm:1.1.2" + checksum: 10/c71b100daeb3c9bdccab5cbc29495b906ba0ae22ceedc200e1ba49717d9c4ab15a6256839cebb6f9c6acae4ed7c25c67e0a95e734f612b258261d1a3098fe342 + languageName: node + linkType: hard + +"@protobufjs/codegen@npm:^2.0.4": + version: 2.0.4 + resolution: "@protobufjs/codegen@npm:2.0.4" + checksum: 10/c6ee5fa172a8464f5253174d3c2353ea520c2573ad7b6476983d9b1346f4d8f2b44aa29feb17a949b83c1816bc35286a5ea265ed9d8fdd2865acfa09668c0447 + languageName: node + linkType: hard + +"@protobufjs/eventemitter@npm:^1.1.0": + version: 1.1.0 + resolution: "@protobufjs/eventemitter@npm:1.1.0" + checksum: 10/03af3e99f17ad421283d054c88a06a30a615922a817741b43ca1b13e7c6b37820a37f6eba9980fb5150c54dba6e26cb6f7b64a6f7d8afa83596fafb3afa218c3 + languageName: node + linkType: hard + +"@protobufjs/fetch@npm:^1.1.0": + version: 1.1.0 + resolution: "@protobufjs/fetch@npm:1.1.0" + dependencies: + "@protobufjs/aspromise": "npm:^1.1.1" + "@protobufjs/inquire": "npm:^1.1.0" + checksum: 10/67ae40572ad536e4ef94269199f252c024b66e3059850906bdaee161ca1d75c73d04d35cd56f147a8a5a079f5808e342b99e61942c1dae15604ff0600b09a958 + languageName: node + linkType: hard + +"@protobufjs/float@npm:^1.0.2": + version: 1.0.2 + resolution: "@protobufjs/float@npm:1.0.2" + checksum: 10/634c2c989da0ef2f4f19373d64187e2a79f598c5fb7991afb689d29a2ea17c14b796b29725945fa34b9493c17fb799e08ac0a7ccaae460ee1757d3083ed35187 + languageName: node + linkType: hard + +"@protobufjs/inquire@npm:^1.1.0": + version: 1.1.0 + resolution: "@protobufjs/inquire@npm:1.1.0" + checksum: 10/c09efa34a5465cb120775e1a482136f2340a58b4abce7e93d72b8b5a9324a0e879275016ef9fcd73d72a4731639c54f2bb755bb82f916e4a78892d1d840bb3d2 + languageName: node + linkType: hard + +"@protobufjs/path@npm:^1.1.2": + version: 1.1.2 + resolution: "@protobufjs/path@npm:1.1.2" + checksum: 10/bb709567935fd385a86ad1f575aea98131bbd719c743fb9b6edd6b47ede429ff71a801cecbd64fc72deebf4e08b8f1bd8062793178cdaed3713b8d15771f9b83 + languageName: node + linkType: hard + +"@protobufjs/pool@npm:^1.1.0": + version: 1.1.0 + resolution: "@protobufjs/pool@npm:1.1.0" + checksum: 10/b9c7047647f6af28e92aac54f6f7c1f7ff31b201b4bfcc7a415b2861528854fce3ec666d7e7e10fd744da905f7d4aef2205bbcc8944ca0ca7a82e18134d00c46 + languageName: node + linkType: hard + +"@protobufjs/utf8@npm:^1.1.0": + version: 1.1.0 + resolution: "@protobufjs/utf8@npm:1.1.0" + checksum: 10/131e289c57534c1d73a0e55782d6751dd821db1583cb2f7f7e017c9d6747addaebe79f28120b2e0185395d990aad347fb14ffa73ef4096fa38508d61a0e64602 + languageName: node + linkType: hard + +"@radix-ui/colors@npm:^3.0.0": + version: 3.0.0 + resolution: "@radix-ui/colors@npm:3.0.0" + checksum: 10/bc9ffb01b3964f16482dc278cf6d86d85b73eae7cb94a77018fbd53b103b3d5ec1ecf77882cfbf8892f28c2c6057db8e19bf4d66234ed330a430373b092c5cfe + languageName: node + linkType: hard + +"@radix-ui/number@npm:1.1.0": + version: 1.1.0 + resolution: "@radix-ui/number@npm:1.1.0" + checksum: 10/e4fc7483c19141c25dbaf3d140b75e2b7fed0bfa3ad969f4441f0266ed34b35413f57a35df7b025e2a977152bbe6131849d3444fc6f15a73345dfc2bfdc105fa + languageName: node + linkType: hard + +"@radix-ui/primitive@npm:1.1.0": + version: 1.1.0 + resolution: "@radix-ui/primitive@npm:1.1.0" + checksum: 10/7cbf70bfd4b2200972dbd52a9366801b5a43dd844743dc97eb673b3ec8e64f5dd547538faaf9939abbfe8bb275773767ecf5a87295d90ba09c15cba2b5528c89 + languageName: node + linkType: hard + +"@radix-ui/react-arrow@npm:1.1.0": + version: 1.1.0 + resolution: "@radix-ui/react-arrow@npm:1.1.0" + dependencies: + "@radix-ui/react-primitive": "npm:2.0.0" + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 10/8522e0a8095ecc32d3a719f9c3bc0514c677a9c9d5ac26985d5416576dbc487c2a49ba2484397d9de502b54657856cb41ca3ea0b2165563eeeae45a83750885b + languageName: node + linkType: hard + +"@radix-ui/react-collection@npm:1.1.0": + version: 1.1.0 + resolution: "@radix-ui/react-collection@npm:1.1.0" + dependencies: + "@radix-ui/react-compose-refs": "npm:1.1.0" + "@radix-ui/react-context": "npm:1.1.0" + "@radix-ui/react-primitive": "npm:2.0.0" + "@radix-ui/react-slot": "npm:1.1.0" + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 10/d3e656761773602f3a6be0fb568c328125d07ed202527f5fe839d1cdcc38a05d32f0568d2430199534206b86fad2dbe96725691300810033e65ec1e2e5181ccb + languageName: node + linkType: hard + +"@radix-ui/react-compose-refs@npm:1.1.0": + version: 1.1.0 + resolution: "@radix-ui/react-compose-refs@npm:1.1.0" + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10/047a4ed5f87cb848be475507cd62836cf5af5761484681f521ea543ea7c9d59d61d42806d6208863d5e2380bf38cdf4cff73c2bbe5f52dbbe50fb04e1a13ac72 languageName: node linkType: hard -"@parcel/watcher-linux-arm-musl@npm:2.5.0": - version: 2.5.0 - resolution: "@parcel/watcher-linux-arm-musl@npm:2.5.0" - conditions: os=linux & cpu=arm & libc=musl +"@radix-ui/react-context@npm:1.1.0": + version: 1.1.0 + resolution: "@radix-ui/react-context@npm:1.1.0" + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10/755aea1966dc9b778890e6d330482e9285e9cd9417425da364706cf1d43a041f0b5b2412e6dfebb81e35f68ce47304dd52bcda01f223685c287ac654e6142d7e languageName: node linkType: hard -"@parcel/watcher-linux-arm64-glibc@npm:2.5.0": - version: 2.5.0 - resolution: "@parcel/watcher-linux-arm64-glibc@npm:2.5.0" - conditions: os=linux & cpu=arm64 & libc=glibc +"@radix-ui/react-context@npm:1.1.1": + version: 1.1.1 + resolution: "@radix-ui/react-context@npm:1.1.1" + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10/f6469583bf11cc7bff3ea5c95c56b0774a959512adead00dc64b0527cca01b90b476ca39a64edfd7e18e428e17940aa0339116b1ce5b6e8eab513cfd1065d391 languageName: node linkType: hard -"@parcel/watcher-linux-arm64-musl@npm:2.5.0": - version: 2.5.0 - resolution: "@parcel/watcher-linux-arm64-musl@npm:2.5.0" - conditions: os=linux & cpu=arm64 & libc=musl +"@radix-ui/react-dialog@npm:^1.0.5": + version: 1.1.2 + resolution: "@radix-ui/react-dialog@npm:1.1.2" + dependencies: + "@radix-ui/primitive": "npm:1.1.0" + "@radix-ui/react-compose-refs": "npm:1.1.0" + "@radix-ui/react-context": "npm:1.1.1" + "@radix-ui/react-dismissable-layer": "npm:1.1.1" + "@radix-ui/react-focus-guards": "npm:1.1.1" + "@radix-ui/react-focus-scope": "npm:1.1.0" + "@radix-ui/react-id": "npm:1.1.0" + "@radix-ui/react-portal": "npm:1.1.2" + "@radix-ui/react-presence": "npm:1.1.1" + "@radix-ui/react-primitive": "npm:2.0.0" + "@radix-ui/react-slot": "npm:1.1.0" + "@radix-ui/react-use-controllable-state": "npm:1.1.0" + aria-hidden: "npm:^1.1.1" + react-remove-scroll: "npm:2.6.0" + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 10/9ed653e8e29443331d8dda8174f3c47194104e8b6e7bdd8f56998860fd8bf5e5a8867863bc93b089f8f7e37964375341eec2965e2f35ec26ee2cf2dd175bb503 languageName: node linkType: hard -"@parcel/watcher-linux-x64-glibc@npm:2.5.0": - version: 2.5.0 - resolution: "@parcel/watcher-linux-x64-glibc@npm:2.5.0" - conditions: os=linux & cpu=x64 & libc=glibc +"@radix-ui/react-direction@npm:1.1.0": + version: 1.1.0 + resolution: "@radix-ui/react-direction@npm:1.1.0" + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10/25ad0d1d65ad08c93cebfbefdff9ef2602e53f4573a66b37d2c366ede9485e75ec6fc8e7dd7d2939b34ea5504ca0fe6ac4a3acc2f6ee9b62d131d65486eafd49 languageName: node linkType: hard -"@parcel/watcher-linux-x64-musl@npm:2.5.0": - version: 2.5.0 - resolution: "@parcel/watcher-linux-x64-musl@npm:2.5.0" - conditions: os=linux & cpu=x64 & libc=musl +"@radix-ui/react-dismissable-layer@npm:1.1.1": + version: 1.1.1 + resolution: "@radix-ui/react-dismissable-layer@npm:1.1.1" + dependencies: + "@radix-ui/primitive": "npm:1.1.0" + "@radix-ui/react-compose-refs": "npm:1.1.0" + "@radix-ui/react-primitive": "npm:2.0.0" + "@radix-ui/react-use-callback-ref": "npm:1.1.0" + "@radix-ui/react-use-escape-keydown": "npm:1.1.0" + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 10/4f2346846f15f3482b8b6cd850448838d01520366deef840d8e80f5a3443aa7f21f86bd5b9a26f2709dcf94f1ec3f2bdfe80d5c37cba504c41e487c7ff8af3de languageName: node linkType: hard -"@parcel/watcher-win32-arm64@npm:2.5.0": - version: 2.5.0 - resolution: "@parcel/watcher-win32-arm64@npm:2.5.0" - conditions: os=win32 & cpu=arm64 +"@radix-ui/react-focus-guards@npm:1.1.1": + version: 1.1.1 + resolution: "@radix-ui/react-focus-guards@npm:1.1.1" + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10/ac8dd31f48fa0500bafd9368f2f06c5a06918dccefa89fa5dc77ca218dc931a094a81ca57f6b181138029822f7acdd5280dceccf5ba4d9263c754fb8f7961879 languageName: node linkType: hard -"@parcel/watcher-win32-ia32@npm:2.5.0": - version: 2.5.0 - resolution: "@parcel/watcher-win32-ia32@npm:2.5.0" - conditions: os=win32 & cpu=ia32 +"@radix-ui/react-focus-scope@npm:1.1.0": + version: 1.1.0 + resolution: "@radix-ui/react-focus-scope@npm:1.1.0" + dependencies: + "@radix-ui/react-compose-refs": "npm:1.1.0" + "@radix-ui/react-primitive": "npm:2.0.0" + "@radix-ui/react-use-callback-ref": "npm:1.1.0" + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 10/a34dc5caecc56483e293de770fde3addcebd975b94625cb7057bee3f0837d82bba9a672bef7c7902d28d68d31ab9b3847c88285664b5b747ac9141dabf11df3c languageName: node linkType: hard -"@parcel/watcher-win32-x64@npm:2.5.0": - version: 2.5.0 - resolution: "@parcel/watcher-win32-x64@npm:2.5.0" - conditions: os=win32 & cpu=x64 +"@radix-ui/react-icons@npm:^1.3.0": + version: 1.3.2 + resolution: "@radix-ui/react-icons@npm:1.3.2" + peerDependencies: + react: ^16.x || ^17.x || ^18.x || ^19.0.0 || ^19.0.0-rc + checksum: 10/cf6c694a400677890d0d83cd30ac01fa638c04eefe6194cd4e3c9edff49269e97dccc01e7bb480e2dcc249b27ee4eafa5d82d72d15c5119d66f4c26db6128f36 languageName: node linkType: hard -"@parcel/watcher@npm:^2.5.0": - version: 2.5.0 - resolution: "@parcel/watcher@npm:2.5.0" +"@radix-ui/react-id@npm:1.1.0": + version: 1.1.0 + resolution: "@radix-ui/react-id@npm:1.1.0" dependencies: - "@parcel/watcher-android-arm64": "npm:2.5.0" - "@parcel/watcher-darwin-arm64": "npm:2.5.0" - "@parcel/watcher-darwin-x64": "npm:2.5.0" - "@parcel/watcher-freebsd-x64": "npm:2.5.0" - "@parcel/watcher-linux-arm-glibc": "npm:2.5.0" - "@parcel/watcher-linux-arm-musl": "npm:2.5.0" - "@parcel/watcher-linux-arm64-glibc": "npm:2.5.0" - "@parcel/watcher-linux-arm64-musl": "npm:2.5.0" - "@parcel/watcher-linux-x64-glibc": "npm:2.5.0" - "@parcel/watcher-linux-x64-musl": "npm:2.5.0" - "@parcel/watcher-win32-arm64": "npm:2.5.0" - "@parcel/watcher-win32-ia32": "npm:2.5.0" - "@parcel/watcher-win32-x64": "npm:2.5.0" - detect-libc: "npm:^1.0.3" - is-glob: "npm:^4.0.3" - micromatch: "npm:^4.0.5" - node-addon-api: "npm:^7.0.0" - node-gyp: "npm:latest" - dependenciesMeta: - "@parcel/watcher-android-arm64": - optional: true - "@parcel/watcher-darwin-arm64": - optional: true - "@parcel/watcher-darwin-x64": - optional: true - "@parcel/watcher-freebsd-x64": - optional: true - "@parcel/watcher-linux-arm-glibc": - optional: true - "@parcel/watcher-linux-arm-musl": - optional: true - "@parcel/watcher-linux-arm64-glibc": - optional: true - "@parcel/watcher-linux-arm64-musl": - optional: true - "@parcel/watcher-linux-x64-glibc": - optional: true - "@parcel/watcher-linux-x64-musl": - optional: true - "@parcel/watcher-win32-arm64": + "@radix-ui/react-use-layout-effect": "npm:1.1.0" + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": optional: true - "@parcel/watcher-win32-ia32": + checksum: 10/6fbc9d1739b3b082412da10359e63967b4f3a60383ebda4c9e56b07a722d29bee53b203b3b1418f88854a29315a7715867133bb149e6e22a027a048cdd20d970 + languageName: node + linkType: hard + +"@radix-ui/react-popover@npm:^1.0.7": + version: 1.1.2 + resolution: "@radix-ui/react-popover@npm:1.1.2" + dependencies: + "@radix-ui/primitive": "npm:1.1.0" + "@radix-ui/react-compose-refs": "npm:1.1.0" + "@radix-ui/react-context": "npm:1.1.1" + "@radix-ui/react-dismissable-layer": "npm:1.1.1" + "@radix-ui/react-focus-guards": "npm:1.1.1" + "@radix-ui/react-focus-scope": "npm:1.1.0" + "@radix-ui/react-id": "npm:1.1.0" + "@radix-ui/react-popper": "npm:1.2.0" + "@radix-ui/react-portal": "npm:1.1.2" + "@radix-ui/react-presence": "npm:1.1.1" + "@radix-ui/react-primitive": "npm:2.0.0" + "@radix-ui/react-slot": "npm:1.1.0" + "@radix-ui/react-use-controllable-state": "npm:1.1.0" + aria-hidden: "npm:^1.1.1" + react-remove-scroll: "npm:2.6.0" + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": optional: true - "@parcel/watcher-win32-x64": + "@types/react-dom": optional: true - checksum: 10/1e28b1aa9a63456ebfa7af3e41297d088bd31d9e32548604f4f26ed96c5808f4330cd515062e879c24a9eaab7894066c8a3951ee30b59e7cbe6786ab2c790dae + checksum: 10/e8390e144516a016ade2a2a8fdf6b3c2f281d67cdbbcc46478d63366498c4af6946bafef8ecc496d37350d287e05adacc22da5e286298843ac49c285ce69bfdd languageName: node linkType: hard -"@pkgjs/parseargs@npm:^0.11.0": - version: 0.11.0 - resolution: "@pkgjs/parseargs@npm:0.11.0" - checksum: 10/115e8ceeec6bc69dff2048b35c0ab4f8bbee12d8bb6c1f4af758604586d802b6e669dcb02dda61d078de42c2b4ddce41b3d9e726d7daa6b4b850f4adbf7333ff +"@radix-ui/react-popper@npm:1.2.0": + version: 1.2.0 + resolution: "@radix-ui/react-popper@npm:1.2.0" + dependencies: + "@floating-ui/react-dom": "npm:^2.0.0" + "@radix-ui/react-arrow": "npm:1.1.0" + "@radix-ui/react-compose-refs": "npm:1.1.0" + "@radix-ui/react-context": "npm:1.1.0" + "@radix-ui/react-primitive": "npm:2.0.0" + "@radix-ui/react-use-callback-ref": "npm:1.1.0" + "@radix-ui/react-use-layout-effect": "npm:1.1.0" + "@radix-ui/react-use-rect": "npm:1.1.0" + "@radix-ui/react-use-size": "npm:1.1.0" + "@radix-ui/rect": "npm:1.1.0" + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 10/33aeb8e3436c4764e53ac97b85617309f77b4a34ac3848e2f2c638ed01590895d4787a4382e4e8cedc1a04fd0346e35108adc296ce600399545d8587f4201d09 languageName: node linkType: hard -"@polka/url@npm:^1.0.0-next.24": - version: 1.0.0-next.28 - resolution: "@polka/url@npm:1.0.0-next.28" - checksum: 10/7402aaf1de781d0eb0870d50cbcd394f949aee11b38a267a5c3b4e3cfee117e920693e6e93ce24c87ae2d477a59634f39d9edde8e86471cae756839b07c79af7 +"@radix-ui/react-portal@npm:1.1.2": + version: 1.1.2 + resolution: "@radix-ui/react-portal@npm:1.1.2" + dependencies: + "@radix-ui/react-primitive": "npm:2.0.0" + "@radix-ui/react-use-layout-effect": "npm:1.1.0" + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 10/2f737dc0445f02f512f814ba140227e1a049b3d215d79e22ead412c9befe830292c48a559a8ad1514a474ae8f0c4c43954dfbe294b93a0279d8747d08f7b7924 languageName: node linkType: hard -"@portive/api-types@npm:^9.0.0": - version: 9.0.0 - resolution: "@portive/api-types@npm:9.0.0" +"@radix-ui/react-presence@npm:1.1.1": + version: 1.1.1 + resolution: "@radix-ui/react-presence@npm:1.1.1" dependencies: - "@thesunny/assert-type": "npm:^0.1.13" - superstruct: "npm:^0.15.4" - checksum: 10/5b63a96614ce6bb3c0d087475a96a62a3d4cdc064676bb01f7e1bd0a016db63e36a6a1c567d59b1fd198e46d4c445120286fa046c3d8ddc76ff09118b4eb6166 + "@radix-ui/react-compose-refs": "npm:1.1.0" + "@radix-ui/react-use-layout-effect": "npm:1.1.0" + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 10/1ae074efae47ab52a63239a5936fddb334b2f66ed91e74bfe8b1ae591e5db01fa7e9ddb1412002cc043066d40478ba05187a27eb2684dcd68dea545993f9ee20 languageName: node linkType: hard -"@portive/client@npm:^10.0.3": - version: 10.0.3 - resolution: "@portive/client@npm:10.0.3" +"@radix-ui/react-primitive@npm:2.0.0": + version: 2.0.0 + resolution: "@radix-ui/react-primitive@npm:2.0.0" dependencies: - "@portive/api-types": "npm:^9.0.0" - axios: "npm:^0.27.2" - resolvable-value: "npm:^1.0.2" - checksum: 10/31c497f241583b33541abfb63a93f02d2f2eae5ce1d5ae6221cc5f622927fc25e3fd63560da69bddf46e4a47c7f386056d9722ec9eb55400af205b91205ee0b0 + "@radix-ui/react-slot": "npm:1.1.0" + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 10/f3dc683f5ba6534739356ac78ba5008d237b2f0e97eb3d578fcb01ecdb869a0729c24adc6dec238bfb1074763629935724381451313c109ca1be2a60fe4c16e3 languageName: node linkType: hard -"@prisma/client@npm:^5.22.0": - version: 5.22.0 - resolution: "@prisma/client@npm:5.22.0" +"@radix-ui/react-roving-focus@npm:1.1.0": + version: 1.1.0 + resolution: "@radix-ui/react-roving-focus@npm:1.1.0" + dependencies: + "@radix-ui/primitive": "npm:1.1.0" + "@radix-ui/react-collection": "npm:1.1.0" + "@radix-ui/react-compose-refs": "npm:1.1.0" + "@radix-ui/react-context": "npm:1.1.0" + "@radix-ui/react-direction": "npm:1.1.0" + "@radix-ui/react-id": "npm:1.1.0" + "@radix-ui/react-primitive": "npm:2.0.0" + "@radix-ui/react-use-callback-ref": "npm:1.1.0" + "@radix-ui/react-use-controllable-state": "npm:1.1.0" peerDependencies: - prisma: "*" + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: - prisma: + "@types/react": optional: true - checksum: 10/991d7410ded2d6c824303ac222ae94df4f1bf42240dda97c710cfee3a3bc59fef65c73e1ee55177e38d12f4d7dfc0a047748f98b4e3026dddd65f462684cd2d1 + "@types/react-dom": + optional: true + checksum: 10/f7c3d9b6d9dc1036d56b6005c58a948ee20f07ba21a00063dc1c1a790918feae13f16f9383dea3a1ccc3698ac552b8382c6885844580f0eeb11108a6d4824ea7 languageName: node linkType: hard -"@prisma/debug@npm:5.22.0": - version: 5.22.0 - resolution: "@prisma/debug@npm:5.22.0" - checksum: 10/e4c425fde57f83c1a3df44d3f9088684a3e1d764022d2378f981209e57b7f421bc773d7f4fc23daa3936aa3e3772fa99fef81d2f2ec9673269f06efe822e3b53 +"@radix-ui/react-select@npm:^2.0.0": + version: 2.1.2 + resolution: "@radix-ui/react-select@npm:2.1.2" + dependencies: + "@radix-ui/number": "npm:1.1.0" + "@radix-ui/primitive": "npm:1.1.0" + "@radix-ui/react-collection": "npm:1.1.0" + "@radix-ui/react-compose-refs": "npm:1.1.0" + "@radix-ui/react-context": "npm:1.1.1" + "@radix-ui/react-direction": "npm:1.1.0" + "@radix-ui/react-dismissable-layer": "npm:1.1.1" + "@radix-ui/react-focus-guards": "npm:1.1.1" + "@radix-ui/react-focus-scope": "npm:1.1.0" + "@radix-ui/react-id": "npm:1.1.0" + "@radix-ui/react-popper": "npm:1.2.0" + "@radix-ui/react-portal": "npm:1.1.2" + "@radix-ui/react-primitive": "npm:2.0.0" + "@radix-ui/react-slot": "npm:1.1.0" + "@radix-ui/react-use-callback-ref": "npm:1.1.0" + "@radix-ui/react-use-controllable-state": "npm:1.1.0" + "@radix-ui/react-use-layout-effect": "npm:1.1.0" + "@radix-ui/react-use-previous": "npm:1.1.0" + "@radix-ui/react-visually-hidden": "npm:1.1.0" + aria-hidden: "npm:^1.1.1" + react-remove-scroll: "npm:2.6.0" + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 10/b9b6e34680bd0d9b161af1ef93560b84b398f7c710adb82e3676a724600698653682aa7873aa7f5198370182bd2bec9a79e7b651b80379ba3df99140c76813e5 languageName: node linkType: hard -"@prisma/engines-version@npm:5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2": - version: 5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2 - resolution: "@prisma/engines-version@npm:5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2" - checksum: 10/e9e3563d75482591ca482dfc26afaf8a727796f48bfcc9b550652ca9c66176f5841944d00f8a4cb60a7fd58b117d8e28b1c6b84fc7316123738f7290c91c291d +"@radix-ui/react-separator@npm:1.1.0": + version: 1.1.0 + resolution: "@radix-ui/react-separator@npm:1.1.0" + dependencies: + "@radix-ui/react-primitive": "npm:2.0.0" + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 10/a7c3445603a45075dcf3559eb8f2f2e8545afeae253e67d0bde736c66b293c601974a1d6f9d7be1802d83869933dc120a7389ab98189ceb9a24659737dde0162 languageName: node linkType: hard -"@prisma/engines@npm:5.22.0": - version: 5.22.0 - resolution: "@prisma/engines@npm:5.22.0" +"@radix-ui/react-slot@npm:1.1.0": + version: 1.1.0 + resolution: "@radix-ui/react-slot@npm:1.1.0" dependencies: - "@prisma/debug": "npm:5.22.0" - "@prisma/engines-version": "npm:5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2" - "@prisma/fetch-engine": "npm:5.22.0" - "@prisma/get-platform": "npm:5.22.0" - checksum: 10/887381d8be0acc713159ef70cdc5c4b2ca5af6f3a5d2f6ae73b63c6cf31b74e5c41a9c2dbccb02b1a49bbda19a3d0a75cd1bcc86d76e98991c3a3978e61b622b + "@radix-ui/react-compose-refs": "npm:1.1.0" + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10/95e190868418b1c83adf6627256f6b664b0dcbea95d7215de9c64ac2c31102fc09155565d9ca27be6abd20fc63d0b0bacfe1b67d78b2de1d198244c848e1a54e languageName: node linkType: hard -"@prisma/fetch-engine@npm:5.22.0": - version: 5.22.0 - resolution: "@prisma/fetch-engine@npm:5.22.0" +"@radix-ui/react-toggle-group@npm:1.1.0, @radix-ui/react-toggle-group@npm:^1.0.4": + version: 1.1.0 + resolution: "@radix-ui/react-toggle-group@npm:1.1.0" dependencies: - "@prisma/debug": "npm:5.22.0" - "@prisma/engines-version": "npm:5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2" - "@prisma/get-platform": "npm:5.22.0" - checksum: 10/512ce722e582a72429c90d438a63c730b230faa1235300215ab6cd1c5a0dfb2aa63704a66ee72c57222d9c094477ea198b1c154b9779dcab78ebf36e09f79161 + "@radix-ui/primitive": "npm:1.1.0" + "@radix-ui/react-context": "npm:1.1.0" + "@radix-ui/react-direction": "npm:1.1.0" + "@radix-ui/react-primitive": "npm:2.0.0" + "@radix-ui/react-roving-focus": "npm:1.1.0" + "@radix-ui/react-toggle": "npm:1.1.0" + "@radix-ui/react-use-controllable-state": "npm:1.1.0" + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 10/0d9102e2e30f097b6d07e47280b5368d9502521f8aa43de9091abedafd828e1f8845f94c15c1c4eecbe9862e3d91bae58b7a1c3924f1abd5e98b623e2b46dc8d languageName: node linkType: hard -"@prisma/get-platform@npm:5.22.0": - version: 5.22.0 - resolution: "@prisma/get-platform@npm:5.22.0" +"@radix-ui/react-toggle@npm:1.1.0": + version: 1.1.0 + resolution: "@radix-ui/react-toggle@npm:1.1.0" dependencies: - "@prisma/debug": "npm:5.22.0" - checksum: 10/15db9f38933a59a1b0f3a1d6284a50261803677516d6eedec2f7caaa1767c1c6e94e6465aca9239766b1cc363002476da21f7eeab55cc5329937d6d6d57a5f67 + "@radix-ui/primitive": "npm:1.1.0" + "@radix-ui/react-primitive": "npm:2.0.0" + "@radix-ui/react-use-controllable-state": "npm:1.1.0" + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 10/556818c9d57c024cca0533c859464ee101b69c527d2f2791fa97175fdabea4320eb0078e84bb73f2c5d5794a8a0069666bbdcfe07067fe02ebe4950917ca8e3a languageName: node linkType: hard -"@prisma/instrumentation@npm:5.19.1": - version: 5.19.1 - resolution: "@prisma/instrumentation@npm:5.19.1" +"@radix-ui/react-toolbar@npm:^1.0.4": + version: 1.1.0 + resolution: "@radix-ui/react-toolbar@npm:1.1.0" dependencies: - "@opentelemetry/api": "npm:^1.8" - "@opentelemetry/instrumentation": "npm:^0.49 || ^0.50 || ^0.51 || ^0.52.0" - "@opentelemetry/sdk-trace-base": "npm:^1.22" - checksum: 10/62029ace33406901d1dfee136d4ae83b51d5787fbcdb104378edc890310e1989a0b0c95c1eb28fe8bfc314565aebee48189aebee600486859383d8981993045b + "@radix-ui/primitive": "npm:1.1.0" + "@radix-ui/react-context": "npm:1.1.0" + "@radix-ui/react-direction": "npm:1.1.0" + "@radix-ui/react-primitive": "npm:2.0.0" + "@radix-ui/react-roving-focus": "npm:1.1.0" + "@radix-ui/react-separator": "npm:1.1.0" + "@radix-ui/react-toggle-group": "npm:1.1.0" + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 10/dd5a727de160e31e825f36dcabb62575a1c5568ec0a16e5f07ed5d56d6122bd4d1379a3c9cea656238382b46ac9fa1bca59905af991dbac4621f9b2c7edea16b languageName: node linkType: hard -"@protobufjs/aspromise@npm:^1.1.1, @protobufjs/aspromise@npm:^1.1.2": - version: 1.1.2 - resolution: "@protobufjs/aspromise@npm:1.1.2" - checksum: 10/8a938d84fe4889411296db66b29287bd61ea3c14c2d23e7a8325f46a2b8ce899857c5f038d65d7641805e6c1d06b495525c7faf00c44f85a7ee6476649034969 +"@radix-ui/react-tooltip@npm:^1.0.7": + version: 1.1.4 + resolution: "@radix-ui/react-tooltip@npm:1.1.4" + dependencies: + "@radix-ui/primitive": "npm:1.1.0" + "@radix-ui/react-compose-refs": "npm:1.1.0" + "@radix-ui/react-context": "npm:1.1.1" + "@radix-ui/react-dismissable-layer": "npm:1.1.1" + "@radix-ui/react-id": "npm:1.1.0" + "@radix-ui/react-popper": "npm:1.2.0" + "@radix-ui/react-portal": "npm:1.1.2" + "@radix-ui/react-presence": "npm:1.1.1" + "@radix-ui/react-primitive": "npm:2.0.0" + "@radix-ui/react-slot": "npm:1.1.0" + "@radix-ui/react-use-controllable-state": "npm:1.1.0" + "@radix-ui/react-visually-hidden": "npm:1.1.0" + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 10/de595811329ce789e770fd3c361c6b6e7dde3f0faa4b31d8cf72adb59036734d8389c3116756cd127dd1e7674ae1df5fa2365cf201f76413f26b24662299bde4 languageName: node linkType: hard -"@protobufjs/base64@npm:^1.1.2": - version: 1.1.2 - resolution: "@protobufjs/base64@npm:1.1.2" - checksum: 10/c71b100daeb3c9bdccab5cbc29495b906ba0ae22ceedc200e1ba49717d9c4ab15a6256839cebb6f9c6acae4ed7c25c67e0a95e734f612b258261d1a3098fe342 +"@radix-ui/react-use-callback-ref@npm:1.1.0": + version: 1.1.0 + resolution: "@radix-ui/react-use-callback-ref@npm:1.1.0" + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10/2ec7903c67e3034b646005556f44fd975dc5204db6885fc58403e3584f27d95f0b573bc161de3d14fab9fda25150bf3b91f718d299fdfc701c736bd0bd2281fa languageName: node linkType: hard -"@protobufjs/codegen@npm:^2.0.4": - version: 2.0.4 - resolution: "@protobufjs/codegen@npm:2.0.4" - checksum: 10/c6ee5fa172a8464f5253174d3c2353ea520c2573ad7b6476983d9b1346f4d8f2b44aa29feb17a949b83c1816bc35286a5ea265ed9d8fdd2865acfa09668c0447 +"@radix-ui/react-use-controllable-state@npm:1.1.0": + version: 1.1.0 + resolution: "@radix-ui/react-use-controllable-state@npm:1.1.0" + dependencies: + "@radix-ui/react-use-callback-ref": "npm:1.1.0" + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10/9583679150dc521c9de20ee22cb858697dd4f5cefc46ab8ebfc5e7511415a053994e87d4ca3f49de84d27eebc13535b0a6c9892c91ab43e3e553e5d7270f378f languageName: node linkType: hard -"@protobufjs/eventemitter@npm:^1.1.0": +"@radix-ui/react-use-escape-keydown@npm:1.1.0": version: 1.1.0 - resolution: "@protobufjs/eventemitter@npm:1.1.0" - checksum: 10/03af3e99f17ad421283d054c88a06a30a615922a817741b43ca1b13e7c6b37820a37f6eba9980fb5150c54dba6e26cb6f7b64a6f7d8afa83596fafb3afa218c3 + resolution: "@radix-ui/react-use-escape-keydown@npm:1.1.0" + dependencies: + "@radix-ui/react-use-callback-ref": "npm:1.1.0" + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10/9bf88ea272b32ea0f292afd336780a59c5646f795036b7e6105df2d224d73c54399ee5265f61d571eb545d28382491a8b02dc436e3088de8dae415d58b959b71 languageName: node linkType: hard -"@protobufjs/fetch@npm:^1.1.0": +"@radix-ui/react-use-layout-effect@npm:1.1.0": version: 1.1.0 - resolution: "@protobufjs/fetch@npm:1.1.0" - dependencies: - "@protobufjs/aspromise": "npm:^1.1.1" - "@protobufjs/inquire": "npm:^1.1.0" - checksum: 10/67ae40572ad536e4ef94269199f252c024b66e3059850906bdaee161ca1d75c73d04d35cd56f147a8a5a079f5808e342b99e61942c1dae15604ff0600b09a958 + resolution: "@radix-ui/react-use-layout-effect@npm:1.1.0" + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10/271ea0bf1cd74718895a68414a6e95537737f36e02ad08eeb61a82b229d6abda9cff3135a479e134e1f0ce2c3ff97bb85babbdce751985fb755a39b231d7ccf2 languageName: node linkType: hard -"@protobufjs/float@npm:^1.0.2": - version: 1.0.2 - resolution: "@protobufjs/float@npm:1.0.2" - checksum: 10/634c2c989da0ef2f4f19373d64187e2a79f598c5fb7991afb689d29a2ea17c14b796b29725945fa34b9493c17fb799e08ac0a7ccaae460ee1757d3083ed35187 +"@radix-ui/react-use-previous@npm:1.1.0": + version: 1.1.0 + resolution: "@radix-ui/react-use-previous@npm:1.1.0" + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10/8a2407e3db6248ab52bf425f5f4161355d09f1a228038094959250ae53552e73543532b3bb80e452f6ad624621e2e1c6aebb8c702f2dfaa5e89f07ec629d9304 languageName: node linkType: hard -"@protobufjs/inquire@npm:^1.1.0": +"@radix-ui/react-use-rect@npm:1.1.0": version: 1.1.0 - resolution: "@protobufjs/inquire@npm:1.1.0" - checksum: 10/c09efa34a5465cb120775e1a482136f2340a58b4abce7e93d72b8b5a9324a0e879275016ef9fcd73d72a4731639c54f2bb755bb82f916e4a78892d1d840bb3d2 + resolution: "@radix-ui/react-use-rect@npm:1.1.0" + dependencies: + "@radix-ui/rect": "npm:1.1.0" + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10/facc9528af43df3b01952dbb915ff751b5924db2c31d41f053ddea19a7cc5cac5b096c4d7a2059e8f564a3f0d4a95bcd909df8faed52fa01709af27337628e2c languageName: node linkType: hard -"@protobufjs/path@npm:^1.1.2": - version: 1.1.2 - resolution: "@protobufjs/path@npm:1.1.2" - checksum: 10/bb709567935fd385a86ad1f575aea98131bbd719c743fb9b6edd6b47ede429ff71a801cecbd64fc72deebf4e08b8f1bd8062793178cdaed3713b8d15771f9b83 +"@radix-ui/react-use-size@npm:1.1.0": + version: 1.1.0 + resolution: "@radix-ui/react-use-size@npm:1.1.0" + dependencies: + "@radix-ui/react-use-layout-effect": "npm:1.1.0" + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10/01a11d4c07fc620b8a081e53d7ec8495b19a11e02688f3d9f47cf41a5fe0428d1e52ed60b2bf88dfd447dc2502797b9dad2841097389126dd108530913c4d90d languageName: node linkType: hard -"@protobufjs/pool@npm:^1.1.0": +"@radix-ui/react-visually-hidden@npm:1.1.0": version: 1.1.0 - resolution: "@protobufjs/pool@npm:1.1.0" - checksum: 10/b9c7047647f6af28e92aac54f6f7c1f7ff31b201b4bfcc7a415b2861528854fce3ec666d7e7e10fd744da905f7d4aef2205bbcc8944ca0ca7a82e18134d00c46 + resolution: "@radix-ui/react-visually-hidden@npm:1.1.0" + dependencies: + "@radix-ui/react-primitive": "npm:2.0.0" + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 10/9e30775dc3bd562722b5671d91545e3e16111f9d1942c98188cb84935eb4a7d31ef1ad1e028e1f1d41e490392f295fbd55424106263869cc7028de9f6141363d languageName: node linkType: hard -"@protobufjs/utf8@npm:^1.1.0": +"@radix-ui/rect@npm:1.1.0": version: 1.1.0 - resolution: "@protobufjs/utf8@npm:1.1.0" - checksum: 10/131e289c57534c1d73a0e55782d6751dd821db1583cb2f7f7e017c9d6747addaebe79f28120b2e0185395d990aad347fb14ffa73ef4096fa38508d61a0e64602 + resolution: "@radix-ui/rect@npm:1.1.0" + checksum: 10/3ffdc5e3f7bcd91de4d5983513bd11c3a82b89b966e5c1bd8c17690a8f5da2d83fa156474c7b68fc6b9465df2281f81983b146e1d9dc57d332abda05751a9cbc languageName: node linkType: hard @@ -5873,6 +7552,27 @@ __metadata: languageName: node linkType: hard +"@react-hook/intersection-observer@npm:^3.1.1": + version: 3.1.2 + resolution: "@react-hook/intersection-observer@npm:3.1.2" + dependencies: + "@react-hook/passive-layout-effect": "npm:^1.2.0" + intersection-observer: "npm:^0.10.0" + peerDependencies: + react: ">=16.8" + checksum: 10/fee587ca06bdfe02aa3a30b95bb9fb63cc745aafa74a34e68b2c1938b6b34e8807f02e8a9f3d656ab4b399f30cd6aaa88ef7b486ec1ee1f848e6e726d73f256d + languageName: node + linkType: hard + +"@react-hook/passive-layout-effect@npm:^1.2.0": + version: 1.2.1 + resolution: "@react-hook/passive-layout-effect@npm:1.2.1" + peerDependencies: + react: ">=16.8" + checksum: 10/217cb8aa90fb8e677672319a9a466d7752890cf4357c76df000b207696e9cc717cf2ee88080671cc9dae238a82f92093ab4f61ab2f6032d2a8db958fc7d99b5d + languageName: node + linkType: hard + "@react-native-aria/button@npm:^0.2.4": version: 0.2.7 resolution: "@react-native-aria/button@npm:0.2.7" @@ -7914,6 +9614,13 @@ __metadata: languageName: node linkType: hard +"@stitches/core@npm:^1.2.6": + version: 1.2.8 + resolution: "@stitches/core@npm:1.2.8" + checksum: 10/bdee1772f033b20bfb1081a3bb8c3e85f2a44f381379d4c2ae21408336025e2d2578f79eec74db26824b852ebcc72de3edfc8c51418608f189aa5ffc26df667a + languageName: node + linkType: hard + "@swc/core-darwin-arm64@npm:1.9.2": version: 1.9.2 resolution: "@swc/core-darwin-arm64@npm:1.9.2" @@ -8290,6 +9997,15 @@ __metadata: languageName: node linkType: hard +"@types/acorn@npm:^4.0.0": + version: 4.0.6 + resolution: "@types/acorn@npm:4.0.6" + dependencies: + "@types/estree": "npm:*" + checksum: 10/e00671d5055d06b07feccb8c2841467a4bdd1ab95a29e191d51cacc08c496e1ba1f54edeefab274bb2ba51cb45b0aaaa662a63897650e9d02e9997ad82124ae4 + languageName: node + linkType: hard + "@types/babel__core@npm:^7.1.14, @types/babel__core@npm:^7.20.5": version: 7.20.5 resolution: "@types/babel__core@npm:7.20.5" @@ -8440,6 +10156,15 @@ __metadata: languageName: node linkType: hard +"@types/estree-jsx@npm:^1.0.0": + version: 1.0.5 + resolution: "@types/estree-jsx@npm:1.0.5" + dependencies: + "@types/estree": "npm:*" + checksum: 10/a028ab0cd7b2950168a05c6a86026eb3a36a54a4adfae57f13911d7b49dffe573d9c2b28421b2d029b49b3d02fcd686611be2622dc3dad6d9791166c083f6008 + languageName: node + linkType: hard + "@types/estree@npm:*, @types/estree@npm:1.0.6, @types/estree@npm:^1.0.0, @types/estree@npm:^1.0.6": version: 1.0.6 resolution: "@types/estree@npm:1.0.6" @@ -8671,7 +10396,7 @@ __metadata: languageName: node linkType: hard -"@types/markdown-it@npm:^14.1.2": +"@types/markdown-it@npm:^14, @types/markdown-it@npm:^14.1.2": version: 14.1.2 resolution: "@types/markdown-it@npm:14.1.2" dependencies: @@ -8991,6 +10716,13 @@ __metadata: languageName: node linkType: hard +"@types/turndown@npm:^5.0.5": + version: 5.0.5 + resolution: "@types/turndown@npm:5.0.5" + checksum: 10/8b5e246f4fec5135d3313954b2be32aa24d599fbae15d75a1627066e1279098f260eaaf191fd8a43bf4a0678f8cf5d120e7fb19839668c237d5569b0836324fd + languageName: node + linkType: hard + "@types/unist@npm:*, @types/unist@npm:^3.0.0": version: 3.0.3 resolution: "@types/unist@npm:3.0.3" @@ -9411,6 +11143,7 @@ __metadata: dependencies: "@ant-design/icons": "npm:^5.5.1" "@casl/ability": "npm:^6.7.2" + "@mdxeditor/editor": "npm:^3.20.0" "@refinedev/antd": "npm:^5.44.0" "@refinedev/cli": "npm:^2.16.40" "@refinedev/core": "npm:^4.56.0" @@ -9423,6 +11156,7 @@ __metadata: "@tanstack/router-plugin": "npm:^1.81.9" "@types/lodash.isequal": "npm:^4.5.8" "@types/luxon": "npm:^3.4.2" + "@types/markdown-it": "npm:^14" "@types/pluralize": "npm:^0.0.33" "@types/react": "npm:~18.3.12" "@types/react-dom": "npm:~18.3.1" @@ -9441,6 +11175,7 @@ __metadata: graphql-scalars: "npm:^1.23.0" lodash.isequal: "npm:^4.5.0" luxon: "npm:^3.5.0" + markdown-it: "npm:^14.1.0" normalize.css: "npm:^8.0.1" pluralize: "npm:^8.0.0" rc-picker: "npm:^4.8.2" @@ -9504,6 +11239,7 @@ __metadata: "@types/normalize-path": "npm:^3.0.2" "@types/pg": "npm:^8.11.10" "@types/serve-static": "npm:^1.15.7" + "@types/turndown": "npm:^5.0.5" "@ukdanceblue/common": "workspace:^" "@ukdanceblue/portal": "workspace:^" class-validator: "npm:^0.14.1" @@ -9535,6 +11271,7 @@ __metadata: thumbhash: "npm:^0.1.1" ts-node: "npm:^10.9.2" ts-results-es: "npm:^4.2.0" + turndown: "npm:^7.2.0" type-graphql: "npm:^2.0.0-rc.2" typescript: "npm:^5.6.3" utility-types: "npm:^3.11.0" @@ -9987,7 +11724,7 @@ __metadata: languageName: node linkType: hard -"acorn-jsx@npm:^5.3.2": +"acorn-jsx@npm:^5.0.0, acorn-jsx@npm:^5.3.2": version: 5.3.2 resolution: "acorn-jsx@npm:5.3.2" peerDependencies: @@ -10005,7 +11742,7 @@ __metadata: languageName: node linkType: hard -"acorn@npm:^8.11.0, acorn@npm:^8.14.0, acorn@npm:^8.4.1, acorn@npm:^8.8.1, acorn@npm:^8.8.2": +"acorn@npm:^8.0.0, acorn@npm:^8.11.0, acorn@npm:^8.14.0, acorn@npm:^8.4.1, acorn@npm:^8.8.1, acorn@npm:^8.8.2": version: 8.14.0 resolution: "acorn@npm:8.14.0" bin: @@ -10110,6 +11847,13 @@ __metadata: languageName: node linkType: hard +"anser@npm:^2.1.1": + version: 2.3.0 + resolution: "anser@npm:2.3.0" + checksum: 10/a3be4be16b753c6bf4790d93bac4f64819d29dbb09592ade1979d3cb3584d7f2cb229fcff33b3519978f3c8bf5a85dac70fb1182a45c42c72826aba5e512ee98 + languageName: node + linkType: hard + "ansi-align@npm:^3.0.0": version: 3.0.1 resolution: "ansi-align@npm:3.0.1" @@ -10421,6 +12165,15 @@ __metadata: languageName: node linkType: hard +"aria-hidden@npm:^1.1.1": + version: 1.2.4 + resolution: "aria-hidden@npm:1.2.4" + dependencies: + tslib: "npm:^2.0.0" + checksum: 10/df4bc15423aaaba3729a7d40abcbf6d3fffa5b8fd5eb33d3ac8b7da0110c47552fca60d97f2e1edfbb68a27cae1da499f1c3896966efb3e26aac4e3b57e3cc8b + languageName: node + linkType: hard + "arr-diff@npm:^2.0.0": version: 2.0.0 resolution: "arr-diff@npm:2.0.0" @@ -11573,6 +13326,16 @@ __metadata: languageName: node linkType: hard +"buffer@npm:^6.0.3": + version: 6.0.3 + resolution: "buffer@npm:6.0.3" + dependencies: + base64-js: "npm:^1.3.1" + ieee754: "npm:^1.2.1" + checksum: 10/b6bc68237ebf29bdacae48ce60e5e28fc53ae886301f2ad9496618efac49427ed79096750033e7eab1897a4f26ae374ace49106a5758f38fb70c78c9fda2c3b1 + languageName: node + linkType: hard + "builtin-modules@npm:^3.3.0": version: 3.3.0 resolution: "builtin-modules@npm:3.3.0" @@ -11983,6 +13746,13 @@ __metadata: languageName: node linkType: hard +"character-reference-invalid@npm:^2.0.0": + version: 2.0.1 + resolution: "character-reference-invalid@npm:2.0.1" + checksum: 10/98d3b1a52ae510b7329e6ee7f6210df14f1e318c5415975d4c9e7ee0ef4c07875d47c6e74230c64551f12f556b4a8ccc24d9f3691a2aa197019e72a95e9297ee + languageName: node + linkType: hard + "chardet@npm:^0.7.0": version: 0.7.0 resolution: "chardet@npm:0.7.0" @@ -12175,6 +13945,13 @@ __metadata: languageName: node linkType: hard +"clean-set@npm:^1.1.2": + version: 1.1.2 + resolution: "clean-set@npm:1.1.2" + checksum: 10/b460b59c82a11945052b59efa1d51405c0ba11d227f2c2140e9f7803d702b4a36354bfee0077d46a758979891aa4322211ce392c86961f11bf57e2ff774d5506 + languageName: node + linkType: hard + "clean-stack@npm:^2.0.0": version: 2.2.0 resolution: "clean-stack@npm:2.2.0" @@ -12336,6 +14113,33 @@ __metadata: languageName: node linkType: hard +"cm6-theme-basic-light@npm:^0.2.0": + version: 0.2.0 + resolution: "cm6-theme-basic-light@npm:0.2.0" + peerDependencies: + "@codemirror/language": ^6.0.0 + "@codemirror/state": ^6.0.0 + "@codemirror/view": ^6.0.0 + "@lezer/highlight": ^1.0.0 + checksum: 10/8eb28ac7d8d7abdd8e5e4589d6abe459f1fe841940e49ea6431135c446b93af67bccab3981627c8e6377d1199f64879691570d6d1d409f9df9627bf4edceb1fc + languageName: node + linkType: hard + +"codemirror@npm:^6.0.1": + version: 6.0.1 + resolution: "codemirror@npm:6.0.1" + dependencies: + "@codemirror/autocomplete": "npm:^6.0.0" + "@codemirror/commands": "npm:^6.0.0" + "@codemirror/language": "npm:^6.0.0" + "@codemirror/lint": "npm:^6.0.0" + "@codemirror/search": "npm:^6.0.0" + "@codemirror/state": "npm:^6.0.0" + "@codemirror/view": "npm:^6.0.0" + checksum: 10/4f858cde1cf8ce4670de9df4a64f4990bb8abdb8e13d3e437f278c40c86d841ef505aa1e5dc798582109ceaac8577a3bb4a1f026c0e5ce730465c89652ee6036 + languageName: node + linkType: hard + "collection-visit@npm:^1.0.0": version: 1.0.0 resolution: "collection-visit@npm:1.0.0" @@ -12556,6 +14360,13 @@ __metadata: languageName: node linkType: hard +"compute-scroll-into-view@npm:^2.0.4": + version: 2.0.4 + resolution: "compute-scroll-into-view@npm:2.0.4" + checksum: 10/a9015cbf464ed852d3c459c1777d5890e26925dd2e99ad438dc8cb6a0154f33f0ce6856f6c50de9dd176168d315e7223d08c4bae1e5dbe82b056dd5216c0bcc6 + languageName: node + linkType: hard + "compute-scroll-into-view@npm:^3.0.2": version: 3.1.0 resolution: "compute-scroll-into-view@npm:3.1.0" @@ -12824,6 +14635,13 @@ __metadata: languageName: node linkType: hard +"crelt@npm:^1.0.5": + version: 1.0.6 + resolution: "crelt@npm:1.0.6" + checksum: 10/5ed326ca6bd243b1dba6b943f665b21c2c04be03271824bc48f20dba324b0f8233e221f8c67312526d24af2b1243c023dc05a41bd8bd05d1a479fd2c72fb39c3 + languageName: node + linkType: hard + "croner@npm:^9.0.0": version: 9.0.0 resolution: "croner@npm:9.0.0" @@ -13034,6 +14852,16 @@ __metadata: languageName: node linkType: hard +"d@npm:1, d@npm:^1.0.1, d@npm:^1.0.2": + version: 1.0.2 + resolution: "d@npm:1.0.2" + dependencies: + es5-ext: "npm:^0.10.64" + type: "npm:^2.7.2" + checksum: 10/a3f45ef964622f683f6a1cb9b8dcbd75ce490cd2f4ac9794099db3d8f0e2814d412d84cd3fe522e58feb1f273117bb480f29c5381f6225f0abca82517caaa77a + languageName: node + linkType: hard + "dashdash@npm:^1.12.0": version: 1.14.1 resolution: "dashdash@npm:1.14.1" @@ -13396,7 +15224,7 @@ __metadata: languageName: node linkType: hard -"dequal@npm:^2.0.0": +"dequal@npm:^2.0.0, dequal@npm:^2.0.2": version: 2.0.3 resolution: "dequal@npm:2.0.3" checksum: 10/6ff05a7561f33603df87c45e389c9ac0a95e3c056be3da1a0c4702149e3a7f6fe5ffbb294478687ba51a9e95f3a60e8b6b9005993acd79c292c7d15f71964b6b @@ -13463,7 +15291,14 @@ __metadata: languageName: node linkType: hard -"devlop@npm:^1.0.0": +"detect-node-es@npm:^1.1.0": + version: 1.1.0 + resolution: "detect-node-es@npm:1.1.0" + checksum: 10/e46307d7264644975b71c104b9f028ed1d3d34b83a15b8a22373640ce5ea630e5640b1078b8ea15f202b54641da71e4aa7597093bd4b91f113db520a26a37449 + languageName: node + linkType: hard + +"devlop@npm:^1.0.0, devlop@npm:^1.1.0": version: 1.1.0 resolution: "devlop@npm:1.1.0" dependencies: @@ -13486,7 +15321,7 @@ __metadata: languageName: node linkType: hard -"diff@npm:^5.0.0": +"diff@npm:^5.0.0, diff@npm:^5.1.0": version: 5.2.0 resolution: "diff@npm:5.2.0" checksum: 10/01b7b440f83a997350a988e9d2f558366c0f90f15be19f4aa7f1bb3109a4e153dfc3b9fbf78e14ea725717017407eeaa2271e3896374a0181e8f52445740846d @@ -13603,6 +15438,21 @@ __metadata: languageName: node linkType: hard +"downshift@npm:^7.6.0": + version: 7.6.2 + resolution: "downshift@npm:7.6.2" + dependencies: + "@babel/runtime": "npm:^7.14.8" + compute-scroll-into-view: "npm:^2.0.4" + prop-types: "npm:^15.7.2" + react-is: "npm:^17.0.2" + tslib: "npm:^2.3.0" + peerDependencies: + react: ">=16.12.0" + checksum: 10/4ffca012d185a6eb57d5543650401323c932ae5f7227f92419aba4f04d5de2ca4f0270aa398caa289622f60ebb5f831fddf11e5d6d56981a89f7fbd7ed0a1f48 + languageName: node + linkType: hard + "dree@npm:^5.1.5": version: 5.1.5 resolution: "dree@npm:5.1.5" @@ -13983,6 +15833,39 @@ __metadata: languageName: node linkType: hard +"es5-ext@npm:^0.10.35, es5-ext@npm:^0.10.62, es5-ext@npm:^0.10.64, es5-ext@npm:~0.10.14": + version: 0.10.64 + resolution: "es5-ext@npm:0.10.64" + dependencies: + es6-iterator: "npm:^2.0.3" + es6-symbol: "npm:^3.1.3" + esniff: "npm:^2.0.1" + next-tick: "npm:^1.1.0" + checksum: 10/0c5d8657708b1695ddc4b06f4e0b9fbdda4d2fe46d037b6bedb49a7d1931e542ec9eecf4824d59e1d357e93229deab014bb4b86485db2d41b1d68e54439689ce + languageName: node + linkType: hard + +"es6-iterator@npm:^2.0.3": + version: 2.0.3 + resolution: "es6-iterator@npm:2.0.3" + dependencies: + d: "npm:1" + es5-ext: "npm:^0.10.35" + es6-symbol: "npm:^3.1.1" + checksum: 10/dbadecf3d0e467692815c2b438dfa99e5a97cbbecf4a58720adcb467a04220e0e36282399ba297911fd472c50ae4158fffba7ed0b7d4273fe322b69d03f9e3a5 + languageName: node + linkType: hard + +"es6-symbol@npm:^3, es6-symbol@npm:^3.1.1, es6-symbol@npm:^3.1.3": + version: 3.1.4 + resolution: "es6-symbol@npm:3.1.4" + dependencies: + d: "npm:^1.0.2" + ext: "npm:^1.7.0" + checksum: 10/3743119fe61f89e2f049a6ce52bd82fab5f65d13e2faa72453b73f95c15292c3cb9bdf3747940d504517e675e45fd375554c6b5d35d2bcbefd35f5489ecba546 + languageName: node + linkType: hard + "esbuild@npm:^0.21.3": version: 0.21.5 resolution: "esbuild@npm:0.21.5" @@ -14236,6 +16119,13 @@ __metadata: languageName: node linkType: hard +"escape-carriage@npm:^1.3.1": + version: 1.3.1 + resolution: "escape-carriage@npm:1.3.1" + checksum: 10/6d7613a5875977b04eb4651f3fa14eb8f54c72aa198b603c823502fcfef9f5969afc4c76d27b2f2fc008d88764fbf4c3a6e04d549d6134e2ff7f705c99e3ddb9 + languageName: node + linkType: hard + "escape-html@npm:^1.0.3, escape-html@npm:~1.0.3": version: 1.0.3 resolution: "escape-html@npm:1.0.3" @@ -14479,6 +16369,18 @@ __metadata: languageName: node linkType: hard +"esniff@npm:^2.0.1": + version: 2.0.1 + resolution: "esniff@npm:2.0.1" + dependencies: + d: "npm:^1.0.1" + es5-ext: "npm:^0.10.62" + event-emitter: "npm:^0.3.5" + type: "npm:^2.7.2" + checksum: 10/f6a2abd2f8c5fe57c5fcf53e5407c278023313d0f6c3a92688e7122ab9ac233029fd424508a196ae5bc561aa1f67d23f4e2435b1a0d378030f476596129056ac + languageName: node + linkType: hard + "espree@npm:^10.0.1, espree@npm:^10.3.0": version: 10.3.0 resolution: "espree@npm:10.3.0" @@ -14525,6 +16427,23 @@ __metadata: languageName: node linkType: hard +"estree-util-is-identifier-name@npm:^3.0.0": + version: 3.0.0 + resolution: "estree-util-is-identifier-name@npm:3.0.0" + checksum: 10/cdc9187614fdb269d714eddfdf72c270a79daa9ed51e259bb78527983be6dcc68da6a914ccc41175b662194c67fbd2a1cd262f85fac1eef7111cfddfaf6f77f8 + languageName: node + linkType: hard + +"estree-util-visit@npm:^2.0.0": + version: 2.0.0 + resolution: "estree-util-visit@npm:2.0.0" + dependencies: + "@types/estree-jsx": "npm:^1.0.0" + "@types/unist": "npm:^3.0.0" + checksum: 10/e3c39d34c8b42fc2067dfa64d460f754b43cca4b573b031a5e5bb185e02c4efc753353197815bbb094b8149a781ab76f18116bec8056b5ff375162e68bffa0bd + languageName: node + linkType: hard + "estree-walker@npm:^3.0.3": version: 3.0.3 resolution: "estree-walker@npm:3.0.3" @@ -14548,6 +16467,16 @@ __metadata: languageName: node linkType: hard +"event-emitter@npm:^0.3.5": + version: 0.3.5 + resolution: "event-emitter@npm:0.3.5" + dependencies: + d: "npm:1" + es5-ext: "npm:~0.10.14" + checksum: 10/a7f5ea80029193f4869782d34ef7eb43baa49cd397013add1953491b24588468efbe7e3cc9eb87d53f33397e7aab690fd74c079ec440bf8b12856f6bdb6e9396 + languageName: node + linkType: hard + "event-target-shim@npm:^5.0.0, event-target-shim@npm:^5.0.1": version: 5.0.1 resolution: "event-target-shim@npm:5.0.1" @@ -15241,6 +17170,15 @@ __metadata: languageName: node linkType: hard +"ext@npm:^1.7.0": + version: 1.7.0 + resolution: "ext@npm:1.7.0" + dependencies: + type: "npm:^2.7.2" + checksum: 10/666a135980b002df0e75c8ac6c389140cdc59ac953db62770479ee2856d58ce69d2f845e5f2586716350b725400f6945e51e9159573158c39f369984c72dcd84 + languageName: node + linkType: hard + "extend-shallow@npm:^2.0.1": version: 2.0.1 resolution: "extend-shallow@npm:2.0.1" @@ -15414,6 +17352,15 @@ __metadata: languageName: node linkType: hard +"fault@npm:^2.0.0": + version: 2.0.1 + resolution: "fault@npm:2.0.1" + dependencies: + format: "npm:^0.2.0" + checksum: 10/c9b30f47d95769177130a9409976a899ed31eb598450fbad5b0d39f2f5f56d5f4a9ff9257e0bee8407cb0fc3ce37165657888c6aa6d78472e403893104329b72 + languageName: node + linkType: hard + "fb-watchman@npm:^2.0.0": version: 2.0.2 resolution: "fb-watchman@npm:2.0.2" @@ -15834,6 +17781,13 @@ __metadata: languageName: node linkType: hard +"format@npm:^0.2.0": + version: 0.2.2 + resolution: "format@npm:0.2.2" + checksum: 10/5f878b8fc1a672c8cbefa4f293bdd977c822862577d70d53456a48b4169ec9b51677c0c995bf62c633b4e5cd673624b7c273f57923b28735a6c0c0a72c382a4a + languageName: node + linkType: hard + "formdata-polyfill@npm:^4.0.10": version: 4.0.10 resolution: "formdata-polyfill@npm:4.0.10" @@ -16044,6 +17998,13 @@ __metadata: languageName: node linkType: hard +"get-nonce@npm:^1.0.0": + version: 1.0.1 + resolution: "get-nonce@npm:1.0.1" + checksum: 10/ad5104871d114a694ecc506a2d406e2331beccb961fe1e110dc25556b38bcdbf399a823a8a375976cd8889668156a9561e12ebe3fa6a4c6ba169c8466c2ff868 + languageName: node + linkType: hard + "get-package-type@npm:^0.1.0": version: 0.1.0 resolution: "get-package-type@npm:0.1.0" @@ -16952,7 +18913,7 @@ __metadata: languageName: node linkType: hard -"ieee754@npm:^1.1.13": +"ieee754@npm:^1.1.13, ieee754@npm:^1.2.1": version: 1.2.1 resolution: "ieee754@npm:1.2.1" checksum: 10/d9f2557a59036f16c282aaeb107832dc957a93d73397d89bbad4eb1130560560eb695060145e8e6b3b498b15ab95510226649a0b8f52ae06583575419fe10fc4 @@ -17165,6 +19126,13 @@ __metadata: languageName: node linkType: hard +"intersection-observer@npm:^0.10.0": + version: 0.10.0 + resolution: "intersection-observer@npm:0.10.0" + checksum: 10/d9ffce291459a2c4ada9e45f23ef805361691f93e9a41a2afe801d29e6c9b1d0054f7faddafd79ecc341d7008e1f921a2e67cadeae9692d9831af903f23fc644 + languageName: node + linkType: hard + "intl-messageformat@npm:^10.1.0": version: 10.7.6 resolution: "intl-messageformat@npm:10.7.6" @@ -17243,6 +19211,13 @@ __metadata: languageName: node linkType: hard +"is-alphabetical@npm:^2.0.0": + version: 2.0.1 + resolution: "is-alphabetical@npm:2.0.1" + checksum: 10/56207db8d9de0850f0cd30f4966bf731eb82cedfe496cbc2e97e7c3bacaf66fc54a972d2d08c0d93bb679cb84976a05d24c5ad63de56fabbfc60aadae312edaa + languageName: node + linkType: hard + "is-alphanumerical@npm:^1.0.0": version: 1.0.4 resolution: "is-alphanumerical@npm:1.0.4" @@ -17253,6 +19228,16 @@ __metadata: languageName: node linkType: hard +"is-alphanumerical@npm:^2.0.0": + version: 2.0.1 + resolution: "is-alphanumerical@npm:2.0.1" + dependencies: + is-alphabetical: "npm:^2.0.0" + is-decimal: "npm:^2.0.0" + checksum: 10/87acc068008d4c9c4e9f5bd5e251041d42e7a50995c77b1499cf6ed248f971aadeddb11f239cabf09f7975ee58cac7a48ffc170b7890076d8d227b24a68663c9 + languageName: node + linkType: hard + "is-arguments@npm:^1.0.4": version: 1.1.1 resolution: "is-arguments@npm:1.1.1" @@ -17406,6 +19391,13 @@ __metadata: languageName: node linkType: hard +"is-decimal@npm:^2.0.0": + version: 2.0.1 + resolution: "is-decimal@npm:2.0.1" + checksum: 10/97132de7acdce77caa7b797632970a2ecd649a88e715db0e4dbc00ab0708b5e7574ba5903962c860cd4894a14fd12b100c0c4ac8aed445cf6f55c6cf747a4158 + languageName: node + linkType: hard + "is-descriptor@npm:^0.1.0": version: 0.1.7 resolution: "is-descriptor@npm:0.1.7" @@ -17561,6 +19553,13 @@ __metadata: languageName: node linkType: hard +"is-hexadecimal@npm:^2.0.0": + version: 2.0.1 + resolution: "is-hexadecimal@npm:2.0.1" + checksum: 10/66a2ea85994c622858f063f23eda506db29d92b52580709eb6f4c19550552d4dcf3fb81952e52f7cf972097237959e00adc7bb8c9400cd12886e15bf06145321 + languageName: node + linkType: hard + "is-hotkey@npm:^0.1.6": version: 0.1.8 resolution: "is-hotkey@npm:0.1.8" @@ -18256,26 +20255,26 @@ __metadata: languageName: node linkType: hard -"js-yaml@npm:^3.13.0, js-yaml@npm:^3.13.1": - version: 3.14.1 - resolution: "js-yaml@npm:3.14.1" +"js-yaml@npm:4.1.0, js-yaml@npm:^4.0.0, js-yaml@npm:^4.1.0": + version: 4.1.0 + resolution: "js-yaml@npm:4.1.0" dependencies: - argparse: "npm:^1.0.7" - esprima: "npm:^4.0.0" + argparse: "npm:^2.0.1" bin: js-yaml: bin/js-yaml.js - checksum: 10/9e22d80b4d0105b9899135365f746d47466ed53ef4223c529b3c0f7a39907743fdbd3c4379f94f1106f02755b5e90b2faaf84801a891135544e1ea475d1a1379 + checksum: 10/c138a34a3fd0d08ebaf71273ad4465569a483b8a639e0b118ff65698d257c2791d3199e3f303631f2cb98213fa7b5f5d6a4621fd0fff819421b990d30d967140 languageName: node linkType: hard -"js-yaml@npm:^4.0.0, js-yaml@npm:^4.1.0": - version: 4.1.0 - resolution: "js-yaml@npm:4.1.0" +"js-yaml@npm:^3.13.0, js-yaml@npm:^3.13.1": + version: 3.14.1 + resolution: "js-yaml@npm:3.14.1" dependencies: - argparse: "npm:^2.0.1" + argparse: "npm:^1.0.7" + esprima: "npm:^4.0.0" bin: js-yaml: bin/js-yaml.js - checksum: 10/c138a34a3fd0d08ebaf71273ad4465569a483b8a639e0b118ff65698d257c2791d3199e3f303631f2cb98213fa7b5f5d6a4621fd0fff819421b990d30d967140 + checksum: 10/9e22d80b4d0105b9899135365f746d47466ed53ef4223c529b3c0f7a39907743fdbd3c4379f94f1106f02755b5e90b2faaf84801a891135544e1ea475d1a1379 languageName: node linkType: hard @@ -18681,6 +20680,13 @@ __metadata: languageName: node linkType: hard +"lexical@npm:0.17.1, lexical@npm:^0.17.1": + version: 0.17.1 + resolution: "lexical@npm:0.17.1" + checksum: 10/e9f202800b8a7ebb00a40726e12cf9840a39c462563a5dc8aae1d53df0667b0c85f4051bd8749f650bbddf366fa2725621d99a77a17279fa50b98be68d2e5185 + languageName: node + linkType: hard + "libphonenumber-js@npm:^1.10.53": version: 1.11.14 resolution: "libphonenumber-js@npm:1.11.14" @@ -19266,7 +21272,7 @@ __metadata: languageName: node linkType: hard -"lz-string@npm:*, lz-string@npm:^1.5.0": +"lz-string@npm:*, lz-string@npm:^1.4.4, lz-string@npm:^1.5.0": version: 1.5.0 resolution: "lz-string@npm:1.5.0" bin: @@ -19488,6 +21494,22 @@ __metadata: languageName: node linkType: hard +"mdast-util-directive@npm:^3.0.0": + version: 3.0.0 + resolution: "mdast-util-directive@npm:3.0.0" + dependencies: + "@types/mdast": "npm:^4.0.0" + "@types/unist": "npm:^3.0.0" + devlop: "npm:^1.0.0" + mdast-util-from-markdown: "npm:^2.0.0" + mdast-util-to-markdown: "npm:^2.0.0" + parse-entities: "npm:^4.0.0" + stringify-entities: "npm:^4.0.0" + unist-util-visit-parents: "npm:^6.0.0" + checksum: 10/a205af936302467648b6007704b40e31a822016789402cbcb0239d23ce7a48e676db1cd6792c9318c1047a47c5b3956b2bd0053f14c8d257528404d6bf9b9ab4 + languageName: node + linkType: hard + "mdast-util-find-and-replace@npm:^1.1.0": version: 1.1.1 resolution: "mdast-util-find-and-replace@npm:1.1.1" @@ -19544,6 +21566,40 @@ __metadata: languageName: node linkType: hard +"mdast-util-from-markdown@npm:^2.0.0": + version: 2.0.2 + resolution: "mdast-util-from-markdown@npm:2.0.2" + dependencies: + "@types/mdast": "npm:^4.0.0" + "@types/unist": "npm:^3.0.0" + decode-named-character-reference: "npm:^1.0.0" + devlop: "npm:^1.0.0" + mdast-util-to-string: "npm:^4.0.0" + micromark: "npm:^4.0.0" + micromark-util-decode-numeric-character-reference: "npm:^2.0.0" + micromark-util-decode-string: "npm:^2.0.0" + micromark-util-normalize-identifier: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + unist-util-stringify-position: "npm:^4.0.0" + checksum: 10/69b207913fbcc0469f8c59d922af4d5509b79e809d77c9bd4781543a907fe2ecc8e6433ce0707066a27b117b13f38af3aae4f2d085e18ebd2d3ad5f1a5647902 + languageName: node + linkType: hard + +"mdast-util-frontmatter@npm:^2.0.1": + version: 2.0.1 + resolution: "mdast-util-frontmatter@npm:2.0.1" + dependencies: + "@types/mdast": "npm:^4.0.0" + devlop: "npm:^1.0.0" + escape-string-regexp: "npm:^5.0.0" + mdast-util-from-markdown: "npm:^2.0.0" + mdast-util-to-markdown: "npm:^2.0.0" + micromark-extension-frontmatter: "npm:^2.0.0" + checksum: 10/afd9486af6ea74a94d84a225c367ab810ad4439683ecafc1ce9fc7bb0ecacaafac82e0af529974489c145824b242509f9387f833fc01a14a83a978049772ef80 + languageName: node + linkType: hard + "mdast-util-gfm-autolink-literal@npm:^0.1.0": version: 0.1.3 resolution: "mdast-util-gfm-autolink-literal@npm:0.1.3" @@ -19597,6 +21653,17 @@ __metadata: languageName: node linkType: hard +"mdast-util-gfm-strikethrough@npm:^2.0.0": + version: 2.0.0 + resolution: "mdast-util-gfm-strikethrough@npm:2.0.0" + dependencies: + "@types/mdast": "npm:^4.0.0" + mdast-util-from-markdown: "npm:^2.0.0" + mdast-util-to-markdown: "npm:^2.0.0" + checksum: 10/b1abc137d78270540585ad94a7a4ed1630683312690b902389dae0ede50a6832e26d1be053687f49728e14fa8a379da9384342725d3beb4480fc30b12866ab37 + languageName: node + linkType: hard + "mdast-util-gfm-table@npm:^0.1.0": version: 0.1.6 resolution: "mdast-util-gfm-table@npm:0.1.6" @@ -19619,6 +21686,19 @@ __metadata: languageName: node linkType: hard +"mdast-util-gfm-table@npm:^2.0.0": + version: 2.0.0 + resolution: "mdast-util-gfm-table@npm:2.0.0" + dependencies: + "@types/mdast": "npm:^4.0.0" + devlop: "npm:^1.0.0" + markdown-table: "npm:^3.0.0" + mdast-util-from-markdown: "npm:^2.0.0" + mdast-util-to-markdown: "npm:^2.0.0" + checksum: 10/a043d60d723a86f79c49cbdd1d98b80c89f4a8f9f5fa84b3880c53e132f40150972460aba9be1f44a612ef5abd6810d122c5e7e5d9c54f3ac7560cce8c305c75 + languageName: node + linkType: hard + "mdast-util-gfm-task-list-item@npm:^0.1.0": version: 0.1.6 resolution: "mdast-util-gfm-task-list-item@npm:0.1.6" @@ -19638,6 +21718,18 @@ __metadata: languageName: node linkType: hard +"mdast-util-gfm-task-list-item@npm:^2.0.0": + version: 2.0.0 + resolution: "mdast-util-gfm-task-list-item@npm:2.0.0" + dependencies: + "@types/mdast": "npm:^4.0.0" + devlop: "npm:^1.0.0" + mdast-util-from-markdown: "npm:^2.0.0" + mdast-util-to-markdown: "npm:^2.0.0" + checksum: 10/679a3ff09b52015c0088cd0616ccecc7cc9d250d56a8762aafdffc640f3f607bbd9fe047d3e7e7078e6a996e83f677be3bfcad7ac7260563825fa80a04f8e09d + languageName: node + linkType: hard + "mdast-util-gfm@npm:^0.1.0": version: 0.1.2 resolution: "mdast-util-gfm@npm:0.1.2" @@ -19666,6 +21758,67 @@ __metadata: languageName: node linkType: hard +"mdast-util-mdx-expression@npm:^2.0.0": + version: 2.0.1 + resolution: "mdast-util-mdx-expression@npm:2.0.1" + dependencies: + "@types/estree-jsx": "npm:^1.0.0" + "@types/hast": "npm:^3.0.0" + "@types/mdast": "npm:^4.0.0" + devlop: "npm:^1.0.0" + mdast-util-from-markdown: "npm:^2.0.0" + mdast-util-to-markdown: "npm:^2.0.0" + checksum: 10/70e860f8ee22c4f478449942750055d649d4380bf43b235d0710af510189d285fb057e401d20b59596d9789f4e270fce08ca892dc849676f9e3383b991d52485 + languageName: node + linkType: hard + +"mdast-util-mdx-jsx@npm:^3.0.0": + version: 3.1.3 + resolution: "mdast-util-mdx-jsx@npm:3.1.3" + dependencies: + "@types/estree-jsx": "npm:^1.0.0" + "@types/hast": "npm:^3.0.0" + "@types/mdast": "npm:^4.0.0" + "@types/unist": "npm:^3.0.0" + ccount: "npm:^2.0.0" + devlop: "npm:^1.1.0" + mdast-util-from-markdown: "npm:^2.0.0" + mdast-util-to-markdown: "npm:^2.0.0" + parse-entities: "npm:^4.0.0" + stringify-entities: "npm:^4.0.0" + unist-util-stringify-position: "npm:^4.0.0" + vfile-message: "npm:^4.0.0" + checksum: 10/6c14f271f1380fd512038247f45887b7aa71bbf4acd8881651a317b61706b114f2582f62f7777d0eacd42c4a7b979802825c2a2fd8bb7c46a1ab931ccb1ddf3e + languageName: node + linkType: hard + +"mdast-util-mdx@npm:^3.0.0": + version: 3.0.0 + resolution: "mdast-util-mdx@npm:3.0.0" + dependencies: + mdast-util-from-markdown: "npm:^2.0.0" + mdast-util-mdx-expression: "npm:^2.0.0" + mdast-util-mdx-jsx: "npm:^3.0.0" + mdast-util-mdxjs-esm: "npm:^2.0.0" + mdast-util-to-markdown: "npm:^2.0.0" + checksum: 10/547d928f0d1e60d9087cd8ad301cdf2e1d14b094d2662a00292874b923bcb59323bdad3a29804c7f323ad78f4d3954361bfdaf4a9be765c4e6fe47a815df50c2 + languageName: node + linkType: hard + +"mdast-util-mdxjs-esm@npm:^2.0.0": + version: 2.0.1 + resolution: "mdast-util-mdxjs-esm@npm:2.0.1" + dependencies: + "@types/estree-jsx": "npm:^1.0.0" + "@types/hast": "npm:^3.0.0" + "@types/mdast": "npm:^4.0.0" + devlop: "npm:^1.0.0" + mdast-util-from-markdown: "npm:^2.0.0" + mdast-util-to-markdown: "npm:^2.0.0" + checksum: 10/05474226e163a3f407fccb5780b0d8585a95e548e5da4a85227df43f281b940c7941a9a9d4af1be4f885fe554731647addb057a728e87aa1f503ff9cc72c9163 + languageName: node + linkType: hard + "mdast-util-phrasing@npm:^3.0.0": version: 3.0.1 resolution: "mdast-util-phrasing@npm:3.0.1" @@ -19676,6 +21829,16 @@ __metadata: languageName: node linkType: hard +"mdast-util-phrasing@npm:^4.0.0": + version: 4.1.0 + resolution: "mdast-util-phrasing@npm:4.1.0" + dependencies: + "@types/mdast": "npm:^4.0.0" + unist-util-is: "npm:^6.0.0" + checksum: 10/3a97533e8ad104a422f8bebb34b3dde4f17167b8ed3a721cf9263c7416bd3447d2364e6d012a594aada40cac9e949db28a060bb71a982231693609034ed5324e + languageName: node + linkType: hard + "mdast-util-to-hast@npm:^10.2.0": version: 10.2.0 resolution: "mdast-util-to-hast@npm:10.2.0" @@ -19739,6 +21902,23 @@ __metadata: languageName: node linkType: hard +"mdast-util-to-markdown@npm:^2.0.0, mdast-util-to-markdown@npm:^2.1.0": + version: 2.1.2 + resolution: "mdast-util-to-markdown@npm:2.1.2" + dependencies: + "@types/mdast": "npm:^4.0.0" + "@types/unist": "npm:^3.0.0" + longest-streak: "npm:^3.0.0" + mdast-util-phrasing: "npm:^4.0.0" + mdast-util-to-string: "npm:^4.0.0" + micromark-util-classify-character: "npm:^2.0.0" + micromark-util-decode-string: "npm:^2.0.0" + unist-util-visit: "npm:^5.0.0" + zwitch: "npm:^2.0.0" + checksum: 10/ab494a32f1ec90f0a502970b403b1847a10f3ba635adddb66ce70994cc47b4924c6c05078ddd29a8c2c5c9bc8c0bcc20e5fc1ef0fcb9b0cb9c0589a000817f1c + languageName: node + linkType: hard + "mdast-util-to-string@npm:^2.0.0": version: 2.0.0 resolution: "mdast-util-to-string@npm:2.0.0" @@ -19755,6 +21935,15 @@ __metadata: languageName: node linkType: hard +"mdast-util-to-string@npm:^4.0.0": + version: 4.0.0 + resolution: "mdast-util-to-string@npm:4.0.0" + dependencies: + "@types/mdast": "npm:^4.0.0" + checksum: 10/f4a5dbb9ea03521d7d3e26a9ba5652a1d6fbd55706dddd2155427517085688830e0ecd3f12418cfd40892640886eb39a4034c3c967d85e01e2fa64cfb53cff05 + languageName: node + linkType: hard + "mdn-data@npm:2.0.14": version: 2.0.14 resolution: "mdn-data@npm:2.0.14" @@ -20122,6 +22311,57 @@ __metadata: languageName: node linkType: hard +"micromark-core-commonmark@npm:^2.0.0": + version: 2.0.2 + resolution: "micromark-core-commonmark@npm:2.0.2" + dependencies: + decode-named-character-reference: "npm:^1.0.0" + devlop: "npm:^1.0.0" + micromark-factory-destination: "npm:^2.0.0" + micromark-factory-label: "npm:^2.0.0" + micromark-factory-space: "npm:^2.0.0" + micromark-factory-title: "npm:^2.0.0" + micromark-factory-whitespace: "npm:^2.0.0" + micromark-util-character: "npm:^2.0.0" + micromark-util-chunked: "npm:^2.0.0" + micromark-util-classify-character: "npm:^2.0.0" + micromark-util-html-tag-name: "npm:^2.0.0" + micromark-util-normalize-identifier: "npm:^2.0.0" + micromark-util-resolve-all: "npm:^2.0.0" + micromark-util-subtokenize: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 10/eafa6b9cd6fd9f51efa7795824af9a765e24a4519855a5b6dfcb0f619a93d90599d39a261f626bfcc1dfa64f22430f7a677a83cb6ce4bd8e4eeabc892610c016 + languageName: node + linkType: hard + +"micromark-extension-directive@npm:^3.0.0": + version: 3.0.2 + resolution: "micromark-extension-directive@npm:3.0.2" + dependencies: + devlop: "npm:^1.0.0" + micromark-factory-space: "npm:^2.0.0" + micromark-factory-whitespace: "npm:^2.0.0" + micromark-util-character: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + parse-entities: "npm:^4.0.0" + checksum: 10/63dbaa209722c1a77ffea6c6d5ea0f873f5e795ef08a2039f3d795320c889e5ce10fe1162500b0ff3063f8ceb1f7d727ec1d29d2df6271cbe90ec0646e061c8d + languageName: node + linkType: hard + +"micromark-extension-frontmatter@npm:^2.0.0": + version: 2.0.0 + resolution: "micromark-extension-frontmatter@npm:2.0.0" + dependencies: + fault: "npm:^2.0.0" + micromark-util-character: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 10/55873937494e9bfe1cc8cba3c8710e14e85ad0c9f3bb859d367268fc2204f3fe2eb70f9f83e496de0d3ea79c468fe6df879f9d475c716644c2daa90056cc8374 + languageName: node + linkType: hard + "micromark-extension-gfm-autolink-literal@npm:^1.0.0": version: 1.0.5 resolution: "micromark-extension-gfm-autolink-literal@npm:1.0.5" @@ -20173,6 +22413,20 @@ __metadata: languageName: node linkType: hard +"micromark-extension-gfm-strikethrough@npm:^2.0.0": + version: 2.1.0 + resolution: "micromark-extension-gfm-strikethrough@npm:2.1.0" + dependencies: + devlop: "npm:^1.0.0" + micromark-util-chunked: "npm:^2.0.0" + micromark-util-classify-character: "npm:^2.0.0" + micromark-util-resolve-all: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 10/eaf2c7b1e3eb2a7d7f405e8abe561be083cc52b8e027225ed286490939f527d18c120df59c8d8e17fdcf284f8d014502bf3db45d8e36e3109457ece8fb1db29b + languageName: node + linkType: hard + "micromark-extension-gfm-strikethrough@npm:~0.6.5": version: 0.6.5 resolution: "micromark-extension-gfm-strikethrough@npm:0.6.5" @@ -20195,6 +22449,19 @@ __metadata: languageName: node linkType: hard +"micromark-extension-gfm-table@npm:^2.0.0": + version: 2.1.0 + resolution: "micromark-extension-gfm-table@npm:2.1.0" + dependencies: + devlop: "npm:^1.0.0" + micromark-factory-space: "npm:^2.0.0" + micromark-util-character: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 10/37385c3b6e4833f9d9a277f98062af40ccf8c70e83726ab0c1ef9d6cb5784dd18489d1df62b241e8289349be11f5ab0ab3337043fe004bc9150f1067f9476c9b + languageName: node + linkType: hard + "micromark-extension-gfm-table@npm:~0.4.0": version: 0.4.3 resolution: "micromark-extension-gfm-table@npm:0.4.3" @@ -20233,6 +22500,19 @@ __metadata: languageName: node linkType: hard +"micromark-extension-gfm-task-list-item@npm:^2.0.1": + version: 2.1.0 + resolution: "micromark-extension-gfm-task-list-item@npm:2.1.0" + dependencies: + devlop: "npm:^1.0.0" + micromark-factory-space: "npm:^2.0.0" + micromark-util-character: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 10/c5f72929f0dca77df01442b721356624de6657364e2264ef50fc7226305976f302a49b670836f9494ce70a9b0335d974b5ef8e6457553c4c200bfc06d6951964 + languageName: node + linkType: hard + "micromark-extension-gfm-task-list-item@npm:~0.3.0": version: 0.3.3 resolution: "micromark-extension-gfm-task-list-item@npm:0.3.3" @@ -20272,6 +22552,83 @@ __metadata: languageName: node linkType: hard +"micromark-extension-mdx-expression@npm:^3.0.0": + version: 3.0.0 + resolution: "micromark-extension-mdx-expression@npm:3.0.0" + dependencies: + "@types/estree": "npm:^1.0.0" + devlop: "npm:^1.0.0" + micromark-factory-mdx-expression: "npm:^2.0.0" + micromark-factory-space: "npm:^2.0.0" + micromark-util-character: "npm:^2.0.0" + micromark-util-events-to-acorn: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 10/a5592160319d4617362f6b72a6fc44b5570466afa07419d44bcfdd9398a77a5693d7c5f8da7b3ff4682edf6209d4781835f5d2e3166fdf6bba37db456fd2d091 + languageName: node + linkType: hard + +"micromark-extension-mdx-jsx@npm:^3.0.0": + version: 3.0.1 + resolution: "micromark-extension-mdx-jsx@npm:3.0.1" + dependencies: + "@types/acorn": "npm:^4.0.0" + "@types/estree": "npm:^1.0.0" + devlop: "npm:^1.0.0" + estree-util-is-identifier-name: "npm:^3.0.0" + micromark-factory-mdx-expression: "npm:^2.0.0" + micromark-factory-space: "npm:^2.0.0" + micromark-util-character: "npm:^2.0.0" + micromark-util-events-to-acorn: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + vfile-message: "npm:^4.0.0" + checksum: 10/2cc0277d91c3c85d52e88755d17d021b5eab6fa03a578a9965f9d3d2c184dbc1accce63e7f8437a092ceeb602840ef451d4dce6dc9e8c13df0bc76e741080a89 + languageName: node + linkType: hard + +"micromark-extension-mdx-md@npm:^2.0.0": + version: 2.0.0 + resolution: "micromark-extension-mdx-md@npm:2.0.0" + dependencies: + micromark-util-types: "npm:^2.0.0" + checksum: 10/8b364a69b23196075258143c8c19fa58d7d5a91f6811ec0f881b75cf024a4869994be29f84f4d281147275c5a104af8b6a7fcd98abd8fde9f5b534a1acb254e8 + languageName: node + linkType: hard + +"micromark-extension-mdxjs-esm@npm:^3.0.0": + version: 3.0.0 + resolution: "micromark-extension-mdxjs-esm@npm:3.0.0" + dependencies: + "@types/estree": "npm:^1.0.0" + devlop: "npm:^1.0.0" + micromark-core-commonmark: "npm:^2.0.0" + micromark-util-character: "npm:^2.0.0" + micromark-util-events-to-acorn: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + unist-util-position-from-estree: "npm:^2.0.0" + vfile-message: "npm:^4.0.0" + checksum: 10/f2e0977f9a65284b0c765d1175d55ec5d1928dae3ae90f65cc36f293cda152a97fe2007977aaf5595b1bc02298b34c96e8ce8b647c9c647c75f1ea53e92d14d2 + languageName: node + linkType: hard + +"micromark-extension-mdxjs@npm:^3.0.0": + version: 3.0.0 + resolution: "micromark-extension-mdxjs@npm:3.0.0" + dependencies: + acorn: "npm:^8.0.0" + acorn-jsx: "npm:^5.0.0" + micromark-extension-mdx-expression: "npm:^3.0.0" + micromark-extension-mdx-jsx: "npm:^3.0.0" + micromark-extension-mdx-md: "npm:^2.0.0" + micromark-extension-mdxjs-esm: "npm:^3.0.0" + micromark-util-combine-extensions: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 10/66e0df7b2db05b9c88796600e354e0753594f06760abfddcac706afcd5754586c9085adb89e15447ce1450e6a5f2fa66a75f6da394e0eceb919e9c364475593e + languageName: node + linkType: hard + "micromark-factory-destination@npm:^1.0.0": version: 1.1.0 resolution: "micromark-factory-destination@npm:1.1.0" @@ -20283,6 +22640,17 @@ __metadata: languageName: node linkType: hard +"micromark-factory-destination@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-factory-destination@npm:2.0.1" + dependencies: + micromark-util-character: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 10/9c4baa9ca2ed43c061bbf40ddd3d85154c2a0f1f485de9dea41d7dd2ad994ebb02034a003b2c1dbe228ba83a0576d591f0e90e0bf978713f84ee7d7f3aa98320 + languageName: node + linkType: hard + "micromark-factory-label@npm:^1.0.0": version: 1.1.0 resolution: "micromark-factory-label@npm:1.1.0" @@ -20295,6 +22663,35 @@ __metadata: languageName: node linkType: hard +"micromark-factory-label@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-factory-label@npm:2.0.1" + dependencies: + devlop: "npm:^1.0.0" + micromark-util-character: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 10/bd03f5a75f27cdbf03b894ddc5c4480fc0763061fecf9eb927d6429233c930394f223969a99472df142d570c831236134de3dc23245d23d9f046f9d0b623b5c2 + languageName: node + linkType: hard + +"micromark-factory-mdx-expression@npm:^2.0.0": + version: 2.0.2 + resolution: "micromark-factory-mdx-expression@npm:2.0.2" + dependencies: + "@types/estree": "npm:^1.0.0" + devlop: "npm:^1.0.0" + micromark-factory-space: "npm:^2.0.0" + micromark-util-character: "npm:^2.0.0" + micromark-util-events-to-acorn: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + unist-util-position-from-estree: "npm:^2.0.0" + vfile-message: "npm:^4.0.0" + checksum: 10/d5285fa98018f14a058c7cd4a961aacedd2d3c4f4fddd4c58c16f1e640d1284a8f581f4d00fa3e18c06ed302ce23bca23f6a01edd66064c23c9057e65385a62d + languageName: node + linkType: hard + "micromark-factory-space@npm:^1.0.0": version: 1.1.0 resolution: "micromark-factory-space@npm:1.1.0" @@ -20305,6 +22702,16 @@ __metadata: languageName: node linkType: hard +"micromark-factory-space@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-factory-space@npm:2.0.1" + dependencies: + micromark-util-character: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 10/1bd68a017c1a66f4787506660c1e1c5019169aac3b1cb075d49ac5e360e0b2065e984d4e1d6e9e52a9d44000f2fa1c98e66a743d7aae78b4b05616bf3242ed71 + languageName: node + linkType: hard + "micromark-factory-title@npm:^1.0.0": version: 1.1.0 resolution: "micromark-factory-title@npm:1.1.0" @@ -20317,6 +22724,18 @@ __metadata: languageName: node linkType: hard +"micromark-factory-title@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-factory-title@npm:2.0.1" + dependencies: + micromark-factory-space: "npm:^2.0.0" + micromark-util-character: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 10/b4d2e4850a8ba0dff25ce54e55a3eb0d43dda88a16293f53953153288f9d84bcdfa8ca4606b2cfbb4f132ea79587bbb478a73092a349f893f5264fbcdbce2ee1 + languageName: node + linkType: hard + "micromark-factory-whitespace@npm:^1.0.0": version: 1.1.0 resolution: "micromark-factory-whitespace@npm:1.1.0" @@ -20329,6 +22748,18 @@ __metadata: languageName: node linkType: hard +"micromark-factory-whitespace@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-factory-whitespace@npm:2.0.1" + dependencies: + micromark-factory-space: "npm:^2.0.0" + micromark-util-character: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 10/67b3944d012a42fee9e10e99178254a04d48af762b54c10a50fcab988688799993efb038daf9f5dbc04001a97b9c1b673fc6f00e6a56997877ab25449f0c8650 + languageName: node + linkType: hard + "micromark-util-character@npm:^1.0.0": version: 1.2.0 resolution: "micromark-util-character@npm:1.2.0" @@ -20339,7 +22770,7 @@ __metadata: languageName: node linkType: hard -"micromark-util-character@npm:^2.0.0": +"micromark-util-character@npm:^2.0.0, micromark-util-character@npm:^2.0.1": version: 2.1.1 resolution: "micromark-util-character@npm:2.1.1" dependencies: @@ -20358,6 +22789,15 @@ __metadata: languageName: node linkType: hard +"micromark-util-chunked@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-util-chunked@npm:2.0.1" + dependencies: + micromark-util-symbol: "npm:^2.0.0" + checksum: 10/f8cb2a67bcefe4bd2846d838c97b777101f0043b9f1de4f69baf3e26bb1f9885948444e3c3aec66db7595cad8173bd4567a000eb933576c233d54631f6323fe4 + languageName: node + linkType: hard + "micromark-util-classify-character@npm:^1.0.0": version: 1.1.0 resolution: "micromark-util-classify-character@npm:1.1.0" @@ -20369,6 +22809,17 @@ __metadata: languageName: node linkType: hard +"micromark-util-classify-character@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-util-classify-character@npm:2.0.1" + dependencies: + micromark-util-character: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 10/4d8bbe3a6dbf69ac0fc43516866b5bab019fe3f4568edc525d4feaaaf78423fa54e6b6732b5bccbeed924455279a3758ffc9556954aafb903982598a95a02704 + languageName: node + linkType: hard + "micromark-util-combine-extensions@npm:^1.0.0": version: 1.1.0 resolution: "micromark-util-combine-extensions@npm:1.1.0" @@ -20379,6 +22830,16 @@ __metadata: languageName: node linkType: hard +"micromark-util-combine-extensions@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-util-combine-extensions@npm:2.0.1" + dependencies: + micromark-util-chunked: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 10/5d22fb9ee37e8143adfe128a72b50fa09568c2cc553b3c76160486c96dbbb298c5802a177a10a215144a604b381796071b5d35be1f2c2b2ee17995eda92f0c8e + languageName: node + linkType: hard + "micromark-util-decode-numeric-character-reference@npm:^1.0.0": version: 1.1.0 resolution: "micromark-util-decode-numeric-character-reference@npm:1.1.0" @@ -20388,6 +22849,15 @@ __metadata: languageName: node linkType: hard +"micromark-util-decode-numeric-character-reference@npm:^2.0.0": + version: 2.0.2 + resolution: "micromark-util-decode-numeric-character-reference@npm:2.0.2" + dependencies: + micromark-util-symbol: "npm:^2.0.0" + checksum: 10/ee11c8bde51e250e302050474c4a2adca094bca05c69f6cdd241af12df285c48c88d19ee6e022b9728281c280be16328904adca994605680c43af56019f4b0b6 + languageName: node + linkType: hard + "micromark-util-decode-string@npm:^1.0.0": version: 1.1.0 resolution: "micromark-util-decode-string@npm:1.1.0" @@ -20400,6 +22870,18 @@ __metadata: languageName: node linkType: hard +"micromark-util-decode-string@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-util-decode-string@npm:2.0.1" + dependencies: + decode-named-character-reference: "npm:^1.0.0" + micromark-util-character: "npm:^2.0.0" + micromark-util-decode-numeric-character-reference: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + checksum: 10/2f517e4c613609445db4b9a17f8c77832f55fb341620a8fd598f083c1227027485d601c2021c2f8f9883210b8671e7b3990f0c6feeecd49a136475465808c380 + languageName: node + linkType: hard + "micromark-util-encode@npm:^1.0.0": version: 1.1.0 resolution: "micromark-util-encode@npm:1.1.0" @@ -20414,6 +22896,22 @@ __metadata: languageName: node linkType: hard +"micromark-util-events-to-acorn@npm:^2.0.0": + version: 2.0.2 + resolution: "micromark-util-events-to-acorn@npm:2.0.2" + dependencies: + "@types/acorn": "npm:^4.0.0" + "@types/estree": "npm:^1.0.0" + "@types/unist": "npm:^3.0.0" + devlop: "npm:^1.0.0" + estree-util-visit: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + vfile-message: "npm:^4.0.0" + checksum: 10/475367e716c4d24f2a57464a7f2c8aa507ae36c05b7767fd652895525f3f0a1179ea3219cabccc0f3038bb5e4f9cce5390d530dc56decaa5f1786bda42739810 + languageName: node + linkType: hard + "micromark-util-html-tag-name@npm:^1.0.0": version: 1.2.0 resolution: "micromark-util-html-tag-name@npm:1.2.0" @@ -20421,6 +22919,13 @@ __metadata: languageName: node linkType: hard +"micromark-util-html-tag-name@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-util-html-tag-name@npm:2.0.1" + checksum: 10/dea365f5ad28ad74ff29fcb581f7b74fc1f80271c5141b3b2bc91c454cbb6dfca753f28ae03730d657874fcbd89d0494d0e3965dfdca06d9855f467c576afa9d + languageName: node + linkType: hard + "micromark-util-normalize-identifier@npm:^1.0.0": version: 1.1.0 resolution: "micromark-util-normalize-identifier@npm:1.1.0" @@ -20430,6 +22935,15 @@ __metadata: languageName: node linkType: hard +"micromark-util-normalize-identifier@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-util-normalize-identifier@npm:2.0.1" + dependencies: + micromark-util-symbol: "npm:^2.0.0" + checksum: 10/1eb9a289d7da067323df9fdc78bfa90ca3207ad8fd893ca02f3133e973adcb3743b233393d23d95c84ccaf5d220ae7f5a28402a644f135dcd4b8cfa60a7b5f84 + languageName: node + linkType: hard + "micromark-util-resolve-all@npm:^1.0.0": version: 1.1.0 resolution: "micromark-util-resolve-all@npm:1.1.0" @@ -20439,6 +22953,15 @@ __metadata: languageName: node linkType: hard +"micromark-util-resolve-all@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-util-resolve-all@npm:2.0.1" + dependencies: + micromark-util-types: "npm:^2.0.0" + checksum: 10/9275f3ddb6c26f254dd2158e66215d050454b279707a7d9ce5a3cd0eba23201021cedcb78ae1a746c1b23227dcc418ee40dd074ade195359506797a5493550cc + languageName: node + linkType: hard + "micromark-util-sanitize-uri@npm:^1.0.0": version: 1.2.0 resolution: "micromark-util-sanitize-uri@npm:1.2.0" @@ -20473,6 +22996,18 @@ __metadata: languageName: node linkType: hard +"micromark-util-subtokenize@npm:^2.0.0": + version: 2.0.3 + resolution: "micromark-util-subtokenize@npm:2.0.3" + dependencies: + devlop: "npm:^1.0.0" + micromark-util-chunked: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 10/57b26f129f46424a4670bb47c50f13c7309bb1bc557c02150a788d1634337c1bb25a3523af3d6dffc29aaec873b3bd88fd931bfff34e64284e6436e23873ca22 + languageName: node + linkType: hard + "micromark-util-symbol@npm:^1.0.0": version: 1.1.0 resolution: "micromark-util-symbol@npm:1.1.0" @@ -20536,6 +23071,31 @@ __metadata: languageName: node linkType: hard +"micromark@npm:^4.0.0": + version: 4.0.1 + resolution: "micromark@npm:4.0.1" + dependencies: + "@types/debug": "npm:^4.0.0" + debug: "npm:^4.0.0" + decode-named-character-reference: "npm:^1.0.0" + devlop: "npm:^1.0.0" + micromark-core-commonmark: "npm:^2.0.0" + micromark-factory-space: "npm:^2.0.0" + micromark-util-character: "npm:^2.0.0" + micromark-util-chunked: "npm:^2.0.0" + micromark-util-combine-extensions: "npm:^2.0.0" + micromark-util-decode-numeric-character-reference: "npm:^2.0.0" + micromark-util-encode: "npm:^2.0.0" + micromark-util-normalize-identifier: "npm:^2.0.0" + micromark-util-resolve-all: "npm:^2.0.0" + micromark-util-sanitize-uri: "npm:^2.0.0" + micromark-util-subtokenize: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 10/b948b1b239e589826bdaf2835daa9e88873e23d4b9148cd22109a86d4af55b96345cf9fc9059b6b19ae828f64d55e66f376ca3aeb4af3d2b0241560125f5dae6 + languageName: node + linkType: hard + "micromatch@npm:^2.1.5": version: 2.3.11 resolution: "micromatch@npm:2.3.11" @@ -20595,7 +23155,7 @@ __metadata: languageName: node linkType: hard -"mime-db@npm:>= 1.43.0 < 2": +"mime-db@npm:>= 1.43.0 < 2, mime-db@npm:^1.52.0": version: 1.53.0 resolution: "mime-db@npm:1.53.0" checksum: 10/82409c568a20254cc67a763a25e581d2213e1ef5d070a0af805239634f8a655f5d8a15138200f5f81c5b06fc6623d27f6168c612d447642d59e37eb7f20f7412 @@ -21121,6 +23681,13 @@ __metadata: languageName: node linkType: hard +"next-tick@npm:^1.1.0": + version: 1.1.0 + resolution: "next-tick@npm:1.1.0" + checksum: 10/83b5cf36027a53ee6d8b7f9c0782f2ba87f4858d977342bfc3c20c21629290a2111f8374d13a81221179603ffc4364f38374b5655d17b6a8f8a8c77bdea4fe8b + languageName: node + linkType: hard + "nice-try@npm:^1.0.4": version: 1.0.5 resolution: "nice-try@npm:1.0.5" @@ -21795,6 +24362,20 @@ __metadata: languageName: node linkType: hard +"outvariant@npm:1.4.0": + version: 1.4.0 + resolution: "outvariant@npm:1.4.0" + checksum: 10/07b9bcb9b3a2ff1b3db02af6b07d70e663082b30ddc08ff475d7c85fc623fdcc4433a4ab5b88f6902b62dbb284eef1be386aa537e14cef0519fad887ec483054 + languageName: node + linkType: hard + +"outvariant@npm:^1.3.0, outvariant@npm:^1.4.0": + version: 1.4.3 + resolution: "outvariant@npm:1.4.3" + checksum: 10/3a7582745850cb344d49641867a4c080858c54f4091afd91b9c0765ba6e471c2bc841348f0fff344845ddd0a4db42fd5d68c6f7ebaf32d4b676a3a9987b2488a + languageName: node + linkType: hard + "p-cancelable@npm:^2.0.0": version: 2.1.1 resolution: "p-cancelable@npm:2.1.1" @@ -21942,6 +24523,22 @@ __metadata: languageName: node linkType: hard +"parse-entities@npm:^4.0.0": + version: 4.0.1 + resolution: "parse-entities@npm:4.0.1" + dependencies: + "@types/unist": "npm:^2.0.0" + character-entities: "npm:^2.0.0" + character-entities-legacy: "npm:^3.0.0" + character-reference-invalid: "npm:^2.0.0" + decode-named-character-reference: "npm:^1.0.0" + is-alphanumerical: "npm:^2.0.0" + is-decimal: "npm:^2.0.0" + is-hexadecimal: "npm:^2.0.0" + checksum: 10/71314312d2482422fcf0b6675e020643bab424b11f64c654b7843652cae03842a7802eda1fed194ec435debb5db47a33513eb6b1176888e9e998a0368f01f5c8 + languageName: node + linkType: hard + "parse-filepath@npm:^1.0.2": version: 1.0.2 resolution: "parse-filepath@npm:1.0.2" @@ -22578,7 +25175,7 @@ __metadata: languageName: node linkType: hard -"prismjs@npm:^1.29.0": +"prismjs@npm:^1.27.0, prismjs@npm:^1.29.0": version: 1.29.0 resolution: "prismjs@npm:1.29.0" checksum: 10/2080db382c2dde0cfc7693769e89b501ef1bfc8ff4f8d25c07fd4c37ca31bc443f6133d5b7c145a73309dc396e829ddb7cc18560026d862a887ae08864ef6b07 @@ -23431,6 +26028,15 @@ __metadata: languageName: node linkType: hard +"react-devtools-inline@npm:4.4.0": + version: 4.4.0 + resolution: "react-devtools-inline@npm:4.4.0" + dependencies: + es6-symbol: "npm:^3" + checksum: 10/316a03bd21eb34f511e74e4e969e6a7d75d299800a4158ff59081457f024dc3aebc0c471938fd7e1c4676823fdcb546ca0d7c2d7ab74e34a9c1bdac31f2284b2 + languageName: node + linkType: hard + "react-dom@npm:^18.0.0, react-dom@npm:~18.3.1": version: 18.3.1 resolution: "react-dom@npm:18.3.1" @@ -23443,6 +26049,17 @@ __metadata: languageName: node linkType: hard +"react-error-boundary@npm:^3.1.4": + version: 3.1.4 + resolution: "react-error-boundary@npm:3.1.4" + dependencies: + "@babel/runtime": "npm:^7.12.5" + peerDependencies: + react: ">=16.13.1" + checksum: 10/7418637bf352b88f35ff3798e6faa094ee046df9d422fc08f54c017892c3c0738dac661ba3d64d97209464e7a60e7fbbeffdbeaee5edc38f3aaf5f1f4a8bf610 + languageName: node + linkType: hard + "react-freeze@npm:^1.0.0": version: 1.0.4 resolution: "react-freeze@npm:1.0.4" @@ -23452,6 +26069,15 @@ __metadata: languageName: node linkType: hard +"react-hook-form@npm:^7.44.2": + version: 7.54.0 + resolution: "react-hook-form@npm:7.54.0" + peerDependencies: + react: ^16.8.0 || ^17 || ^18 || ^19 + checksum: 10/dd3f133d7635ee610e6c16630de757d1b534b397833f36e2d981a7a84334446d92ac4f17b683a11eeaf55064730a13d262c933c188390974e449256c9047d979 + languageName: node + linkType: hard + "react-hook-form@npm:^7.53.2": version: 7.53.2 resolution: "react-hook-form@npm:7.53.2" @@ -23475,7 +26101,7 @@ __metadata: languageName: node linkType: hard -"react-is@npm:^17.0.0": +"react-is@npm:^17.0.0, react-is@npm:^17.0.2": version: 17.0.2 resolution: "react-is@npm:17.0.2" checksum: 10/73b36281e58eeb27c9cc6031301b6ae19ecdc9f18ae2d518bdb39b0ac564e65c5779405d623f1df9abf378a13858b79442480244bd579968afc1faf9a2ce5e05 @@ -23876,6 +26502,41 @@ __metadata: languageName: node linkType: hard +"react-remove-scroll-bar@npm:^2.3.6": + version: 2.3.6 + resolution: "react-remove-scroll-bar@npm:2.3.6" + dependencies: + react-style-singleton: "npm:^2.2.1" + tslib: "npm:^2.0.0" + peerDependencies: + "@types/react": ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10/5ab8eda61d5b10825447d11e9c824486c929351a471457c22452caa19b6898e18c3af6a46c3fa68010c713baed1eb9956106d068b4a1058bdcf97a1a9bbed734 + languageName: node + linkType: hard + +"react-remove-scroll@npm:2.6.0": + version: 2.6.0 + resolution: "react-remove-scroll@npm:2.6.0" + dependencies: + react-remove-scroll-bar: "npm:^2.3.6" + react-style-singleton: "npm:^2.2.1" + tslib: "npm:^2.1.0" + use-callback-ref: "npm:^1.3.0" + use-sidecar: "npm:^1.1.2" + peerDependencies: + "@types/react": ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10/9fac79e1c2ed2c85729bfe82f61ef4ae5ce51f478736a13892a9a11e05cbd4e9599f9f0e012cb5fc0719e18dc1dd687ab61f516193228615df636db8b851245e + languageName: node + linkType: hard + "react-shallow-renderer@npm:^16.15.0": version: 16.15.0 resolution: "react-shallow-renderer@npm:16.15.0" @@ -23888,6 +26549,23 @@ __metadata: languageName: node linkType: hard +"react-style-singleton@npm:^2.2.1": + version: 2.2.1 + resolution: "react-style-singleton@npm:2.2.1" + dependencies: + get-nonce: "npm:^1.0.0" + invariant: "npm:^2.2.4" + tslib: "npm:^2.0.0" + peerDependencies: + "@types/react": ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10/80c58fd6aac3594e351e2e7b048d8a5b09508adb21031a38b3c40911fe58295572eddc640d4b20a7be364842c8ed1120fe30097e22ea055316b375b88d4ff02a + languageName: node + linkType: hard + "react-test-renderer@npm:^18.3.1": version: 18.3.1 resolution: "react-test-renderer@npm:18.3.1" @@ -25831,6 +28509,18 @@ __metadata: languageName: node linkType: hard +"static-browser-server@npm:1.0.3": + version: 1.0.3 + resolution: "static-browser-server@npm:1.0.3" + dependencies: + "@open-draft/deferred-promise": "npm:^2.1.0" + dotenv: "npm:^16.0.3" + mime-db: "npm:^1.52.0" + outvariant: "npm:^1.3.0" + checksum: 10/d047c6c8a667a054db23c3d9770cf5e9d558694f052be81886f606ccc1c6dab034eb1fc5b35d1e3bf23a8dc6ca6f9cd7e4349bfb7b5cad1c2af13058c32dea86 + languageName: node + linkType: hard + "static-extend@npm:^0.1.1": version: 0.1.2 resolution: "static-extend@npm:0.1.2" @@ -25883,6 +28573,13 @@ __metadata: languageName: node linkType: hard +"strict-event-emitter@npm:^0.4.3": + version: 0.4.6 + resolution: "strict-event-emitter@npm:0.4.6" + checksum: 10/abdbf59b6c45b599cc2f227fa473765d1510d155ebd22533e8ecb06110dfacb2ff07aece7fd528dde2b4f9e379d60f2687eee8af3fa2877c3ed88ee5b7ed2707 + languageName: node + linkType: hard + "strict-uri-encode@npm:^2.0.0": version: 2.0.0 resolution: "strict-uri-encode@npm:2.0.0" @@ -26123,6 +28820,13 @@ __metadata: languageName: node linkType: hard +"style-mod@npm:^4.0.0, style-mod@npm:^4.1.0": + version: 4.1.2 + resolution: "style-mod@npm:4.1.2" + checksum: 10/9da37909d6dbc3c043ab6d18da5d997073a4698c91e86058293252493eb18aca4e44e3fb18f32fcee26dcee8785f393c6c95f3c96cc722a0dd6b8de622b5b293 + languageName: node + linkType: hard + "style-to-object@npm:^0.3.0": version: 0.3.0 resolution: "style-to-object@npm:0.3.0" @@ -26827,7 +29531,7 @@ __metadata: languageName: node linkType: hard -"tslib@npm:2, tslib@npm:^2.0.1, tslib@npm:^2.0.3, tslib@npm:^2.1.0, tslib@npm:^2.3.0, tslib@npm:^2.4.0, tslib@npm:^2.5.0, tslib@npm:^2.5.2, tslib@npm:^2.6.2, tslib@npm:^2.6.3, tslib@npm:^2.8.0": +"tslib@npm:2, tslib@npm:^2.0.0, tslib@npm:^2.0.1, tslib@npm:^2.0.3, tslib@npm:^2.1.0, tslib@npm:^2.3.0, tslib@npm:^2.4.0, tslib@npm:^2.5.0, tslib@npm:^2.5.2, tslib@npm:^2.6.2, tslib@npm:^2.6.3, tslib@npm:^2.8.0": version: 2.8.1 resolution: "tslib@npm:2.8.1" checksum: 10/3e2e043d5c2316461cb54e5c7fe02c30ef6dccb3384717ca22ae5c6b5bc95232a6241df19c622d9c73b809bea33b187f6dbc73030963e29950c2141bc32a79f7 @@ -26873,6 +29577,15 @@ __metadata: languageName: node linkType: hard +"turndown@npm:^7.2.0": + version: 7.2.0 + resolution: "turndown@npm:7.2.0" + dependencies: + "@mixmark-io/domino": "npm:^2.2.0" + checksum: 10/ad373d9cff76e80196effe29c19c1a14fda428fa9f41ef7458daa9c0c8939f37bbc36d0a504940f95ba34a6db8fd4d9cafd1ae498f91d010805f8d540e9e71bb + languageName: node + linkType: hard + "tweetnacl@npm:^0.14.3, tweetnacl@npm:~0.14.0": version: 0.14.5 resolution: "tweetnacl@npm:0.14.5" @@ -26976,6 +29689,13 @@ __metadata: languageName: node linkType: hard +"type@npm:^2.7.2": + version: 2.7.3 + resolution: "type@npm:2.7.3" + checksum: 10/82e99e7795b3de3ecfe685680685e79a77aea515fad9f60b7c55fbf6d43a5c360b1e6e9443354ec8906b38cdf5325829c69f094cb7cd2a1238e85bef9026dc04 + languageName: node + linkType: hard + "typed-array-buffer@npm:^1.0.2": version: 1.0.2 resolution: "typed-array-buffer@npm:1.0.2" @@ -27282,6 +30002,15 @@ __metadata: languageName: node linkType: hard +"unidiff@npm:^1.0.2": + version: 1.0.4 + resolution: "unidiff@npm:1.0.4" + dependencies: + diff: "npm:^5.1.0" + checksum: 10/c0771d3107c79ef63446f34c9a3574df9889486fba4f9ce50c1beb51d441d1fbc42b84c9889af68a3806f60560633f1c26a81378b5469e00ec223c99fb238f27 + languageName: node + linkType: hard + "unified@npm:^10.0.0, unified@npm:^10.1.2": version: 10.1.2 resolution: "unified@npm:10.1.2" @@ -27389,6 +30118,15 @@ __metadata: languageName: node linkType: hard +"unist-util-position-from-estree@npm:^2.0.0": + version: 2.0.0 + resolution: "unist-util-position-from-estree@npm:2.0.0" + dependencies: + "@types/unist": "npm:^3.0.0" + checksum: 10/d3b3048a5727c2367f64ef6dcc5b20c4717215ef8b1372ff9a7c426297c5d1e5776409938acd01531213e2cd2543218d16e73f9f862f318e9496e2c73bb18354 + languageName: node + linkType: hard + "unist-util-position@npm:^3.0.0": version: 3.1.0 resolution: "unist-util-position@npm:3.1.0" @@ -27639,6 +30377,21 @@ __metadata: languageName: node linkType: hard +"use-callback-ref@npm:^1.3.0": + version: 1.3.2 + resolution: "use-callback-ref@npm:1.3.2" + dependencies: + tslib: "npm:^2.0.0" + peerDependencies: + "@types/react": ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10/3be76eae71b52ab233b4fde974eddeff72e67e6723100a0c0297df4b0d60daabedfa706ffb314d0a52645f2c1235e50fdbd53d99f374eb5df68c74d412e98a9b + languageName: node + linkType: hard + "use-debounce@npm:^10.0.4": version: 10.0.4 resolution: "use-debounce@npm:10.0.4" @@ -27657,6 +30410,22 @@ __metadata: languageName: node linkType: hard +"use-sidecar@npm:^1.1.2": + version: 1.1.2 + resolution: "use-sidecar@npm:1.1.2" + dependencies: + detect-node-es: "npm:^1.1.0" + tslib: "npm:^2.0.0" + peerDependencies: + "@types/react": ^16.9.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10/ec99e31aefeb880f6dc4d02cb19a01d123364954f857811470ece32872f70d6c3eadbe4d073770706a9b7db6136f2a9fbf1bb803e07fbb21e936a47479281690 + languageName: node + linkType: hard + "use-sync-external-store@npm:1.2.2, use-sync-external-store@npm:^1.2.0, use-sync-external-store@npm:^1.2.2": version: 1.2.2 resolution: "use-sync-external-store@npm:1.2.2" @@ -28019,6 +30788,13 @@ __metadata: languageName: node linkType: hard +"w3c-keyname@npm:^2.2.4": + version: 2.2.8 + resolution: "w3c-keyname@npm:2.2.8" + checksum: 10/95bafa4c04fa2f685a86ca1000069c1ec43ace1f8776c10f226a73296caeddd83f893db885c2c220ebeb6c52d424e3b54d7c0c1e963bbf204038ff1a944fbb07 + languageName: node + linkType: hard + "walker@npm:^1.0.7, walker@npm:^1.0.8": version: 1.0.8 resolution: "walker@npm:1.0.8" From b836dbbfca629f2cec22b8036cf03b42dc3f794b Mon Sep 17 00:00:00 2001 From: Joshua Tag Howard Date: Sun, 8 Dec 2024 14:51:26 +0000 Subject: [PATCH 10/22] More stuff --- package.json | 50 +- packages/common/package.json | 8 +- packages/mobile/package.json | 12 +- packages/portal/package.json | 32 +- packages/portal/src/config/marathon.tsx | 2 +- packages/portal/src/config/refine/data.ts | 32 +- packages/portal/src/config/refine/router.ts | 113 ++ packages/portal/src/documents/notification.ts | 16 +- packages/portal/src/documents/person.ts | 4 +- packages/portal/src/documents/pointEntry.ts | 4 +- packages/portal/src/documents/team.ts | 6 +- .../components/event/EventDeletePopup.tsx | 6 +- .../event/EventOccurrencePicker.tsx | 127 +- .../components/person/PersonDeletePopup.tsx | 6 +- .../point-entry/PointEntryDeletePopup.tsx | 6 +- .../components/team/TeamDeletePopup.tsx | 6 +- .../elements/forms/event/edit/EventEditor.tsx | 531 ++--- .../forms/event/edit/EventEditorGQL.ts | 17 +- .../forms/event/edit/useEventEditorForm.ts | 92 - .../forms/marathon/useMarathonEditorForm.ts | 4 +- .../manage/useNotificationManager.ts | 9 +- .../forms/person/edit/usePersonEditorForm.ts | 2 +- .../create/PointEntryPersonLookup.tsx | 4 +- .../forms/team/edit/useTeamEditorForm.ts | 2 +- .../elements/viewers/event/EventViewer.tsx | 252 +-- packages/portal/src/main.tsx | 3 +- packages/portal/src/routes/__root.tsx | 74 +- .../src/routes/events/$eventId/edit.tsx | 31 +- .../src/routes/events/$eventId/index.tsx | 10 +- packages/portal/src/routes/feed/index.tsx | 6 +- .../$marathonId/hours/$hourId/index.tsx | 8 +- .../src/routes/marathon/$marathonId/index.tsx | 2 +- .../notifications/$notificationId/index.tsx | 8 +- .../notifications/$notificationId/manage.tsx | 8 +- .../src/routes/people/$personId/edit.tsx | 8 +- .../src/routes/people/$personId/index.tsx | 8 +- .../teams/$teamId/_layout/fundraising.tsx | 2 +- .../portal/src/routes/teams/$teamId/edit.tsx | 8 +- packages/server/package.json | 22 +- packages/server/src/jobs/getBBNEvents.ts | 2 +- .../src/resolvers/ConfigurationResolver.ts | 2 +- .../server/src/resolvers/DeviceResolver.ts | 2 +- .../server/src/resolvers/EventResolver.ts | 7 +- .../server/src/resolvers/ImageResolver.ts | 8 +- .../src/resolvers/MarathonHourResolver.ts | 12 +- .../server/src/resolvers/MarathonResolver.ts | 6 +- .../src/resolvers/NotificationResolver.ts | 12 +- .../server/src/resolvers/PersonResolver.ts | 6 +- .../src/resolvers/PointEntryResolver.ts | 4 +- .../src/resolvers/PointOpportunityResolver.ts | 6 +- packages/server/src/resolvers/TeamResolver.ts | 6 +- schema.graphql | 68 +- yarn.lock | 1777 +++++++++++------ 53 files changed, 1930 insertions(+), 1529 deletions(-) create mode 100644 packages/portal/src/config/refine/router.ts delete mode 100644 packages/portal/src/elements/forms/event/edit/useEventEditorForm.ts diff --git a/package.json b/package.json index 82f1130c..f4c92710 100644 --- a/package.json +++ b/package.json @@ -63,50 +63,50 @@ "urijs": "npm:uri-js-replace" }, "devDependencies": { - "@eslint/compat": "^1.2.3", + "@eslint/compat": "^1.2.4", "@eslint/config-inspector": "^0.5.6", "@eslint/eslintrc": "^3.2.0", - "@eslint/js": "^9.15.0", + "@eslint/js": "^9.16.0", "@expo/ngrok": "^4.1.3", "@graphql-codegen/cli": "^5.0.3", "@graphql-codegen/client-preset": "patch:@graphql-codegen/client-preset@npm%3A4.4.0#~/.yarn/patches/@graphql-codegen-client-preset-npm-4.4.0-d441b92060.patch", - "@graphql-eslint/eslint-plugin": "^4.2.0", + "@graphql-eslint/eslint-plugin": "^4.3.0", "@parcel/watcher": "^2.5.0", "@types/eslint-config-prettier": "^6.11.3", "@types/eslint__eslintrc": "^2.1.2", "@types/eslint__js": "^8.42.3", - "@types/react": "~18.3.12", - "@types/react-dom": "~18.3.1", - "@typescript-eslint/utils": "^8.14.0", - "@vitest/coverage-v8": "^2.1.5", - "@vitest/eslint-plugin": "^1.1.10", - "@vitest/ui": "^2.1.5", + "@types/react": "~18.3.14", + "@types/react-dom": "~18.3.2", + "@typescript-eslint/utils": "^8.17.0", + "@vitest/coverage-v8": "^2.1.8", + "@vitest/eslint-plugin": "^1.1.14", + "@vitest/ui": "^2.1.8", "@yarnpkg/types": "^4.0.0", "babel-cli": "^6.26.0", "chokidar-cli": "^3.0.0", - "eslint": "9.14.0", + "eslint": "9.16.0", "eslint-config-prettier": "^9.1.0", - "eslint-plugin-n": "^17.13.2", + "eslint-plugin-n": "^17.14.0", "eslint-plugin-react": "^7.37.2", - "eslint-plugin-react-hooks": "^5.0.0", - "eslint-plugin-react-refresh": "^0.4.14", + "eslint-plugin-react-hooks": "^5.1.0", + "eslint-plugin-react-refresh": "^0.4.16", "eslint-plugin-simple-import-sort": "^12.1.1", - "eslint-plugin-unicorn": "^56.0.0", - "globals": "^15.12.0", + "eslint-plugin-unicorn": "^56.0.1", + "globals": "^15.13.0", "graphql": "^16.9.0", - "graphql-scalars": "^1.23.0", - "node-gyp": "^10.2.0", - "prettier": "^3.3.3", - "sort-package-json": "^2.10.1", + "graphql-scalars": "^1.24.0", + "node-gyp": "^11.0.0", + "prettier": "^3.4.2", + "sort-package-json": "^2.12.0", "ts-node": "^10.9.2", - "typedoc": "^0.26.11", - "typedoc-plugin-dt-links": "^1.1.0", - "typedoc-plugin-mdn-links": "^4.0.1", + "typedoc": "^0.27.3", + "typedoc-plugin-dt-links": "^1.1.2", + "typedoc-plugin-mdn-links": "^4.0.4", "typedoc-plugin-missing-exports": "^3.1.0", "typedoc-plugin-zod": "^1.3.0", - "typescript": "^5.6.3", - "typescript-eslint": "^8.14.0", - "vitest": "^2.1.5" + "typescript": "^5.7.2", + "typescript-eslint": "^8.17.0", + "vitest": "^2.1.8" }, "packageManager": "yarn@4.5.3+sha512.3003a14012e2987072d244c720506549c1aab73ee728208f1b2580a9fd67b92d61ba6b08fe93f6dce68fd771e3af1e59a0afa28dd242dd0940d73b95fedd4e90" } diff --git a/packages/common/package.json b/packages/common/package.json index ab7df2c1..d9166c27 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -60,7 +60,7 @@ "@graphql-typed-document-node/core": "^3.2.0", "class-validator": "^0.14.1", "graphql": "^16.9.0", - "graphql-scalars": "^1.23.0", + "graphql-scalars": "^1.24.0", "htmlparser2": "^9.1.0", "http-status-codes": "^2.3.0", "luxon": "^3.5.0", @@ -73,10 +73,10 @@ }, "devDependencies": { "@types/luxon": "^3.4.2", - "@types/react": "~18.3.12", + "@types/react": "~18.3.14", "@types/validator": "^13.12.2", - "typescript": "^5.6.3", - "vitest": "^2.1.5" + "typescript": "^5.7.2", + "vitest": "^2.1.8" }, "packageManager": "yarn@4.5.3+sha512.3003a14012e2987072d244c720506549c1aab73ee728208f1b2580a9fd67b92d61ba6b08fe93f6dce68fd771e3af1e59a0afa28dd242dd0940d73b95fedd4e90" } diff --git a/packages/mobile/package.json b/packages/mobile/package.json index 87f61455..8c74bd47 100644 --- a/packages/mobile/package.json +++ b/packages/mobile/package.json @@ -83,7 +83,7 @@ "expo-updates": "~0.26.7", "expo-web-browser": "~14.0.1", "graphql": "^16.9.0", - "graphql-scalars": "^1.23.0", + "graphql-scalars": "^1.24.0", "lodash": "^4.17.21", "luxon": "^3.5.0", "lz-string": "^1.5.0", @@ -111,7 +111,7 @@ "reflect-metadata": "^0.2.2", "type-graphql": "^2.0.0-rc.2", "typedi": "^0.10.0", - "typescript": "^5.6.3", + "typescript": "^5.7.2", "urql": "^4.2.1", "utility-types": "^3.11.0", "validator": "^13.12.0" @@ -130,18 +130,18 @@ "@babel/plugin-transform-runtime": "^7.25.9", "@babel/plugin-transform-shorthand-properties": "^7.25.9", "@babel/plugin-transform-template-literals": "^7.25.9", - "@faker-js/faker": "^9.2.0", + "@faker-js/faker": "^9.3.0", "@testing-library/react-native": "^12.8.1", "@types/babel__core": "^7.20.5", "@types/lodash": "^4.17.13", "@types/luxon": "^3.4.2", "@types/lz-string": "^1.5.0", "@types/markdown-it": "^14.1.2", - "@types/react": "~18.3.12", - "@types/react-dom": "~18.3.1", + "@types/react": "~18.3.14", + "@types/react-dom": "~18.3.2", "@types/react-native-rss-parser": "^1.4.3", "@types/react-native-web": "^0.19.0", - "@types/react-test-renderer": "^18.3.0", + "@types/react-test-renderer": "^18.3.1", "@types/uuid": "^10.0.0", "@types/xdate": "^0.8.35", "babel-plugin-module-resolver": "^5.0.2", diff --git a/packages/portal/package.json b/packages/portal/package.json index d872a99c..2e64e446 100644 --- a/packages/portal/package.json +++ b/packages/portal/package.json @@ -39,7 +39,7 @@ "preview": "vite preview" }, "dependencies": { - "@ant-design/icons": "^5.5.1", + "@ant-design/icons": "^5.5.2", "@casl/ability": "^6.7.2", "@mdxeditor/editor": "^3.20.0", "@refinedev/antd": "^5.44.0", @@ -47,27 +47,27 @@ "@refinedev/devtools": "^1.2.10", "@sentry/react": "^8.38.0", "@sentry/vite-plugin": "^2.22.6", - "@tanstack/react-form": "^0.36.0", - "@tanstack/react-router": "^1.81.14", + "@tanstack/react-form": "^0.39.0", + "@tanstack/react-router": "^1.87.0", "@ukdanceblue/common": "workspace:^", "@urql/core": "^5.0.8", "@urql/devtools": "^2.0.3", "@wysimark/react": "^3.0.20", - "antd": "^5.22.1", + "antd": "^5.22.3", "camelcase": "^8.0.0", "class-validator": "^0.14.1", "gql.tada": "^1.8.10", "graphql": "^16.9.0", - "graphql-scalars": "^1.23.0", + "graphql-scalars": "^1.24.0", "lodash.isequal": "^4.5.0", "luxon": "^3.5.0", "markdown-it": "^14.1.0", "normalize.css": "^8.0.1", "pluralize": "^8.0.0", - "rc-picker": "^4.8.2", + "rc-picker": "^4.8.3", "react": "18.3.1", "react-dom": "~18.3.1", - "react-hook-form": "^7.53.2", + "react-hook-form": "^7.54.0", "react-to-print": "^3.0.2", "reflect-metadata": "^0.2.2", "thumbhash": "^0.1.1", @@ -84,19 +84,19 @@ }, "devDependencies": { "@refinedev/cli": "^2.16.40", - "@tanstack/router-devtools": "^1.81.14", - "@tanstack/router-plugin": "^1.81.9", + "@tanstack/router-devtools": "^1.87.0", + "@tanstack/router-plugin": "^1.86.0", "@types/lodash.isequal": "^4.5.8", "@types/luxon": "^3.4.2", - "@types/markdown-it": "^14", + "@types/markdown-it": "^14.1.2", "@types/pluralize": "^0.0.33", - "@types/react": "~18.3.12", - "@types/react-dom": "~18.3.1", - "@vitejs/plugin-react-swc": "^3.7.1", - "cypress": "^13.15.2", + "@types/react": "~18.3.14", + "@types/react-dom": "~18.3.2", + "@vitejs/plugin-react-swc": "^3.7.2", + "cypress": "^13.16.1", "cypress-vite": "^1.5.0", - "typescript": "^5.6.3", - "vite": "^5.4.11" + "typescript": "^5.7.2", + "vite": "^6.0.3" }, "packageManager": "yarn@4.5.3+sha512.3003a14012e2987072d244c720506549c1aab73ee728208f1b2580a9fd67b92d61ba6b08fe93f6dce68fd771e3af1e59a0afa28dd242dd0940d73b95fedd4e90", "refine": { diff --git a/packages/portal/src/config/marathon.tsx b/packages/portal/src/config/marathon.tsx index 05bb5b2a..92ca83d1 100644 --- a/packages/portal/src/config/marathon.tsx +++ b/packages/portal/src/config/marathon.tsx @@ -33,7 +33,7 @@ const allMarathonsDocument = graphql(/* GraphQL */ ` const selectedMarathonDocument = graphql(/* GraphQL */ ` query SelectedMarathon($marathonId: GlobalId!) { - marathon(uuid: $marathonId) { + marathon(id: $marathonId) { id year startDate diff --git a/packages/portal/src/config/refine/data.ts b/packages/portal/src/config/refine/data.ts index 16268175..17445302 100644 --- a/packages/portal/src/config/refine/data.ts +++ b/packages/portal/src/config/refine/data.ts @@ -92,19 +92,35 @@ export const dataProvider: Required = { const { meta, id, resource } = params; const gqlOperation = meta?.gqlQuery ?? meta?.gqlMutation; - if (!gqlOperation) { - throw new Error("Operation is required."); - } - - const query = isMutation(gqlOperation) - ? gqlButDifferentName` - query Get${camelcase(singular(resource), { pascalCase: true })}($id: GlobalID!) { + let query; + if (gqlOperation) { + query = isMutation(gqlOperation) + ? gqlButDifferentName` + query Get${camelcase(singular(resource), { pascalCase: true })}($id: GlobalId!) { ${getOperationName(resource, "getOne")}(id: $id) { ${getOperationFields(gqlOperation)} } } ` - : gqlOperation; + : gqlOperation; + } else if (meta?.gqlFragment) { + const fragmentDefinition = (meta.gqlFragment as DocumentNode) + .definitions[0]; + if (fragmentDefinition?.kind === Kind.FRAGMENT_DEFINITION) { + query = gqlButDifferentName` + query Get${camelcase(singular(resource), { pascalCase: true })}($id: GlobalId!) { + ${getOperationName(resource, "getOne")}(id: $id) { + ...${fragmentDefinition.name.value} + } + } + ${meta.gqlFragment as DocumentNode} + `; + } + } + + if (!query) { + throw new Error("Operation is required."); + } const response = await urqlClient .query(query, { diff --git a/packages/portal/src/config/refine/router.ts b/packages/portal/src/config/refine/router.ts new file mode 100644 index 00000000..e6656639 --- /dev/null +++ b/packages/portal/src/config/refine/router.ts @@ -0,0 +1,113 @@ +import { + type Action, + type GoConfig, + type IResourceItem, + type ParseFunction, +} from "@refinedev/core"; +import { + Link, + useLocation, + useMatches, + useNavigate, + useRouter, +} from "@tanstack/react-router"; +import { useCallback, useEffect, useMemo } from "react"; + +import { findResourceAction } from "./resources"; + +function useRefineGoFunction() { + const navigate = useNavigate(); + const location = useLocation(); + + const go = useCallback( + ({ hash, options, query, to, type }: GoConfig) => { + navigate({ + to, + search: options?.keepQuery ? location.search : query, + hash: options?.keepHash ? location.hash : hash, + replace: type === "replace", + }).catch(console.error); + }, + [location.search, location.hash, navigate] + ); + + useEffect(() => { + console.log("location updated to", location.search, location.hash); + }, [location.search, location.hash]); + + useEffect(() => { + console.log("navigate updated"); + }, [navigate]); + + useEffect(() => { + console.log("go updated"); + }, [go]); + + return go; +} +function useRefineParseFunction(): ParseFunction { + const matches = useMatches(); + const location = useLocation(); + + const parsed = useMemo(() => { + const matchesByLength = matches.toSorted( + ({ fullPath: fullPathA }, { fullPath: fullPathB }) => + String(fullPathB).length - String(fullPathA).length + ); + const longestMatch = matchesByLength[0]; + + let action: Action | undefined; + let resource: IResourceItem | undefined; + let id: string | undefined; + if (longestMatch) { + const idParams = Object.keys(longestMatch.params as object).filter( + (key) => key.toLowerCase().endsWith("id") + ); + if (idParams.length === 1) { + id = (longestMatch.params as Record)[ + idParams[0]! + ]; + } + ({ action, resource } = findResourceAction( + String(longestMatch.fullPath) + )); + } + + return { + pathname: location.pathname, + params: longestMatch?.params, + id, + action, + resource, + }; + }, [location.pathname, matches]); + + useEffect(() => { + console.log("location updated to", location.pathname); + }, [location.pathname]); + + useEffect(() => { + console.log("matches updated"); + }, [matches]); + + return () => parsed; +} + +function useRefineBackFunction() { + const router = useRouter(); + + const back = useCallback(() => router.history.back(), [router.history.back]); + + useEffect(() => { + console.log("back updated"); + }, [back]); + + return back; +} + +export const refineRouterProvider = { + back: useRefineBackFunction, + Link, + go: useRefineGoFunction, + parse: useRefineParseFunction, +}; diff --git a/packages/portal/src/documents/notification.ts b/packages/portal/src/documents/notification.ts index f12170c7..1366dca7 100644 --- a/packages/portal/src/documents/notification.ts +++ b/packages/portal/src/documents/notification.ts @@ -19,30 +19,30 @@ export const createNotificationDocument = graphql(/* GraphQL */ ` `); export const cancelNotificationScheduleDocument = graphql(/* GraphQL */ ` - mutation CancelNotificationSchedule($uuid: GlobalId!) { - abortScheduledNotification(uuid: $uuid) { + mutation CancelNotificationSchedule($id: GlobalId!) { + abortScheduledNotification(id: $id) { id } } `); export const deleteNotificationDocument = graphql(/* GraphQL */ ` - mutation DeleteNotification($uuid: GlobalId!, $force: Boolean) { - deleteNotification(uuid: $uuid, force: $force) { + mutation DeleteNotification($id: GlobalId!, $force: Boolean) { + deleteNotification(id: $id, force: $force) { id } } `); export const sendNotificationDocument = graphql(/* GraphQL */ ` - mutation SendNotification($uuid: GlobalId!) { - sendNotification(uuid: $uuid) + mutation SendNotification($id: GlobalId!) { + sendNotification(id: $id) } `); export const scheduleNotificationDocument = graphql(/* GraphQL */ ` - mutation ScheduleNotification($uuid: GlobalId!, $sendAt: DateTimeISO!) { - scheduleNotification(uuid: $uuid, sendAt: $sendAt) { + mutation ScheduleNotification($id: GlobalId!, $sendAt: DateTimeISO!) { + scheduleNotification(id: $id, sendAt: $sendAt) { id } } diff --git a/packages/portal/src/documents/person.ts b/packages/portal/src/documents/person.ts index 61b7dbc7..3c37423c 100644 --- a/packages/portal/src/documents/person.ts +++ b/packages/portal/src/documents/person.ts @@ -30,8 +30,8 @@ export const PersonEditorFragment = graphql(/* GraphQL */ ` `); export const personEditorDocument = graphql(/* GraphQL */ ` - mutation PersonEditor($uuid: GlobalId!, $input: SetPersonInput!) { - setPerson(uuid: $uuid, input: $input) { + mutation PersonEditor($id: GlobalId!, $input: SetPersonInput!) { + setPerson(id: $id, input: $input) { id } } diff --git a/packages/portal/src/documents/pointEntry.ts b/packages/portal/src/documents/pointEntry.ts index 437ed0fc..205778dc 100644 --- a/packages/portal/src/documents/pointEntry.ts +++ b/packages/portal/src/documents/pointEntry.ts @@ -35,8 +35,8 @@ export const createPointEntryAndAssignDocument = graphql(/* GraphQL */ ` `); export const getPersonByUuidDocument = graphql(/* GraphQL */ ` - query GetPersonByUuid($uuid: GlobalId!) { - person(uuid: $uuid) { + query GetPersonByUuid($id: GlobalId!) { + person(id: $id) { id name linkblue diff --git a/packages/portal/src/documents/team.ts b/packages/portal/src/documents/team.ts index c79a42dd..4c609c8e 100644 --- a/packages/portal/src/documents/team.ts +++ b/packages/portal/src/documents/team.ts @@ -7,7 +7,7 @@ import { PointEntryCreatorFragment } from "./pointEntry"; export const teamPageDocument = graphql( /* GraphQL */ ` query ViewTeamPage($teamUuid: GlobalId!) { - team(uuid: $teamUuid) { + team(id: $teamUuid) { ...PointEntryCreatorFragment ...TeamViewerFragment pointEntries { @@ -41,8 +41,8 @@ export const TeamEditorFragment = graphql(/* GraphQL */ ` `); export const teamEditorDocument = graphql(/* GraphQL */ ` - mutation TeamEditor($uuid: GlobalId!, $input: SetTeamInput!) { - setTeam(uuid: $uuid, input: $input) { + mutation TeamEditor($id: GlobalId!, $input: SetTeamInput!) { + setTeam(id: $id, input: $input) { id } } diff --git a/packages/portal/src/elements/components/event/EventDeletePopup.tsx b/packages/portal/src/elements/components/event/EventDeletePopup.tsx index 1a200c99..5619be4c 100644 --- a/packages/portal/src/elements/components/event/EventDeletePopup.tsx +++ b/packages/portal/src/elements/components/event/EventDeletePopup.tsx @@ -6,8 +6,8 @@ import { useMutation } from "urql"; import { graphql } from "#graphql/index.js"; const deleteEventDocument = graphql(/* GraphQL */ ` - mutation DeleteEvent($uuid: GlobalId!) { - deleteEvent(uuid: $uuid) { + mutation DeleteEvent($id: GlobalId!) { + deleteEvent(id: $id) { id } } @@ -46,7 +46,7 @@ export const useEventDeletePopup = ({ title="Delete Event" open={open} onOk={() => - deleteEvent({ uuid }).then((value) => { + deleteEvent({ id: uuid }).then((value) => { if (value.data?.deleteEvent.id) { showInfoMessage({ message: "Event successfully deleted", diff --git a/packages/portal/src/elements/components/event/EventOccurrencePicker.tsx b/packages/portal/src/elements/components/event/EventOccurrencePicker.tsx index f5ffb84c..5386b52d 100644 --- a/packages/portal/src/elements/components/event/EventOccurrencePicker.tsx +++ b/packages/portal/src/elements/components/event/EventOccurrencePicker.tsx @@ -1,133 +1,12 @@ import { DeleteOutlined } from "@ant-design/icons"; -import { Button, Checkbox, Flex } from "antd"; -import type { DateTime } from "luxon"; -import { Interval } from "luxon"; -import { useEffect, useMemo, useRef, useState } from "react"; +import { Button, Flex } from "antd"; import { LuxonDatePicker } from "#elements/components/antLuxonComponents.js"; -export function EventOccurrencePicker< - XEventOccurrenceInput extends - | { uuid: string; interval: Interval; fullDay: boolean } - | { interval: Interval; fullDay: boolean }, ->({ - defaultOccurrence, - onChange, - onDelete, -}: { - defaultOccurrence: XEventOccurrenceInput; - onChange: (occurrence: XEventOccurrenceInput) => void; - onDelete?: () => void; -}) { - const [start, setStart] = useState( - defaultOccurrence.interval.start - ); - const [end, setEnd] = useState( - defaultOccurrence.interval.end - ); - const [fullDay, setFullDay] = useState(defaultOccurrence.fullDay); - - const oldDefaultOccurrence = useRef(defaultOccurrence); - const oldStart = useRef(start); - const oldEnd = useRef(end); - const oldFullDay = useRef(fullDay); - - useEffect(() => { - if (oldDefaultOccurrence.current !== defaultOccurrence) { - oldDefaultOccurrence.current = defaultOccurrence; - } - if (oldStart.current !== start) { - oldStart.current = start; - } - if (oldEnd.current !== end) { - oldEnd.current = end; - } - if (oldFullDay.current !== fullDay) { - oldFullDay.current = fullDay; - } - }, [defaultOccurrence, start, end, fullDay]); - - const uuid = useMemo(() => { - if ("uuid" in defaultOccurrence) { - return defaultOccurrence.uuid; - } - return undefined; - }, [defaultOccurrence]); - - useEffect(() => { - if ( - start && - end && - (!Interval.fromDateTimes(start, end).equals(defaultOccurrence.interval) || - fullDay !== defaultOccurrence.fullDay) - ) { - if (uuid) { - onChange({ - uuid, - interval: Interval.fromDateTimes(start, end), - fullDay, - } as XEventOccurrenceInput); - } else { - onChange({ - interval: Interval.fromDateTimes(start, end), - fullDay, - } as XEventOccurrenceInput); - } - } - }, [ - start, - end, - fullDay, - onChange, - uuid, - defaultOccurrence.fullDay, - defaultOccurrence.interval, - ]); - - useEffect(() => { - if (fullDay) { - if (start) { - setStart(start.startOf("day")); - } - if (end) { - setEnd(end.endOf("day")); - } - } - }, [fullDay, start, end]); - - const fullDayCheckbox = ( - { - setFullDay(e.target.checked); - }} - > - Full day - - ); - +export function EventOccurrencePicker({ onDelete }: { onDelete?: () => void }) { return ( - {fullDay ? ( - { - setStart(dates?.[0] ?? null); - setEnd(dates?.[1] ?? null); - }} - /> - ) : ( - { - setStart(dates?.[0] ?? null); - setEnd(dates?.[1] ?? null); - }} - showTime - format="YYYY-MM-DD HH:mm" - /> - )} - {fullDayCheckbox} + + // + // )} + // + // ( + // 0 ? "error" : ""} + // help={ + // field.state.meta.errors.length > 0 + // ? field.state.meta.errors[0] + // : undefined + // } + // > + // field.handleChange(text)} + // plugins={[headingsPlugin()]} + // onError={(error) => + // field.setErrorMap({ onChange: error.error }) + // } + // /> + // + // )} + // /> + // + // + // + // + // - return ( - -
{ - formApi.handleSubmit().catch((error: unknown) => { - if (error instanceof Error) { - void message.error(error.message); - } else { - void message.error("An unknown error occurred"); - } - }); - }} - labelCol={{ span: 8 }} - wrapperCol={{ span: 32 }} - > - (!value ? "Title is required" : undefined), - }} - children={(field) => ( - 0 ? "error" : ""} - help={ - field.state.meta.errors.length > 0 - ? field.state.meta.errors[0] - : undefined - } - > - 0 ? "error" : ""} - name={field.name} - value={field.state.value} - onBlur={field.handleBlur} - onChange={(e) => field.handleChange(e.target.value)} - /> - - )} - /> - {eventData.images.length > 0 && ( - - - {eventData.images.map((image) => { - const thumbHash = - image.thumbHash && - thumbHashToDataURL(base64StringToArray(image.thumbHash)); + // + // + // + // + // + // + // + // + // + // + // + // + // {(fields, { add, remove }) => ( + //
+ // {fields.map((field, i) => ( + // + // remove(i)} /> + // + // ))} + // + // + // + //
+ // )} + //
+ // {/* + // formProps.form?.setFieldValue("description", text)} + // plugins={[headingsPlugin(), quotePlugin(), listsPlugin()]} + // /> - return ( - - ) - } - width={image.width} - height={image.height} - alt={image.alt ?? undefined} - /> - ); - })} -
-
- )} - - (value?.length ?? 0) > 255 ? "Too long" : undefined, - }} - children={(field) => ( - 0 ? "error" : ""} - help={ - field.state.meta.errors.length > 0 - ? field.state.meta.errors[0] - : undefined - } - > - field.handleChange(e.target.value)} - /> - - )} - /> - ( - 0 ? "error" : ""} - help={ - field.state.meta.errors.length > 0 - ? field.state.meta.errors[0] - : undefined - } - > - field.handleChange(e.target.value)} - /> - - )} - /> + JSON.stringify(formProps.initialValues) - { - for (let i = 0; i < value.length; i++) { - const interval = value[i]?.interval; - if (interval && !interval.isValid) { - return `Occurrence interval ${i + 1} is invalid: ${ - interval.invalidExplanation - }`; - } - } - return undefined; - }, - }} - mode="array" - > - {(field) => ( - 0 ? "error" : ""} - help={ - field.state.meta.errors.length > 0 - ? field.state.meta.errors[0] - : undefined - } - > - - {field.state.value.length > 0 ? ( - field.state.value.map((occurrence, index) => ( - - { - field.state.value.splice(index, 1, value); - field.handleChange(field.state.value); - }} - /> - - )) - ) : ( - - )} - - - - )} - - ( - 0 ? "error" : ""} - help={ - field.state.meta.errors.length > 0 - ? field.state.meta.errors[0] - : undefined - } - > - field.handleChange(e.target.value)} - /> - - )} - /> - - - - -
+ // */} + // + // + // null ); } diff --git a/packages/portal/src/elements/forms/event/edit/EventEditorGQL.ts b/packages/portal/src/elements/forms/event/edit/EventEditorGQL.ts index 625536c0..a38d67de 100644 --- a/packages/portal/src/elements/forms/event/edit/EventEditorGQL.ts +++ b/packages/portal/src/elements/forms/event/edit/EventEditorGQL.ts @@ -25,10 +25,21 @@ export const EventEditorFragment = graphql(/* GraphQL */ ` } `); -export const eventEditorDocument = graphql( +export const eventEditorQueryDocument = graphql( /* GraphQL */ ` - mutation SaveEvent($uuid: GlobalId!, $input: SetEventInput!) { - setEvent(uuid: $uuid, input: $input) { + query GetEvent($id: GlobalId!) { + event(id: $id) { + ...EventEditorFragment + } + } + `, + [EventEditorFragment] +); + +export const eventEditorMutationDocument = graphql( + /* GraphQL */ ` + mutation SetEvent($id: GlobalId!, $input: SetEventInput!) { + setEvent(id: $id, input: $input) { ...EventEditorFragment } } diff --git a/packages/portal/src/elements/forms/event/edit/useEventEditorForm.ts b/packages/portal/src/elements/forms/event/edit/useEventEditorForm.ts deleted file mode 100644 index ab9775f0..00000000 --- a/packages/portal/src/elements/forms/event/edit/useEventEditorForm.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { useForm } from "@tanstack/react-form"; -import { intervalFromSomething } from "@ukdanceblue/common"; -import type { Interval } from "luxon"; -import type { UseQueryExecute } from "urql"; -import { useMutation } from "urql"; - -import type { FragmentOf, VariablesOf } from "#graphql/index.js"; -import { readFragment } from "#graphql/index.js"; -import { useQueryStatusWatcher } from "#hooks/useQueryStatusWatcher.js"; - -import { eventEditorDocument, EventEditorFragment } from "./EventEditorGQL.js"; - -export function useEventEditorForm( - eventFragment: FragmentOf | undefined, - refetchEvent: UseQueryExecute | undefined -) { - const eventData = readFragment(EventEditorFragment, eventFragment); - - // Form - const [{ fetching, error }, setEvent] = useMutation(eventEditorDocument); - useQueryStatusWatcher({ - error, - fetching, - loadingMessage: "Saving event...", - }); - - const Form = useForm< - Omit["input"], "occurrences"> & { - occurrences: (Omit< - VariablesOf["input"]["occurrences"][number], - "uuid" | "interval" - > & { - uuid?: string; - interval: Interval; - })[]; - } - >({ - defaultValues: { - title: eventData?.title ?? "", - // Logical OR is intentional, we we want to replace empty strings with nulls - - summary: eventData?.summary || null, - - location: eventData?.location || null, - - description: eventData?.description || null, - occurrences: - eventData?.occurrences.map((occurrence) => ({ - uuid: occurrence.id, - interval: intervalFromSomething(occurrence.interval), - fullDay: occurrence.fullDay, - })) ?? [], - }, - onSubmit: async ({ value: values }) => { - if (!eventData) { - return; - } - - await setEvent({ - uuid: eventData.id, - input: { - title: values.title, - summary: values.summary ?? eventData.summary ?? null, - location: values.location ?? eventData.location ?? null, - description: values.description ?? eventData.description ?? null, - occurrences: values.occurrences.map((occurrence) => { - let retVal: Parameters< - typeof setEvent - >[0]["input"]["occurrences"][number] = { - interval: { - start: occurrence.interval.start!.toISO(), - end: occurrence.interval.end!.toISO(), - }, - fullDay: occurrence.fullDay, - }; - if (occurrence.uuid) { - retVal = { - ...retVal, - uuid: occurrence.uuid, - }; - } - return retVal; - }), - }, - }); - - refetchEvent?.(); - }, - }); - - return { formApi: Form }; -} diff --git a/packages/portal/src/elements/forms/marathon/useMarathonEditorForm.ts b/packages/portal/src/elements/forms/marathon/useMarathonEditorForm.ts index e1a66da7..0fe8e56e 100644 --- a/packages/portal/src/elements/forms/marathon/useMarathonEditorForm.ts +++ b/packages/portal/src/elements/forms/marathon/useMarathonEditorForm.ts @@ -16,7 +16,7 @@ export function useMarathonCreatorForm({ marathonId }: { marathonId: string }) { $input: SetMarathonInput! $marathonId: GlobalId! ) { - setMarathon(input: $input, uuid: $marathonId) { + setMarathon(input: $input, id: $marathonId) { id } } @@ -34,7 +34,7 @@ export function useMarathonCreatorForm({ marathonId }: { marathonId: string }) { ] = useQuery({ query: graphql(/* GraphQL */ ` query GetMarathon($marathonId: GlobalId!) { - marathon(uuid: $marathonId) { + marathon(id: $marathonId) { year startDate endDate diff --git a/packages/portal/src/elements/forms/notification/manage/useNotificationManager.ts b/packages/portal/src/elements/forms/notification/manage/useNotificationManager.ts index d91805db..116ae3dc 100644 --- a/packages/portal/src/elements/forms/notification/manage/useNotificationManager.ts +++ b/packages/portal/src/elements/forms/notification/manage/useNotificationManager.ts @@ -50,17 +50,18 @@ export const useNotificationManagerForm = ({ return uuid ? { - sendNotification: () => sendNotification({ uuid }), + sendNotification: () => sendNotification({ id: uuid }), scheduleNotification: (sendAt: DateTime) => { const sendAtISO = sendAt.toISO(); if (!sendAtISO) { throw new Error("Invalid sendAt date"); } - return scheduleNotification({ uuid, sendAt: sendAtISO }); + return scheduleNotification({ id: uuid, sendAt: sendAtISO }); }, - cancelNotificationSchedule: () => cancelNotificationSchedule({ uuid }), + cancelNotificationSchedule: () => + cancelNotificationSchedule({ id: uuid }), deleteNotification: (force = false) => - deleteNotification({ uuid, force }), + deleteNotification({ id: uuid, force }), } : undefined; }; diff --git a/packages/portal/src/elements/forms/person/edit/usePersonEditorForm.ts b/packages/portal/src/elements/forms/person/edit/usePersonEditorForm.ts index 832485a0..6276b064 100644 --- a/packages/portal/src/elements/forms/person/edit/usePersonEditorForm.ts +++ b/packages/portal/src/elements/forms/person/edit/usePersonEditorForm.ts @@ -104,7 +104,7 @@ export function usePersonEditorForm( } const { data } = await setPerson({ - uuid: personData.id, + id: personData.id, input: { name: values.name || null, diff --git a/packages/portal/src/elements/forms/point-entry/create/PointEntryPersonLookup.tsx b/packages/portal/src/elements/forms/point-entry/create/PointEntryPersonLookup.tsx index 0b7adf09..0a20a546 100644 --- a/packages/portal/src/elements/forms/point-entry/create/PointEntryPersonLookup.tsx +++ b/packages/portal/src/elements/forms/point-entry/create/PointEntryPersonLookup.tsx @@ -43,7 +43,7 @@ export function PointEntryPersonLookup({ const [selectedPersonQuery, updateSelectedPerson] = useQuery({ query: getPersonByUuidDocument, pause: true, - variables: { uuid: personFromUuid ?? "" }, + variables: { id: personFromUuid ?? "" }, }); useQueryStatusWatcher({ fetching: selectedPersonQuery.fetching, @@ -53,7 +53,7 @@ export function PointEntryPersonLookup({ useEffect(() => { if (personFromUuid) { - updateSelectedPerson({ uuid: personFromUuid }); + updateSelectedPerson({ id: personFromUuid }); } }, [personFromUuid, updateSelectedPerson]); diff --git a/packages/portal/src/elements/forms/team/edit/useTeamEditorForm.ts b/packages/portal/src/elements/forms/team/edit/useTeamEditorForm.ts index d34bb8d8..ac3d1014 100644 --- a/packages/portal/src/elements/forms/team/edit/useTeamEditorForm.ts +++ b/packages/portal/src/elements/forms/team/edit/useTeamEditorForm.ts @@ -37,7 +37,7 @@ export function useTeamEditorForm( } const { data } = await setTeam({ - uuid: teamData.id, + id: teamData.id, input: { name: values.name ?? null, legacyStatus: values.legacyStatus ?? null, diff --git a/packages/portal/src/elements/viewers/event/EventViewer.tsx b/packages/portal/src/elements/viewers/event/EventViewer.tsx index 9bc85f2a..1b5a79dc 100644 --- a/packages/portal/src/elements/viewers/event/EventViewer.tsx +++ b/packages/portal/src/elements/viewers/event/EventViewer.tsx @@ -1,10 +1,21 @@ import { DeleteOutlined, EditOutlined } from "@ant-design/icons"; +import { Breadcrumb, Show } from "@refinedev/antd"; +import type { HttpError } from "@refinedev/core"; +import { useOne } from "@refinedev/core"; import { Link, useNavigate } from "@tanstack/react-router"; import { base64StringToArray, intervalFromSomething, } from "@ukdanceblue/common"; -import { Button, Descriptions, Flex, Image, List, Typography } from "antd"; +import { + Button, + Descriptions, + Empty, + Flex, + Image, + List, + Typography, +} from "antd"; import DescriptionsItem from "antd/es/descriptions/Item.js"; import type { Interval } from "luxon"; import { DateTime } from "luxon"; @@ -12,8 +23,8 @@ import Markdown from "markdown-it"; import { useMemo } from "react"; import { thumbHashToDataURL } from "thumbhash"; -import type { FragmentOf } from "#graphql/index.js"; -import { graphql, readFragment } from "#graphql/index.js"; +import type { FragmentOf, ResultOf } from "#graphql/index.js"; +import { graphql } from "#graphql/index.js"; import { useEventDeletePopup } from "../../components/event/EventDeletePopup"; @@ -45,12 +56,20 @@ export const EventViewerFragment = graphql(/* GraphQL */ ` const markdown = new Markdown({ linkify: true }); -export function EventViewer({ - eventFragment, -}: { - eventFragment?: FragmentOf | undefined; -}) { - const eventData = readFragment(EventViewerFragment, eventFragment); +export function EventViewer({ id }: { id: string }) { + const { data } = useOne< + FragmentOf, + HttpError, + ResultOf + >({ + resource: "event", + id, + meta: { + gqlFragment: EventViewerFragment, + }, + }); + + const eventData = data?.data; const occurrences = useMemo< { interval: Interval; fullDay: boolean }[] | undefined @@ -83,10 +102,19 @@ export function EventViewer({ return markdown.render(eventData.description); }, [eventData?.description]); - if (!eventData) { - return ( - <> + return ( + }> + + {eventData?.title} + + + - // - // )} - // - // ( - // 0 ? "error" : ""} - // help={ - // field.state.meta.errors.length > 0 - // ? field.state.meta.errors[0] - // : undefined - // } - // > - // field.handleChange(text)} - // plugins={[headingsPlugin()]} - // onError={(error) => - // field.setErrorMap({ onChange: error.error }) - // } - // /> - // - // )} - // /> - // - // - // - // - // - - // - //
- // - // - // - // - // - // - // - // - // - // - // {(fields, { add, remove }) => ( - //
- // {fields.map((field, i) => ( - // - // remove(i)} /> - // - // ))} - // - // - // - //
- // )} - //
- // {/* - // formProps.form?.setFieldValue("description", text)} - // plugins={[headingsPlugin(), quotePlugin(), listsPlugin()]} - // /> - - JSON.stringify(formProps.initialValues) - - // */} - //
- //
- // null + +
, + "occurrences" | "id" | "images" + > & { + occurrences: { + id?: string; + interval: Interval; + fullDay: boolean; + }[]; + } + >)} + layout="vertical" + onFinish={(data) => { + const occurrences: NonNullable< + Parameters[0] + >["occurrences"] = []; + for (const { interval, ...occurrence } of data.occurrences) { + if (!interval.isValid) { + throw new Error(`Invalid interval: ${interval.invalidReason}`); + } else if (!interval.start?.isValid) { + throw new Error( + `Invalid start: ${interval.start?.invalidReason}` + ); + } else if (!interval.end?.isValid) { + throw new Error(`Invalid end: ${interval.end?.invalidReason}`); + } else { + occurrences.push({ + ...occurrence, + interval: { + start: interval.start.toISO(), + end: interval.end.toISO(), + }, + }); + } + } + return onFinish({ + ...data, + occurrences, + description: data.description || undefined, + location: data.location || undefined, + summary: data.summary || undefined, + }); + }} + > + + + + + + + + + + + {(fields, { add, remove }) => ( +
+ {fields.map((field, i) => ( + + remove(i)} /> + + ))} + + + +
+ )} +
+ + {formProps.initialValues ? ( + + formProps.form?.setFieldValue("description", text) + } + plugins={[ + headingsPlugin(), + quotePlugin(), + listsPlugin(), + linkPlugin({ + validateUrl(url) { + try { + new URL(url); + return true; + } catch { + return false; + } + }, + }), + linkDialogPlugin(), + toolbarPlugin({ + toolbarContents: () => ( + <> + + + + + + + ), + }), + ]} + /> + ) : ( + + + + )} + + +
); } diff --git a/packages/portal/src/elements/forms/point-entry/create/PointEntryCreator.tsx b/packages/portal/src/elements/forms/point-entry/create/PointEntryCreator.tsx index e6f99be7..917e3f3c 100644 --- a/packages/portal/src/elements/forms/point-entry/create/PointEntryCreator.tsx +++ b/packages/portal/src/elements/forms/point-entry/create/PointEntryCreator.tsx @@ -194,7 +194,7 @@ export function PointEntryCreator({ {(personFromUuid) => ( field.handleChange(val.target.checked)} disabled={ !personFromUuid || diff --git a/packages/portal/src/elements/forms/point-entry/create/PointEntryPersonLookup.tsx b/packages/portal/src/elements/forms/point-entry/create/PointEntryPersonLookup.tsx index 0a20a546..219701d7 100644 --- a/packages/portal/src/elements/forms/point-entry/create/PointEntryPersonLookup.tsx +++ b/packages/portal/src/elements/forms/point-entry/create/PointEntryPersonLookup.tsx @@ -1,5 +1,8 @@ import { ClearOutlined, PlusOutlined, SearchOutlined } from "@ant-design/icons"; +import type { FormApi } from "@tanstack/react-form"; +import { Field, useField } from "@tanstack/react-form"; import { AutoComplete, Button, Descriptions, Flex, Form, Input } from "antd"; +import type { VariablesOf } from "gql.tada"; import type { LegacyRef } from "react"; import { useEffect, useState } from "react"; import { useMutation, useQuery } from "urql"; @@ -11,13 +14,13 @@ import { } from "#hooks/useAntFeedback.js"; import { useQueryStatusWatcher } from "#hooks/useQueryStatusWatcher.js"; +import type { createPointEntryAndAssignDocument } from "../../../../documents/pointEntry.js"; import { createPersonByLinkBlue, getPersonByLinkBlueDocument, getPersonByUuidDocument, searchPersonByNameDocument, } from "../../../../documents/pointEntry.js"; -import type { usePointEntryCreatorForm } from "./usePointEntryCreatorForm.js"; const generalLinkblueRegex = new RegExp(/^[A-Za-z]{3,4}\d{3}$/); export function PointEntryPersonLookup({ @@ -27,7 +30,12 @@ export function PointEntryPersonLookup({ selectedPersonRef, clearButtonRef, }: { - formApi: ReturnType["formApi"]; + formApi: FormApi< + Omit< + VariablesOf["input"], + "teamUuid" + > & { shouldAddToTeam: boolean } + >; nameFieldRef: LegacyRef; linkblueFieldRef: Parameters[0]["ref"]; selectedPersonRef: LegacyRef; @@ -35,7 +43,8 @@ export function PointEntryPersonLookup({ }) { const { showErrorMessage } = useUnknownErrorHandler(); // Form state (shared with parent) - const { state, setValue: setPersonFromUuid } = formApi.useField({ + const { state, setValue: setPersonFromUuid } = useField({ + form: formApi, name: "personFromUuid", }); const personFromUuid = state.value; @@ -175,7 +184,8 @@ export function PointEntryPersonLookup({ }); return ( - ( <> diff --git a/packages/portal/src/elements/tables/ImagesTable.tsx b/packages/portal/src/elements/tables/ImagesTable.tsx index 3d6a51c5..a2dba77a 100644 --- a/packages/portal/src/elements/tables/ImagesTable.tsx +++ b/packages/portal/src/elements/tables/ImagesTable.tsx @@ -109,7 +109,7 @@ export const ImagesTable = ({ <> navigate({ from: "/images/$", params: { _splat: "" } })} + onCancel={() => navigate({ to: "/images" })} cancelButtonProps={{ style: { display: "none" } }} okButtonProps={{ style: { display: "none" } }} forceRender @@ -238,7 +238,10 @@ export const ImagesTable = ({ thumbHash ? ( - - { - setCreateImageOpen(false); - if (createdImageUuid) { - navigate({ - to: "/images/$", - params: { _splat: createdImageUuid }, - }).catch(console.error); - } - }} - /> - - - ); -} - -export const Route = createFileRoute("/images/$")({ - component: ListImagesPage, -}); diff --git a/packages/portal/src/routes/images/index.tsx b/packages/portal/src/routes/images/index.tsx index 940e4540..17353014 100644 --- a/packages/portal/src/routes/images/index.tsx +++ b/packages/portal/src/routes/images/index.tsx @@ -1,5 +1,9 @@ import { PlusOutlined } from "@ant-design/icons"; -import { createFileRoute, useNavigate } from "@tanstack/react-router"; +import { + createFileRoute, + useNavigate, + useSearch, +} from "@tanstack/react-router"; import { Button, Flex, Typography } from "antd"; import { useState } from "react"; @@ -8,12 +12,20 @@ import { ImagesTable } from "#elements/tables/ImagesTable.js"; export const Route = createFileRoute("/images/")({ component: RouteComponent, + validateSearch(search) { + return "previewedImage" in search && + typeof search.previewedImage === "string" + ? { imageId: search.previewedImage } + : {}; + }, }); function RouteComponent() { const [createImageOpen, setCreateImageOpen] = useState(false); const navigate = useNavigate(); + const { imageId } = useSearch({ from: "/images/" }); + return ( <> @@ -33,13 +45,13 @@ function RouteComponent() { setCreateImageOpen(false); if (createdImageUuid) { navigate({ - to: "/images/$", - params: { _splat: createdImageUuid }, + to: "/images", + search: { imageId: createdImageUuid }, }).catch(console.error); } }} /> - + ); } diff --git a/packages/server/src/repositories/event/EventRepository.ts b/packages/server/src/repositories/event/EventRepository.ts index 08b340c7..175bd9d1 100644 --- a/packages/server/src/repositories/event/EventRepository.ts +++ b/packages/server/src/repositories/event/EventRepository.ts @@ -1,5 +1,5 @@ import { Service } from "@freshgum/typedi"; -import { Event, Prisma, PrismaClient } from "@prisma/client"; +import { Event, EventOccurrence, Prisma, PrismaClient } from "@prisma/client"; import { SortDirection } from "@ukdanceblue/common"; import type { FilterItems } from "#lib/prisma-utils/gqlFilterToPrismaFilter.js"; @@ -62,6 +62,10 @@ import { Ok, Result } from "ts-results-es"; import { externalUrlToImage } from "#lib/external-apis/externalUrlToImage.js"; import { prismaToken } from "#lib/typediTokens.js"; +import { + handleRepositoryError, + RepositoryError, +} from "#repositories/shared.js"; export interface ForeignEvent { id: string; @@ -285,22 +289,81 @@ export class EventRepository { }); } - updateEvent(param: UniqueEventParam, data: Prisma.EventUpdateInput) { + async updateEvent( + param: UniqueEventParam, + data: { + title: string; + summary: string | null; + description: string | null; + location: string | null; + eventOccurrences: { + uuid?: string; + interval: { + start: Date | string; + end: Date | string; + }; + fullDay: boolean; + }[]; + } + ): Promise< + Result + > { try { - return this.prisma.event.update({ - where: param, - data, - include: { eventOccurrences: true }, - }); + return Ok( + await this.prisma.$transaction(async (prisma) => { + await prisma.eventOccurrence.deleteMany({ + where: { + event: param, + uuid: { + notIn: data.eventOccurrences + .filter((occurrence) => occurrence.uuid != null) + .map((occurrence) => occurrence.uuid!), + }, + }, + }); + return prisma.event.update({ + where: param, + include: { eventOccurrences: true }, + data: { + title: data.title, + summary: data.summary, + description: data.description, + location: data.location, + eventOccurrences: { + createMany: { + data: data.eventOccurrences + .filter((occurrence) => occurrence.uuid == null) + .map( + ( + occurrence + ): Prisma.EventOccurrenceCreateManyEventInput => ({ + date: occurrence.interval.start, + endDate: occurrence.interval.end, + fullDay: occurrence.fullDay, + }) + ), + }, + updateMany: data.eventOccurrences + .filter((occurrence) => occurrence.uuid != null) + .map( + ( + occurrence + ): Prisma.EventOccurrenceUpdateManyWithWhereWithoutEventInput => ({ + where: { uuid: occurrence.uuid }, + data: { + date: occurrence.interval.start, + endDate: occurrence.interval.end, + fullDay: occurrence.fullDay, + }, + }) + ), + }, + }, + }); + }) + ); } catch (error) { - if ( - error instanceof Prisma.PrismaClientKnownRequestError && - error.code === "P2025" - ) { - return null; - } else { - throw error; - } + return handleRepositoryError(error); } } diff --git a/packages/server/src/resolvers/EventResolver.ts b/packages/server/src/resolvers/EventResolver.ts index c9b94794..85901cab 100644 --- a/packages/server/src/resolvers/EventResolver.ts +++ b/packages/server/src/resolvers/EventResolver.ts @@ -16,6 +16,7 @@ import { ListEventsResponse, SetEventInput, } from "@ukdanceblue/common"; +import { ConcreteResult } from "@ukdanceblue/common/error"; import { VoidResolver } from "graphql-scalars"; import { Arg, @@ -161,7 +162,7 @@ export class EventResolver implements CrudResolver { async setEvent( @Arg("id", () => GlobalIdScalar) { id }: GlobalId, @Arg("input") input: SetEventInput - ): Promise { + ): Promise> { const row = await this.eventRepository.updateEvent( { uuid: id }, { @@ -169,54 +170,17 @@ export class EventResolver implements CrudResolver { summary: input.summary, description: input.description, location: input.location, - eventOccurrences: { - createMany: { - data: input.occurrences - .filter((occurrence) => occurrence.uuid == null) - .map( - (occurrence): Prisma.EventOccurrenceCreateManyEventInput => ({ - date: occurrence.interval.start, - endDate: occurrence.interval.end, - fullDay: occurrence.fullDay, - }) - ), - }, - updateMany: input.occurrences - .filter((occurrence) => occurrence.uuid != null) - .map( - ( - occurrence - ): Prisma.EventOccurrenceUpdateManyWithWhereWithoutEventInput => ({ - where: { uuid: occurrence.uuid!.id }, - data: { - date: occurrence.interval.start, - endDate: occurrence.interval.end, - fullDay: occurrence.fullDay, - }, - }) - ), - // TODO: test if this delete also deletes the occurrences we are creating - deleteMany: { - uuid: { - notIn: input.occurrences - .filter((occurrence) => occurrence.uuid != null) - .map((occurrence) => occurrence.uuid!.id), - }, - }, - }, + eventOccurrences: input.occurrences, } ); - if (row == null) { - throw new LegacyError(LegacyErrorCode.NotFound, "Event not found"); - } - - auditLogger.secure("Event updated", { event: row }); - - return eventModelToResource( - row, - row.eventOccurrences.map(eventOccurrenceModelToResource) - ); + return row.map((row) => { + auditLogger.secure("Event updated", { event: row }); + return eventModelToResource( + row, + row.eventOccurrences.map(eventOccurrenceModelToResource) + ); + }); } @AccessControlAuthorized("update", "EventNode") diff --git a/schema.graphql b/schema.graphql index 516e6d8f..d3be92c4 100644 --- a/schema.graphql +++ b/schema.graphql @@ -531,7 +531,7 @@ type EventNode implements Node { type EventOccurrenceNode { fullDay: Boolean! - id: ID! + id: GlobalId! interval: IntervalISO! } @@ -2479,12 +2479,12 @@ input SetEventInput { input SetEventOccurrenceInput { fullDay: Boolean! - interval: IntervalISOInput! """ - If updating an existing occurrence, the UUID of the occurrence to update + If updating an existing occurrence, the GlobalId of the occurrence to update """ - uuid: GlobalId + id: GlobalId + interval: IntervalISOInput! } input SetFeedInput { diff --git a/yarn.lock b/yarn.lock index 5a2aa9bb..841b69e5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10552,13 +10552,6 @@ __metadata: languageName: node linkType: hard -"@types/linkify-it@npm:^5": - version: 5.0.0 - resolution: "@types/linkify-it@npm:5.0.0" - checksum: 10/c3919044d4876f9d71d037e861745cd2485c95ac8c36a4fa67b132d4e60eb1d067e123cc7965c9cf5110eea351517d767f0d306af5e9147d6d0af87bc374ddcf - languageName: node - linkType: hard - "@types/lodash.isequal@npm:^4.5.8": version: 4.5.8 resolution: "@types/lodash.isequal@npm:4.5.8" @@ -10598,16 +10591,6 @@ __metadata: languageName: node linkType: hard -"@types/markdown-it@npm:^14.1.2": - version: 14.1.2 - resolution: "@types/markdown-it@npm:14.1.2" - dependencies: - "@types/linkify-it": "npm:^5" - "@types/mdurl": "npm:^2" - checksum: 10/ca2f239c8d59610b9f936fd40261a6ccf2fa1ae27a21816c031e5712542dcf9ee01e2fe29b31118df90716e11ade54e47d92a498e9b6488800e77ca8827255a2 - languageName: node - linkType: hard - "@types/mdast@npm:^3.0.0": version: 3.0.15 resolution: "@types/mdast@npm:3.0.15" @@ -10626,13 +10609,6 @@ __metadata: languageName: node linkType: hard -"@types/mdurl@npm:^2": - version: 2.0.0 - resolution: "@types/mdurl@npm:2.0.0" - checksum: 10/78746e96c655ceed63db06382da466fd52c7e9dc54d60b12973dfdd110cae06b9439c4b90e17bb8d4461109184b3ea9f3e9f96b3e4bf4aa9fe18b6ac35f283c8 - languageName: node - linkType: hard - "@types/mime@npm:^1": version: 1.3.5 resolution: "@types/mime@npm:1.3.5" @@ -11234,7 +11210,6 @@ __metadata: "@types/lodash": "npm:^4.17.13" "@types/luxon": "npm:^3.4.2" "@types/lz-string": "npm:^1.5.0" - "@types/markdown-it": "npm:^14.1.2" "@types/react": "npm:~18.3.14" "@types/react-dom": "npm:~18.3.2" "@types/react-native-rss-parser": "npm:^1.4.3" @@ -11382,7 +11357,6 @@ __metadata: "@tanstack/router-plugin": "npm:^1.86.0" "@types/lodash.isequal": "npm:^4.5.8" "@types/luxon": "npm:^3.4.2" - "@types/markdown-it": "npm:^14.1.2" "@types/pluralize": "npm:^0.0.33" "@types/react": "npm:~18.3.14" "@types/react-dom": "npm:~18.3.2" @@ -11401,13 +11375,13 @@ __metadata: graphql-scalars: "npm:^1.24.0" lodash.isequal: "npm:^4.5.0" luxon: "npm:^3.5.0" - markdown-it: "npm:^14.1.0" normalize.css: "npm:^8.0.1" pluralize: "npm:^8.0.0" rc-picker: "npm:^4.8.3" react: "npm:18.3.1" react-dom: "npm:~18.3.1" react-hook-form: "npm:^7.54.0" + react-markdown: "npm:^9.0.1" react-to-print: "npm:^3.0.2" reflect-metadata: "npm:^0.2.2" thumbhash: "npm:^0.1.1" @@ -11524,6 +11498,13 @@ __metadata: languageName: node linkType: hard +"@ungap/structured-clone@npm:^1.0.0": + version: 1.2.1 + resolution: "@ungap/structured-clone@npm:1.2.1" + checksum: 10/6770f71e8183311b2871601ddb02d62a26373be7cf2950cb546a345a2305c75b502e36ce80166120aa2f5f1ea1562141684651ebbfcc711c58acd32035d3e545 + languageName: node + linkType: hard + "@urql/core@npm:^5.0.0, @urql/core@npm:^5.0.6, @urql/core@npm:^5.0.8": version: 5.0.8 resolution: "@urql/core@npm:5.0.8" @@ -14491,6 +14472,13 @@ __metadata: languageName: node linkType: hard +"comma-separated-tokens@npm:^2.0.0": + version: 2.0.3 + resolution: "comma-separated-tokens@npm:2.0.3" + checksum: 10/e3bf9e0332a5c45f49b90e79bcdb4a7a85f28d6a6f0876a94f1bb9b2bfbdbbb9292aac50e1e742d8c0db1e62a0229a106f57917e2d067fca951d81737651700d + languageName: node + linkType: hard + "command-exists@npm:^1.2.4": version: 1.2.9 resolution: "command-exists@npm:1.2.9" @@ -18884,6 +18872,38 @@ __metadata: languageName: node linkType: hard +"hast-util-to-jsx-runtime@npm:^2.0.0": + version: 2.3.2 + resolution: "hast-util-to-jsx-runtime@npm:2.3.2" + dependencies: + "@types/estree": "npm:^1.0.0" + "@types/hast": "npm:^3.0.0" + "@types/unist": "npm:^3.0.0" + comma-separated-tokens: "npm:^2.0.0" + devlop: "npm:^1.0.0" + estree-util-is-identifier-name: "npm:^3.0.0" + hast-util-whitespace: "npm:^3.0.0" + mdast-util-mdx-expression: "npm:^2.0.0" + mdast-util-mdx-jsx: "npm:^3.0.0" + mdast-util-mdxjs-esm: "npm:^2.0.0" + property-information: "npm:^6.0.0" + space-separated-tokens: "npm:^2.0.0" + style-to-object: "npm:^1.0.0" + unist-util-position: "npm:^5.0.0" + vfile-message: "npm:^4.0.0" + checksum: 10/3d72f83e2d8c29adc6576d2c6b41479902fd51fac8cfb2b67c35fd68fcb9c25c274699442e4dee901a7ab926a0ff6851713ed5d92448ac09ae0f10daf293476c + languageName: node + linkType: hard + +"hast-util-whitespace@npm:^3.0.0": + version: 3.0.0 + resolution: "hast-util-whitespace@npm:3.0.0" + dependencies: + "@types/hast": "npm:^3.0.0" + checksum: 10/8c7e9eeb8131fc18702f3a42623eb6b0b09d470347aa8badacac70e6d91f79657ab8c6b57c4c6fee3658cff405fac30e816d1cdfb3ed1fbf6045d0a4555cf4d4 + languageName: node + linkType: hard + "header-case@npm:^2.0.4": version: 2.0.4 resolution: "header-case@npm:2.0.4" @@ -18984,6 +19004,13 @@ __metadata: languageName: node linkType: hard +"html-url-attributes@npm:^3.0.0": + version: 3.0.1 + resolution: "html-url-attributes@npm:3.0.1" + checksum: 10/494074c2f730c5c0e517aa1b10111fb36732534a2d2b70427582c4a615472b47da472cf3a17562cc653826d378d20960f2783e0400f4f7cf0c3c2d91c6188d13 + languageName: node + linkType: hard + "html2canvas@npm:^1.4.1": version: 1.4.1 resolution: "html2canvas@npm:1.4.1" @@ -19304,6 +19331,13 @@ __metadata: languageName: node linkType: hard +"inline-style-parser@npm:0.2.4": + version: 0.2.4 + resolution: "inline-style-parser@npm:0.2.4" + checksum: 10/80814479d1f3c9cbd102f9de4cd6558cf43cc2e48640e81c4371c3634f1e8b6dfeb2f21063cfa31d46cc83e834c20cd59ed9eeed9bfd45ef5bc02187ad941faf + languageName: node + linkType: hard + "inline-style-prefixer@npm:^6.0.1": version: 6.0.4 resolution: "inline-style-prefixer@npm:6.0.4" @@ -22121,6 +22155,23 @@ __metadata: languageName: node linkType: hard +"mdast-util-to-hast@npm:^13.0.0": + version: 13.2.0 + resolution: "mdast-util-to-hast@npm:13.2.0" + dependencies: + "@types/hast": "npm:^3.0.0" + "@types/mdast": "npm:^4.0.0" + "@ungap/structured-clone": "npm:^1.0.0" + devlop: "npm:^1.0.0" + micromark-util-sanitize-uri: "npm:^2.0.0" + trim-lines: "npm:^3.0.0" + unist-util-position: "npm:^5.0.0" + unist-util-visit: "npm:^5.0.0" + vfile: "npm:^6.0.0" + checksum: 10/b17ee338f843af31a1c7a2ebf0df6f0b41c9380b7119a63ab521d271df665456578e1234bb7617883e8d860fe878038dcf2b76ab2f21e0f7451215a096d26cce + languageName: node + linkType: hard + "mdast-util-to-markdown@npm:^0.6.0, mdast-util-to-markdown@npm:^0.6.1, mdast-util-to-markdown@npm:~0.6.0": version: 0.6.5 resolution: "mdast-util-to-markdown@npm:0.6.5" @@ -25611,6 +25662,13 @@ __metadata: languageName: node linkType: hard +"property-information@npm:^6.0.0": + version: 6.5.0 + resolution: "property-information@npm:6.5.0" + checksum: 10/fced94f3a09bf651ad1824d1bdc8980428e3e480e6d01e98df6babe2cc9d45a1c52eee9a7736d2006958f9b394eb5964dedd37e23038086ddc143fc2fd5e426c + languageName: node + linkType: hard + "proxy-addr@npm:~2.0.7": version: 2.0.7 resolution: "proxy-addr@npm:2.0.7" @@ -26508,6 +26566,27 @@ __metadata: languageName: node linkType: hard +"react-markdown@npm:^9.0.1": + version: 9.0.1 + resolution: "react-markdown@npm:9.0.1" + dependencies: + "@types/hast": "npm:^3.0.0" + devlop: "npm:^1.0.0" + hast-util-to-jsx-runtime: "npm:^2.0.0" + html-url-attributes: "npm:^3.0.0" + mdast-util-to-hast: "npm:^13.0.0" + remark-parse: "npm:^11.0.0" + remark-rehype: "npm:^11.0.0" + unified: "npm:^11.0.0" + unist-util-visit: "npm:^5.0.0" + vfile: "npm:^6.0.0" + peerDependencies: + "@types/react": ">=18" + react: ">=18" + checksum: 10/71ce31f200982f641d363888a26e8fb52a199a589124f20295e9be870fa3aed26fcfa14d1dc766d83df666a15cb82359291bfda207bd55d5728ff376d217e079 + languageName: node + linkType: hard + "react-native-bundle-visualizer@npm:^3.1.3": version: 3.1.3 resolution: "react-native-bundle-visualizer@npm:3.1.3" @@ -27331,6 +27410,18 @@ __metadata: languageName: node linkType: hard +"remark-parse@npm:^11.0.0": + version: 11.0.0 + resolution: "remark-parse@npm:11.0.0" + dependencies: + "@types/mdast": "npm:^4.0.0" + mdast-util-from-markdown: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + unified: "npm:^11.0.0" + checksum: 10/59d584be56ebc7c05524989c4ed86eb8a7b6e361942b705ca13a37349f60740a6073aedf7783af46ce920d09dd156148942d5e33e8be3dbcd47f818cb4bc410c + languageName: node + linkType: hard + "remark-parse@npm:^9.0.0": version: 9.0.0 resolution: "remark-parse@npm:9.0.0" @@ -27340,6 +27431,19 @@ __metadata: languageName: node linkType: hard +"remark-rehype@npm:^11.0.0": + version: 11.1.1 + resolution: "remark-rehype@npm:11.1.1" + dependencies: + "@types/hast": "npm:^3.0.0" + "@types/mdast": "npm:^4.0.0" + mdast-util-to-hast: "npm:^13.0.0" + unified: "npm:^11.0.0" + vfile: "npm:^6.0.0" + checksum: 10/39404bd19c57b2b69660be7e3d587ddb2240495845d42fad3bcc506c9c132d07abacb0a20182b73c530857b2da0c463ad5658382b448243ce432152ab49af08d + languageName: node + linkType: hard + "remark-rehype@npm:^8.0.0": version: 8.1.0 resolution: "remark-rehype@npm:8.1.0" @@ -28750,6 +28854,13 @@ __metadata: languageName: node linkType: hard +"space-separated-tokens@npm:^2.0.0": + version: 2.0.2 + resolution: "space-separated-tokens@npm:2.0.2" + checksum: 10/202e97d7ca1ba0758a0aa4fe226ff98142073bcceeff2da3aad037968878552c3bbce3b3231970025375bbba5aee00c5b8206eda408da837ab2dc9c0f26be990 + languageName: node + linkType: hard + "spdx-correct@npm:^3.0.0": version: 3.2.0 resolution: "spdx-correct@npm:3.2.0" @@ -29251,6 +29362,15 @@ __metadata: languageName: node linkType: hard +"style-to-object@npm:^1.0.0": + version: 1.0.8 + resolution: "style-to-object@npm:1.0.8" + dependencies: + inline-style-parser: "npm:0.2.4" + checksum: 10/530b067325e3119bfaf75bdbe25cc86b02b559db00d881a74b98a2d5bb10ac953d1b455ed90c825963cf3b4bdaa1bda45f406d78d987391434b8d8ab3835df4e + languageName: node + linkType: hard + "styleq@npm:^0.1.3": version: 0.1.3 resolution: "styleq@npm:0.1.3" @@ -29834,6 +29954,13 @@ __metadata: languageName: node linkType: hard +"trim-lines@npm:^3.0.0": + version: 3.0.1 + resolution: "trim-lines@npm:3.0.1" + checksum: 10/7a1325e4ce8ff7e9e52007600e9c9862a166d0db1f1cf0c9357e359e410acab1278fcd91cc279dfa5123fc37b69f080de02f471e91dbbc61b155b9ca92597929 + languageName: node + linkType: hard + "trim-right@npm:^1.0.1": version: 1.0.1 resolution: "trim-right@npm:1.0.1" @@ -30450,6 +30577,21 @@ __metadata: languageName: node linkType: hard +"unified@npm:^11.0.0": + version: 11.0.5 + resolution: "unified@npm:11.0.5" + dependencies: + "@types/unist": "npm:^3.0.0" + bail: "npm:^2.0.0" + devlop: "npm:^1.0.0" + extend: "npm:^3.0.0" + is-plain-obj: "npm:^4.0.0" + trough: "npm:^2.0.0" + vfile: "npm:^6.0.0" + checksum: 10/d9e6e88900a075f391b6bbf06f34062d41fa6257798110d1647753cfc2c6a6e2c1d016434e8ee35706c50485f9fb9ae4707a6a4790bd8dc461ec7e7315ed908b + languageName: node + linkType: hard + "unified@npm:^9.0.0": version: 9.2.2 resolution: "unified@npm:9.2.2" @@ -30576,6 +30718,15 @@ __metadata: languageName: node linkType: hard +"unist-util-position@npm:^5.0.0": + version: 5.0.0 + resolution: "unist-util-position@npm:5.0.0" + dependencies: + "@types/unist": "npm:^3.0.0" + checksum: 10/89d4da00e74618d7562ac7ac288961df9bcd4ccca6df3b5a90650f018eceb6b95de6e771e88bdbef46cc9d96861d456abe57b7ad1108921e0feb67c6292aa29d + languageName: node + linkType: hard + "unist-util-stringify-position@npm:^2.0.0": version: 2.0.3 resolution: "unist-util-stringify-position@npm:2.0.3" @@ -31096,6 +31247,16 @@ __metadata: languageName: node linkType: hard +"vfile@npm:^6.0.0": + version: 6.0.3 + resolution: "vfile@npm:6.0.3" + dependencies: + "@types/unist": "npm:^3.0.0" + vfile-message: "npm:^4.0.0" + checksum: 10/a5a85293c9eb8787aa42e180edaef00c13199a493d6ed82fecf13ab29a68526850788e22434d77808ea6b17a74e03ff899b9b4711df5b9eee75afcddd7c2e1fb + languageName: node + linkType: hard + "vite-node@npm:2.1.8": version: 2.1.8 resolution: "vite-node@npm:2.1.8" From 960584c50c0dff89900d2ab95f62bb5e66668bfb Mon Sep 17 00:00:00 2001 From: Joshua Tag Howard Date: Sun, 8 Dec 2024 19:50:12 +0000 Subject: [PATCH 12/22] Enhance access control and authorization logic; add 'readActive' permission for ConfigurationNode and refine subject handling --- eslint.config.js | 1 - .../lib/authorization/accessControl.test.ts | 321 +++++++++++++++--- .../common/lib/authorization/accessControl.ts | 72 ++-- .../src/resolvers/ConfigurationResolver.ts | 1 + 4 files changed, 320 insertions(+), 75 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index 6ca02ec3..d5ae4fa3 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -171,7 +171,6 @@ export default eslintTs.config( "unicorn/prefer-regexp-test": "error", "unicorn/prefer-set-has": "error", "unicorn/prefer-set-size": "error", - "unicorn/prefer-spread": "error", "unicorn/prefer-string-replace-all": "error", "unicorn/prefer-string-starts-ends-with": "error", "unicorn/prefer-string-trim-start-end": "error", diff --git a/packages/common/lib/authorization/accessControl.test.ts b/packages/common/lib/authorization/accessControl.test.ts index ded962dc..753afd38 100644 --- a/packages/common/lib/authorization/accessControl.test.ts +++ b/packages/common/lib/authorization/accessControl.test.ts @@ -1,13 +1,87 @@ import { randomUUID } from "node:crypto"; -import { describe } from "vitest"; +import { describe, expect } from "vitest"; import { MembershipPositionType } from "../api/resources/Membership.js"; import { TeamType } from "../api/resources/Team.js"; -import { type CaslParam, getAuthorizationFor } from "./accessControl.js"; +import type { Action, AppAbility, Subject } from "./accessControl.js"; +import { + type CaslParam, + getAuthorizationFor, + SubjectStrings, +} from "./accessControl.js"; import { AccessLevel } from "./structures.js"; -describe("Person authorization", (test) => { +describe("Unauthenticated user", (test) => { + const context: CaslParam = { + accessLevel: AccessLevel.None, + effectiveCommitteeRoles: [], + teamMemberships: [], + userId: randomUUID(), + }; + const ability = getAuthorizationFor(context); + + test("has no permissions for most resources", ({ expect }) => { + for (const resource of SubjectStrings) { + if (resource === "ConfigurationNode" || resource === "DeviceNode") { + continue; + } + + expect(ability).cannot("get", resource); + expect(ability).cannot("list", resource); + expect(ability).cannot("readActive", resource); + expect(ability).cannot("update", resource); + } + }); + + test("has permission to read active configuration", ({ expect }) => { + expect(ability).can("readActive", "ConfigurationNode"); + + expect(ability).cannot("get", "ConfigurationNode"); + expect(ability).cannot("list", "ConfigurationNode"); + expect(ability).cannot("update", "ConfigurationNode"); + }); + + test("has permission to read a device by uuid", ({ expect }) => { + expect(ability).can("get", "DeviceNode"); + + expect(ability).cannot("readActive", "DeviceNode"); + expect(ability).cannot("list", "DeviceNode"); + expect(ability).cannot("update", "DeviceNode"); + }); +}); + +describe("Super-admin authorization", (test) => { + const context: CaslParam = { + accessLevel: AccessLevel.SuperAdmin, + effectiveCommitteeRoles: [], + teamMemberships: [], + userId: randomUUID(), + }; + const ability = getAuthorizationFor(context); + + test("has all permissions", ({ expect }) => { + expect(ability).can("manage", "all"); + }); + + test("has all permissions individually", ({ expect }) => { + for (const resource of SubjectStrings) { + expect(ability).can("manage", resource, "."); + } + }); + + test("had no permission for non-existent fields", ({ expect }) => { + for (const resource of SubjectStrings) { + expect(ability).cannot( + "manage", + resource, + "~nonexistent~" as `.${string}` + ); + } + }); +}); + +describe("A normal user", (test) => { const userId = randomUUID(); const context: CaslParam = { accessLevel: AccessLevel.UKY, @@ -25,24 +99,59 @@ describe("Person authorization", (test) => { id: randomUUID(), } as const; - test("has correct permissions for self", ({ expect }) => { - expect(ability.can("read", self)).toBe(true); - expect(ability.can("update", self)).toBe(false); - expect(ability.can("read", other)).toBe(false); + test("can only read their own information", ({ expect }) => { + expect(ability).can("get", self); + expect(ability).cannot("list", "PersonNode"); + expect(ability).cannot("update", self); + expect(ability).cannot("get", other); }); - test("has correct permissions for own team memberships", ({ expect }) => { - expect(ability.can("read", self, ".memberships")).toBe(true); - expect(ability.can("update", self, ".memberships")).toBe(false); - expect(ability.can("read", other, ".memberships")).toBe(false); + test("can only read their own memberships", ({ expect }) => { + expect(ability).can("list", self, ".memberships"); + expect(ability).cannot("update", self, ".memberships"); + expect(ability).cannot("list", other, ".memberships"); }); - test("has correct permissions for own fundraising assignments", ({ - expect, - }) => { - expect(ability.can("read", self, ".fundraisingAssignments")).toBe(true); - expect(ability.can("update", self, ".fundraisingAssignments")).toBe(false); - expect(ability.can("read", other, ".fundraisingAssignments")).toBe(false); + test("can only read their own fundraising assignments", ({ expect }) => { + expect(ability).can("list", self, ".fundraisingAssignments"); + expect(ability).cannot("update", self, ".fundraisingAssignments"); + expect(ability).cannot("list", other, ".fundraisingAssignments"); + }); + + test("can only read the list of teams", ({ expect }) => { + expect(ability).can("list", "TeamNode"); + expect(ability).cannot("get", "TeamNode"); + expect(ability).cannot("update", "TeamNode"); + }); + + test("can read events", ({ expect }) => { + expect(ability).can("get", "EventNode"); + expect(ability).can("list", "EventNode"); + expect(ability).cannot("update", "EventNode"); + }); + + test("can read committees", ({ expect }) => { + expect(ability).can("get", "CommitteeNode"); + expect(ability).can("list", "CommitteeNode"); + expect(ability).cannot("update", "CommitteeNode"); + }); + + test("can read images", ({ expect }) => { + expect(ability).can("get", "ImageNode"); + expect(ability).can("list", "ImageNode"); + expect(ability).cannot("update", "ImageNode"); + }); + + test("can only read the active marathon", ({ expect }) => { + expect(ability).can("readActive", "MarathonNode"); + expect(ability).cannot("get", "MarathonNode"); + expect(ability).cannot("list", "MarathonNode"); + }); + + test("can only read the active marathon hour", ({ expect }) => { + expect(ability).can("readActive", "MarathonHourNode"); + expect(ability).cannot("get", "MarathonHourNode"); + expect(ability).cannot("list", "MarathonHourNode"); }); }); @@ -76,33 +185,33 @@ describe("Team authorization for team member", (test) => { const ability = getAuthorizationFor(context); test("has correct permissions for team", ({ expect }) => { - expect(ability.can("read", team1)).toBe(true); - expect(ability.can("read", team2)).toBe(true); - expect(ability.can("update", team1)).toBe(false); + expect(ability).can("get", team1); + expect(ability).can("list", team2); + expect(ability).cannot("update", team1); }); test("has correct permissions for team members", ({ expect }) => { - expect(ability.can("read", team1, ".members")).toBe(true); - expect(ability.can("update", team1, ".members")).toBe(false); - expect(ability.can("read", team2, ".members")).toBe(false); + expect(ability).can("list", team1, ".members"); + expect(ability).cannot("update", team1, ".members"); + expect(ability).cannot("list", team2, ".members"); }); test("has correct permissions for team fundraising assignments", ({ expect, }) => { - expect(ability.can("read", team1, ".fundraisingAssignments")).toBe(false); - expect(ability.can("read", team2, ".fundraisingAssignments")).toBe(false); + expect(ability).cannot("list", team1, ".fundraisingAssignments"); + expect(ability).cannot("list", team2, ".fundraisingAssignments"); }); test("has correct permissions for team solicitation code", ({ expect }) => { - expect(ability.can("read", team1, ".solicitationCode")).toBe(false); - expect(ability.can("read", team2, ".solicitationCode")).toBe(false); - expect(ability.can("update", team1, ".solicitationCode")).toBe(false); + expect(ability).cannot("get", team1, ".solicitationCode"); + expect(ability).cannot("get", team2, ".solicitationCode"); + expect(ability).cannot("update", team1, ".solicitationCode"); }); test("has correct permissions for team fundraising total", ({ expect }) => { - expect(ability.can("read", team1, ".fundraisingTotal")).toBe(true); - expect(ability.can("read", team2, ".fundraisingTotal")).toBe(false); + expect(ability).can("get", team1, ".fundraisingTotal"); + expect(ability).cannot("get", team2, ".fundraisingTotal"); }); }); @@ -128,36 +237,154 @@ describe("Team authorization for team captain", (test) => { const ability = getAuthorizationFor(context); test("has correct permissions for team", ({ expect }) => { - expect(ability.can("read", team1)).toBe(true); - expect(ability.can("read", team2)).toBe(true); - expect(ability.can("read", team3)).toBe(true); + expect(ability).can("get", team1); + expect(ability).can("get", team2); + expect(ability).cannot("get", team3); }); test("has correct permissions for team members", ({ expect }) => { - expect(ability.can("read", team1, ".members")).toBe(true); - expect(ability.can("update", team1, ".members")).toBe(false); - expect(ability.can("read", team2, ".members")).toBe(true); - expect(ability.can("read", team3, ".members")).toBe(false); + expect(ability).can("list", team1, ".members"); + expect(ability).cannot("update", team1, ".members"); + expect(ability).can("list", team2, ".members"); + expect(ability).cannot("list", team3, ".members"); }); test("has correct permissions for team fundraising assignments", ({ expect, }) => { - expect(ability.can("modify", team1, ".fundraisingAssignments")).toBe(true); - expect(ability.can("modify", team2, ".fundraisingAssignments")).toBe(false); - expect(ability.can("modify", team3, ".fundraisingAssignments")).toBe(false); + expect(ability).can("modify", team1, ".fundraisingAssignments"); + expect(ability).cannot("modify", team2, ".fundraisingAssignments"); + expect(ability).cannot("modify", team3, ".fundraisingAssignments"); }); test("has correct permissions for team solicitation code", ({ expect }) => { - expect(ability.can("read", team1, ".solicitationCode")).toBe(true); - expect(ability.can("update", team1, ".solicitationCode")).toBe(false); - expect(ability.can("read", team2, ".solicitationCode")).toBe(false); - expect(ability.can("read", team3, ".solicitationCode")).toBe(false); + expect(ability).can("get", team1, ".solicitationCode"); + expect(ability).cannot("update", team1, ".solicitationCode"); + expect(ability).cannot("get", team2, ".solicitationCode"); + expect(ability).cannot("get", team3, ".solicitationCode"); }); test("has correct permissions for team fundraising total", ({ expect }) => { - expect(ability.can("read", team1, ".fundraisingTotal")).toBe(true); - expect(ability.can("read", team2, ".fundraisingTotal")).toBe(true); - expect(ability.can("read", team3, ".fundraisingTotal")).toBe(false); + expect(ability).can("get", team1, ".fundraisingTotal"); + expect(ability).can("get", team2, ".fundraisingTotal"); + expect(ability).cannot("get", team3, ".fundraisingTotal"); }); }); + +describe("Admin authorization", (test) => { + const userId = randomUUID(); + const context: CaslParam = { + accessLevel: AccessLevel.Admin, + effectiveCommitteeRoles: [], + teamMemberships: [], + userId, + }; + const ability = getAuthorizationFor(context); + + test("has correct permissions for admin", ({ expect }) => { + expect(ability).can("manage", "ConfigurationNode", "."); + expect(ability).can("manage", "MarathonNode", "."); + expect(ability).can("manage", "MarathonHourNode", "."); + expect(ability).can("manage", "FundraisingAssignmentNode", "."); + expect(ability).can("manage", "FundraisingEntryNode", "."); + expect(ability).can("deploy", "NotificationNode", "."); + }); +}); + +describe("Committee chair or coordinator authorization", (test) => { + const userId = randomUUID(); + const context: CaslParam = { + accessLevel: AccessLevel.CommitteeChairOrCoordinator, + effectiveCommitteeRoles: [], + teamMemberships: [], + userId, + }; + const ability = getAuthorizationFor(context); + + test("has correct permissions for committee chair or coordinator", ({ + expect, + }) => { + expect(ability).can("manage", "CommitteeNode", "."); + expect(ability).can("manage", "EventNode", "."); + expect(ability).can("manage", "FeedNode", "."); + expect(ability).can("manage", "ImageNode", "."); + expect(ability).can("manage", "MembershipNode", "."); + expect(ability).can("manage", "PersonNode", "."); + expect(ability).can("manage", "PersonNode", ".memberships"); + expect(ability).can("read", "MarathonHourNode", "."); + expect(ability).can("read", "MarathonNode", "."); + expect(ability).can("modify", "NotificationNode", "."); + expect(ability).can("create", "NotificationNode", "."); + expect(ability).can("read", "NotificationNode", ".deliveryIssue"); + expect(ability).can( + "read", + "NotificationNode", + ".deliveryIssueAcknowledgedAt" + ); + expect(ability).can( + "read", + "NotificationDeliveryNode", + ".receiptCheckedAt" + ); + expect(ability).can("read", "NotificationDeliveryNode", ".chunkUuid"); + expect(ability).can("read", "NotificationDeliveryNode", ".deliveryError"); + }); +}); + +function canMessage( + not: boolean, + action: string, + subject: Subject, + field: string +) { + return `expected ${not ? "not " : ""}to be able to ${action} ${typeof subject === "string" ? subject : `${subject.kind}[id=${subject.id}]`}${field}`; +} + +expect.extend({ + can( + received: AppAbility, + ...[action, subject, field]: Parameters + ) { + const { isNot } = this; + + const result = received.can(action, subject, field ?? "."); + const message = () => canMessage(isNot, action, subject, field ?? "."); + return { + pass: result, + message, + }; + }, + cannot( + received: AppAbility, + ...[action, subject, field]: Parameters + ) { + const { isNot } = this; + + const result = received.cannot(action, subject, field ?? "."); + const message = () => canMessage(!isNot, action, subject, field ?? "."); + return { + pass: result, + message, + }; + }, +}); + +interface CustomMatchers { + can( + action: Omit, + subject: Subject, + field?: `.${string}` + ): R; + cannot( + action: Omit, + subject: Subject, + field?: `.${string}` + ): R; +} + +declare module "vitest" { + // eslint-disable-next-line @typescript-eslint/no-empty-object-type, @typescript-eslint/no-explicit-any + interface Assertion extends CustomMatchers {} + // eslint-disable-next-line @typescript-eslint/no-empty-object-type + interface AsymmetricMatchersContaining extends CustomMatchers {} +} diff --git a/packages/common/lib/authorization/accessControl.ts b/packages/common/lib/authorization/accessControl.ts index 3de31758..0fab719a 100644 --- a/packages/common/lib/authorization/accessControl.ts +++ b/packages/common/lib/authorization/accessControl.ts @@ -86,6 +86,13 @@ type ResourceSubject = { type SubjectValue = ResourceSubject[keyof ResourceSubject]; export type Subject = InferSubjects; +export const SubjectStrings = [ + ...(Object.keys( + extraFieldsByResource + ) as (keyof typeof extraFieldsByResource)[]), + "all", +] as const; + export type Action = | "create" | "get" @@ -100,7 +107,7 @@ export type Action = const resolveAction = createAliasResolver({ ["modify"]: ["read", "update", "delete"], - ["read"]: ["get", "list", "readActive"], + ["read"]: ["get", "list"], }); export type AppAbility = MongoAbility<[Action, Subject]>; @@ -142,25 +149,27 @@ export function getAuthorizationFor({ return build(caslOptions); } + allow("readActive", ["ConfigurationNode"], "."); + allow("get", ["DeviceNode"], "."); + if (accessLevel === AccessLevel.None) { return doBuild(); } if (accessLevel === AccessLevel.SuperAdmin) { - allow("manage", "all"); + allow( + "manage", + "all", + Object.values(extraFieldsByResource).reduce( + (acc, fields) => acc.concat(Object.keys(fields)), + ["."] + ) + ); return doBuild(); } - allow("get", "DeviceNode", "."); - allow( - "readActive", - ["FeedNode", "ConfigurationNode", "MarathonNode", "MarathonHourNode"], - "." - ); - allow("read", ["CommitteeNode", "EventNode", "ImageNode", "TeamNode"], "."); - - if (accessLevel >= AccessLevel.Committee) { - allow("read", ["SolicitationCodeNode"], "."); - } + allow("readActive", ["FeedNode", "MarathonNode", "MarathonHourNode"], "."); + allow("read", ["CommitteeNode", "EventNode", "ImageNode"], "."); + allow("list", ["TeamNode"], "."); if (accessLevel >= AccessLevel.CommitteeChairOrCoordinator) { allow( @@ -169,7 +178,7 @@ export function getAuthorizationFor({ "." ); allow("manage", "PersonNode", [".", ".memberships"]); - allow("read", "NotificationDeliveryNode", "."); + allow("read", ["MarathonHourNode", "MarathonNode"], "."); allow(["modify", "create"], "NotificationNode", "."); allow("read", "NotificationNode", [ ".", @@ -225,6 +234,12 @@ export function getAuthorizationFor({ allow("manage", "TeamNode", ".members"); break; } + case CommitteeIdentifier.programmingCommittee: { + if (role !== CommitteeRole.Member) { + allow("manage", "MarathonHourNode", "."); + } + break; + } case CommitteeIdentifier.fundraisingCommittee: { allow( "manage", @@ -253,16 +268,16 @@ export function getAuthorizationFor({ : null; if (parsedUserId) { - allow( - "read", - "PersonNode", - [".", ".memberships", ".fundraisingAssignments"], - { - id: { - $eq: parsedUserId, - }, - } - ); + allow("get", "PersonNode", ["."], { + id: { + $eq: parsedUserId, + }, + }); + allow("list", "PersonNode", [".memberships", ".fundraisingAssignments"], { + id: { + $eq: parsedUserId, + }, + }); } const authTeamMemberships = teamMemberships.map( @@ -270,7 +285,10 @@ export function getAuthorizationFor({ ); if (authTeamMemberships.length > 0) { - allow("read", "TeamNode", [".", ".members", ".fundraisingTotal"], { + allow("get", "TeamNode", [".", ".fundraisingTotal"], { + id: { $in: authTeamMemberships }, + }); + allow("list", "TeamNode", [".members"], { id: { $in: authTeamMemberships }, }); } @@ -281,10 +299,10 @@ export function getAuthorizationFor({ ) .map((membership) => membership.teamId); if (authTeamCaptaincies.length > 0) { - allow(["modify", "create"], "TeamNode", ".fundraisingAssignments", { + allow(["modify", "create"], "TeamNode", [".fundraisingAssignments"], { id: { $in: authTeamCaptaincies }, }); - allow("read", "TeamNode", ".solicitationCode", { + allow("get", "TeamNode", [".solicitationCode"], { id: { $in: authTeamCaptaincies }, }); } diff --git a/packages/server/src/resolvers/ConfigurationResolver.ts b/packages/server/src/resolvers/ConfigurationResolver.ts index 28070ddc..0f04a7f9 100644 --- a/packages/server/src/resolvers/ConfigurationResolver.ts +++ b/packages/server/src/resolvers/ConfigurationResolver.ts @@ -36,6 +36,7 @@ export class ConfigurationResolver private readonly logDir: string ) {} + @AccessControlAuthorized("readActive") @Query(() => GetConfigurationResponse, { name: "activeConfiguration", description: From 04a7bfeb5bd8d4111020754fe5476763a38e63fe Mon Sep 17 00:00:00 2001 From: Joshua Tag Howard Date: Sun, 8 Dec 2024 19:55:16 +0000 Subject: [PATCH 13/22] Add "What's New?" section to HomePage with updates on UI changes and authorization system --- packages/portal/src/routes/index.tsx | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/packages/portal/src/routes/index.tsx b/packages/portal/src/routes/index.tsx index 82e52f7a..516aad2b 100644 --- a/packages/portal/src/routes/index.tsx +++ b/packages/portal/src/routes/index.tsx @@ -47,6 +47,29 @@ function HomePage() { Welcome to the DanceBlue online portal! + What's New? + +
    +
  • This list!
  • +
  • + The portal has had few big changes to the user interface, namely the + menu moving to the left; but you will notice some changes to to + other pages as well. For now this mostly means the events pages, but + others will come soon. +
  • +
  • + The entire authorization system has been replaced, please contact + Tech Committee if you have any issues with "Access Denied" errors! + This change will make it a lot easier to fix these issues in the + future, but there may be some growing pains. +
  • +
  • + Any public events published to BBNvolved are now automatically + pulled into the app's database. +
  • +
  • More coming soon!
  • +
+
{data?.me && ( <> Your Information From 6fb0d3e592269435a7c71c43cafce1eafed0fe5a Mon Sep 17 00:00:00 2001 From: Joshua Tag Howard Date: Sun, 8 Dec 2024 20:22:28 +0000 Subject: [PATCH 14/22] Add React imports to multiple components and update tsconfig for JSX support --- .../CountdownView/CountdownViewNew.tsx | 2 +- .../common/components/CustomImageRenderer.tsx | 5 +- .../src/common/components/ErrorBoundary.tsx | 1 + .../src/common/components/Place/Place.tsx | 1 + .../common/components/Standings/Standings.tsx | 1 + packages/mobile/src/common/markdownRules.tsx | 8 +- .../src/navigation/NavigationContainer.tsx | 1 + .../navigation/root/Modals/SplashLogin.tsx | 1 + .../NotificationScreen/NotificationScreen.tsx | 1 + .../root/ProfileScreen/ProfileFooter.tsx | 1 + .../root/ProfileScreen/ProfileScreen.tsx | 3 +- .../mobile/src/navigation/root/RootScreen.tsx | 3 +- .../tab/EventListScreen/EventListPage.tsx | 1 + .../tab/EventListScreen/EventRow/EventRow.tsx | 1 + .../root/tab/ExplorerScreen/ExplorerItem.tsx | 1 + .../tab/ExplorerScreen/ExplorerScreen.tsx | 1 + .../root/tab/FundrasingScreen/HomeScreen.tsx | 1 + .../root/tab/HomeScreen/HomeScreen.tsx | 1 + .../root/tab/InfoScreen/InfoScreen.tsx | 1 + .../tab/MarathonScreen/MarathonScreen.tsx | 1 + .../navigation/root/tab/TabBarComponent.tsx | 1 + packages/portal/src/routeTree.gen.ts | 26 + .../portal/src/routes/images/$imageId.tsx | 46 ++ yarn.lock | 530 +----------------- 24 files changed, 114 insertions(+), 525 deletions(-) create mode 100644 packages/portal/src/routes/images/$imageId.tsx diff --git a/packages/mobile/src/common/components/CountdownView/CountdownViewNew.tsx b/packages/mobile/src/common/components/CountdownView/CountdownViewNew.tsx index 57ea9a26..b7b98bc4 100644 --- a/packages/mobile/src/common/components/CountdownView/CountdownViewNew.tsx +++ b/packages/mobile/src/common/components/CountdownView/CountdownViewNew.tsx @@ -1,6 +1,6 @@ import { DateTime, Duration, Interval } from "luxon"; import { View } from "native-base"; -import { useEffect, useState } from "react"; +import React, { useEffect, useState } from "react"; import { useThemeColors } from "../../customHooks"; import { CountdownNumber } from "./CountdownNumber"; diff --git a/packages/mobile/src/common/components/CustomImageRenderer.tsx b/packages/mobile/src/common/components/CustomImageRenderer.tsx index 939716ec..7f5c13dc 100644 --- a/packages/mobile/src/common/components/CustomImageRenderer.tsx +++ b/packages/mobile/src/common/components/CustomImageRenderer.tsx @@ -1,6 +1,7 @@ import type { ASTNode } from "@ukdanceblue/react-native-markdown-display"; import type { Key } from "react"; import { useEffect, useState } from "react"; +import type { ImageStyle, StyleProp } from "react-native"; import type { IFitImageProps } from "react-native-fit-image"; import FitImage from "react-native-fit-image"; @@ -54,14 +55,14 @@ export const CustomImageRenderer = ({ ); } setImageProps({ - style: styles._VIEW_SAFE_image, + style: styles._VIEW_SAFE_image as StyleProp, accessibilityLabel: alt ?? title, source: { uri: `${defaultImageHandler}${srcWithoutProtocol}` }, }); } } else { setImageProps({ - style: styles._VIEW_SAFE_image, + style: styles._VIEW_SAFE_image as StyleProp, accessibilityLabel: alt ?? title, source: { uri: src }, }); diff --git a/packages/mobile/src/common/components/ErrorBoundary.tsx b/packages/mobile/src/common/components/ErrorBoundary.tsx index d89ed75d..731451b5 100644 --- a/packages/mobile/src/common/components/ErrorBoundary.tsx +++ b/packages/mobile/src/common/components/ErrorBoundary.tsx @@ -6,6 +6,7 @@ import { import { debugStringify } from "@ukdanceblue/common"; import { openURL } from "expo-linking"; import type { ReactNode } from "react"; +import React from "react"; import { Button, SafeAreaView, ScrollView, Text, View } from "react-native"; import { universalCatch } from "../logging"; diff --git a/packages/mobile/src/common/components/Place/Place.tsx b/packages/mobile/src/common/components/Place/Place.tsx index ac47ed22..47b6b015 100644 --- a/packages/mobile/src/common/components/Place/Place.tsx +++ b/packages/mobile/src/common/components/Place/Place.tsx @@ -2,6 +2,7 @@ import { FontAwesome5 } from "@expo/vector-icons"; import { Divider, Heading, Text, View } from "native-base"; import type { ReactElement } from "react"; +import React from "react"; /** * A row-based component showing a target name, their rank (if applicable), and their points diff --git a/packages/mobile/src/common/components/Standings/Standings.tsx b/packages/mobile/src/common/components/Standings/Standings.tsx index 6a06c28f..f22d83c1 100644 --- a/packages/mobile/src/common/components/Standings/Standings.tsx +++ b/packages/mobile/src/common/components/Standings/Standings.tsx @@ -1,6 +1,7 @@ import { Text, View } from "native-base"; import type { ReactElement } from "react"; import { useEffect, useState } from "react"; +import React from "react"; import { ActivityIndicator, StyleSheet, TouchableOpacity } from "react-native"; import type { StandingType } from "../../../types/StandingType"; diff --git a/packages/mobile/src/common/markdownRules.tsx b/packages/mobile/src/common/markdownRules.tsx index 493a8af5..d9b1881a 100644 --- a/packages/mobile/src/common/markdownRules.tsx +++ b/packages/mobile/src/common/markdownRules.tsx @@ -5,7 +5,7 @@ import { } from "@ukdanceblue/react-native-markdown-display"; import { Platform } from "expo-modules-core"; import { Box, Divider, Heading, Link, Row, Text, VStack } from "native-base"; -import type { FlexAlignType, TextStyle } from "react-native"; +import type { DimensionValue, FlexAlignType, TextStyle } from "react-native"; import { StyleSheet } from "react-native"; import { CustomImageRenderer } from "./components/CustomImageRenderer"; @@ -44,7 +44,7 @@ export interface MarkdownRuleStyle { marginBottom: number; flexWrap: "wrap" | "nowrap" | "wrap-reverse"; alignItems: FlexAlignType; - width: string; + width: DimensionValue; } const markdownTextStyleKeys = new Set>([ @@ -265,9 +265,7 @@ export const rules: typeof renderRules = { ); const orderedList = parent[orderedListIndex]; - const listItemNumber = ( - orderedList.attributes - )?.start + const listItemNumber = orderedList.attributes?.start ? Number(orderedList.attributes.start) + (node.index ?? Number.NaN) : (node.index ?? Number.NaN) + 1; diff --git a/packages/mobile/src/navigation/NavigationContainer.tsx b/packages/mobile/src/navigation/NavigationContainer.tsx index f8ec8804..992a4b7c 100644 --- a/packages/mobile/src/navigation/NavigationContainer.tsx +++ b/packages/mobile/src/navigation/NavigationContainer.tsx @@ -10,6 +10,7 @@ import { import { addNotificationResponseReceivedListener } from "expo-notifications"; import { useDisclose } from "native-base"; import { useRef, useState } from "react"; +import React from "react"; import { StatusBar } from "react-native"; import type { WebViewSource } from "react-native-webview/lib/WebViewTypes"; diff --git a/packages/mobile/src/navigation/root/Modals/SplashLogin.tsx b/packages/mobile/src/navigation/root/Modals/SplashLogin.tsx index 79841ca7..1bb34fd7 100644 --- a/packages/mobile/src/navigation/root/Modals/SplashLogin.tsx +++ b/packages/mobile/src/navigation/root/Modals/SplashLogin.tsx @@ -1,6 +1,7 @@ import { AuthSource } from "@ukdanceblue/common"; import { Button, Center, Image, Text, View, ZStack } from "native-base"; import { useEffect, useState } from "react"; +import React from "react"; import type { ImageSourcePropType } from "react-native"; import { ActivityIndicator, Dimensions, StatusBar } from "react-native"; diff --git a/packages/mobile/src/navigation/root/NotificationScreen/NotificationScreen.tsx b/packages/mobile/src/navigation/root/NotificationScreen/NotificationScreen.tsx index 6aa78e62..65f969d1 100644 --- a/packages/mobile/src/navigation/root/NotificationScreen/NotificationScreen.tsx +++ b/packages/mobile/src/navigation/root/NotificationScreen/NotificationScreen.tsx @@ -5,6 +5,7 @@ import { setBadgeCountAsync } from "expo-notifications"; import { DateTime } from "luxon"; import { Button, SectionList, Text, useTheme, View } from "native-base"; import { useEffect, useMemo } from "react"; +import React from "react"; import { RefreshControl } from "react-native"; import JumbotronGeometric from "@/common/components/JumbotronGeometric"; diff --git a/packages/mobile/src/navigation/root/ProfileScreen/ProfileFooter.tsx b/packages/mobile/src/navigation/root/ProfileScreen/ProfileFooter.tsx index 091bdf67..b46fa6e5 100644 --- a/packages/mobile/src/navigation/root/ProfileScreen/ProfileFooter.tsx +++ b/packages/mobile/src/navigation/root/ProfileScreen/ProfileFooter.tsx @@ -12,6 +12,7 @@ import { View, } from "native-base"; import { useState } from "react"; +import React from "react"; import { TextInput } from "react-native"; import { useLogin, useLogOut } from "@/common/auth"; diff --git a/packages/mobile/src/navigation/root/ProfileScreen/ProfileScreen.tsx b/packages/mobile/src/navigation/root/ProfileScreen/ProfileScreen.tsx index fa2edbd5..25f9c767 100644 --- a/packages/mobile/src/navigation/root/ProfileScreen/ProfileScreen.tsx +++ b/packages/mobile/src/navigation/root/ProfileScreen/ProfileScreen.tsx @@ -17,13 +17,14 @@ import { VStack, } from "native-base"; import { useMemo } from "react"; +import React from "react"; import { useLogin } from "@/common/auth"; import JumbotronGeometric from "@/common/components/JumbotronGeometric"; import { useThemeFonts } from "@/common/customHooks"; import { universalCatch } from "@/common/logging"; import type { FragmentType } from "@/graphql/index"; -import { graphql,readFragment } from "@/graphql/index"; +import { graphql, readFragment } from "@/graphql/index"; import { ProfileFooter } from "./ProfileFooter"; diff --git a/packages/mobile/src/navigation/root/RootScreen.tsx b/packages/mobile/src/navigation/root/RootScreen.tsx index a08bf81e..1f2a523f 100644 --- a/packages/mobile/src/navigation/root/RootScreen.tsx +++ b/packages/mobile/src/navigation/root/RootScreen.tsx @@ -2,6 +2,7 @@ import { createNativeStackNavigator } from "@react-navigation/native-stack"; import { DbRole } from "@ukdanceblue/common"; import { useTheme } from "native-base"; import { useEffect, useMemo, useState } from "react"; +import React from "react"; import { Alert, useWindowDimensions } from "react-native"; import { useQuery } from "urql"; @@ -9,7 +10,7 @@ import ErrorBoundary, { withErrorBoundary, } from "@/common/components/ErrorBoundary"; import { Logger } from "@/common/logger/Logger"; -import { graphql,readFragment } from "@/graphql/index"; +import { graphql, readFragment } from "@/graphql/index"; import { useColorModeValue } from "../../common/customHooks"; import { useLoading } from "../../context"; diff --git a/packages/mobile/src/navigation/root/tab/EventListScreen/EventListPage.tsx b/packages/mobile/src/navigation/root/tab/EventListScreen/EventListPage.tsx index e0497687..aefd5642 100644 --- a/packages/mobile/src/navigation/root/tab/EventListScreen/EventListPage.tsx +++ b/packages/mobile/src/navigation/root/tab/EventListScreen/EventListPage.tsx @@ -1,6 +1,7 @@ import type { DateTime } from "luxon"; import { Column, Divider, Spinner, Text } from "native-base"; import { useEffect, useMemo, useRef, useState } from "react"; +import React from "react"; import { FlatList } from "react-native"; import { Calendar } from "react-native-calendars"; import type { DateData, MarkedDates } from "react-native-calendars/src/types"; diff --git a/packages/mobile/src/navigation/root/tab/EventListScreen/EventRow/EventRow.tsx b/packages/mobile/src/navigation/root/tab/EventListScreen/EventRow/EventRow.tsx index 35e91f4b..91f7d0ac 100644 --- a/packages/mobile/src/navigation/root/tab/EventListScreen/EventRow/EventRow.tsx +++ b/packages/mobile/src/navigation/root/tab/EventListScreen/EventRow/EventRow.tsx @@ -2,6 +2,7 @@ import { FontAwesome } from "@expo/vector-icons"; import type { Interval } from "luxon"; import { DateTime } from "luxon"; import { Heading, Icon, Row, Text } from "native-base"; +import React from "react"; import { useMemo } from "react"; /** diff --git a/packages/mobile/src/navigation/root/tab/ExplorerScreen/ExplorerItem.tsx b/packages/mobile/src/navigation/root/tab/ExplorerScreen/ExplorerItem.tsx index 3f67198d..91564f26 100644 --- a/packages/mobile/src/navigation/root/tab/ExplorerScreen/ExplorerItem.tsx +++ b/packages/mobile/src/navigation/root/tab/ExplorerScreen/ExplorerItem.tsx @@ -5,6 +5,7 @@ import { Image } from "expo-image"; import { openURL } from "expo-linking"; import { Box, Button, HStack, Text, View } from "native-base"; import { useEffect, useState } from "react"; +import React from "react"; import { PixelRatio, useWindowDimensions } from "react-native"; import AudioPlayer from "@/common/components/AudioPlayer"; diff --git a/packages/mobile/src/navigation/root/tab/ExplorerScreen/ExplorerScreen.tsx b/packages/mobile/src/navigation/root/tab/ExplorerScreen/ExplorerScreen.tsx index 8b3ce9b0..ece16ba6 100644 --- a/packages/mobile/src/navigation/root/tab/ExplorerScreen/ExplorerScreen.tsx +++ b/packages/mobile/src/navigation/root/tab/ExplorerScreen/ExplorerScreen.tsx @@ -1,5 +1,6 @@ import { Button, Text, VStack } from "native-base"; import { useEffect, useState } from "react"; +import React from "react"; import { RefreshControl, ScrollView } from "react-native-gesture-handler"; import type { FeedSortingItem } from "./combineFeeds"; diff --git a/packages/mobile/src/navigation/root/tab/FundrasingScreen/HomeScreen.tsx b/packages/mobile/src/navigation/root/tab/FundrasingScreen/HomeScreen.tsx index dbb9ef05..c2f501a4 100644 --- a/packages/mobile/src/navigation/root/tab/FundrasingScreen/HomeScreen.tsx +++ b/packages/mobile/src/navigation/root/tab/FundrasingScreen/HomeScreen.tsx @@ -2,6 +2,7 @@ import { FontAwesome, FontAwesome5 } from "@expo/vector-icons"; import { openURL } from "expo-linking"; import { openBrowserAsync } from "expo-web-browser"; import { Box, Button, HStack, Text, VStack } from "native-base"; +import React from "react"; import { PixelRatio, StatusBar, useWindowDimensions } from "react-native"; import { TouchableOpacity } from "react-native-gesture-handler"; diff --git a/packages/mobile/src/navigation/root/tab/HomeScreen/HomeScreen.tsx b/packages/mobile/src/navigation/root/tab/HomeScreen/HomeScreen.tsx index 3e0ac377..097d1a10 100644 --- a/packages/mobile/src/navigation/root/tab/HomeScreen/HomeScreen.tsx +++ b/packages/mobile/src/navigation/root/tab/HomeScreen/HomeScreen.tsx @@ -2,6 +2,7 @@ import { FontAwesome, FontAwesome5 } from "@expo/vector-icons"; import { openURL } from "expo-linking"; import { openBrowserAsync } from "expo-web-browser"; import { Box, Button, HStack, Text, VStack } from "native-base"; +import React from "react"; import { PixelRatio, StatusBar, diff --git a/packages/mobile/src/navigation/root/tab/InfoScreen/InfoScreen.tsx b/packages/mobile/src/navigation/root/tab/InfoScreen/InfoScreen.tsx index c75f2e5f..0b6436cd 100644 --- a/packages/mobile/src/navigation/root/tab/InfoScreen/InfoScreen.tsx +++ b/packages/mobile/src/navigation/root/tab/InfoScreen/InfoScreen.tsx @@ -1,6 +1,7 @@ import { FontAwesome, FontAwesome5 } from "@expo/vector-icons"; import { openURL } from "expo-linking"; import { Box, Button, HStack, Text, VStack } from "native-base"; +import React from "react"; import { PixelRatio, Share, diff --git a/packages/mobile/src/navigation/root/tab/MarathonScreen/MarathonScreen.tsx b/packages/mobile/src/navigation/root/tab/MarathonScreen/MarathonScreen.tsx index 232eb4b5..374368b8 100644 --- a/packages/mobile/src/navigation/root/tab/MarathonScreen/MarathonScreen.tsx +++ b/packages/mobile/src/navigation/root/tab/MarathonScreen/MarathonScreen.tsx @@ -1,6 +1,7 @@ import { dateTimeFromSomething } from "@ukdanceblue/common"; import { Button, Modal, Text, View } from "native-base"; import { useEffect, useMemo, useState } from "react"; +import React from "react"; import { ActivityIndicator, TextInput, diff --git a/packages/mobile/src/navigation/root/tab/TabBarComponent.tsx b/packages/mobile/src/navigation/root/tab/TabBarComponent.tsx index 5df13479..e86161ba 100644 --- a/packages/mobile/src/navigation/root/tab/TabBarComponent.tsx +++ b/packages/mobile/src/navigation/root/tab/TabBarComponent.tsx @@ -8,6 +8,7 @@ import type { TabNavigationState, } from "@react-navigation/native"; import { Box, useTheme, View, VStack, ZStack } from "native-base"; +import React from "react"; import { Text, TouchableOpacity, useWindowDimensions } from "react-native"; import { useColorModeValue, useThemeColors } from "@/common/customHooks"; diff --git a/packages/portal/src/routeTree.gen.ts b/packages/portal/src/routeTree.gen.ts index ae7a9799..b96832b4 100644 --- a/packages/portal/src/routeTree.gen.ts +++ b/packages/portal/src/routeTree.gen.ts @@ -29,6 +29,7 @@ import { Route as PeopleCreateImport } from "./routes/people/create"; import { Route as PeopleBulkImport } from "./routes/people/bulk"; import { Route as NotificationsCreateImport } from "./routes/notifications/create"; import { Route as MarathonCreateImport } from "./routes/marathon/create"; +import { Route as ImagesImageIdImport } from "./routes/images/$imageId"; import { Route as FundraisingDbfundsImport } from "./routes/fundraising/dbfunds"; import { Route as EventsCreateImport } from "./routes/events/create"; import { Route as AdminLogsImport } from "./routes/admin/logs"; @@ -163,6 +164,12 @@ const MarathonCreateRoute = MarathonCreateImport.update({ getParentRoute: () => rootRoute, } as any); +const ImagesImageIdRoute = ImagesImageIdImport.update({ + id: "/images/$imageId", + path: "/images/$imageId", + getParentRoute: () => rootRoute, +} as any); + const FundraisingDbfundsRoute = FundraisingDbfundsImport.update({ id: "/fundraising/dbfunds", path: "/fundraising/dbfunds", @@ -353,6 +360,13 @@ declare module "@tanstack/react-router" { preLoaderRoute: typeof FundraisingDbfundsImport; parentRoute: typeof rootRoute; }; + "/images/$imageId": { + id: "/images/$imageId"; + path: "/images/$imageId"; + fullPath: "/images/$imageId"; + preLoaderRoute: typeof ImagesImageIdImport; + parentRoute: typeof rootRoute; + }; "/marathon/create": { id: "/marathon/create"; path: "/marathon/create"; @@ -658,6 +672,7 @@ export interface FileRoutesByFullPath { "/admin/logs": typeof AdminLogsRoute; "/events/create": typeof EventsCreateRoute; "/fundraising/dbfunds": typeof FundraisingDbfundsRoute; + "/images/$imageId": typeof ImagesImageIdRoute; "/marathon/create": typeof MarathonCreateRoute; "/notifications/create": typeof NotificationsCreateRoute; "/people/bulk": typeof PeopleBulkRoute; @@ -702,6 +717,7 @@ export interface FileRoutesByTo { "/admin/logs": typeof AdminLogsRoute; "/events/create": typeof EventsCreateRoute; "/fundraising/dbfunds": typeof FundraisingDbfundsRoute; + "/images/$imageId": typeof ImagesImageIdRoute; "/marathon/create": typeof MarathonCreateRoute; "/notifications/create": typeof NotificationsCreateRoute; "/people/bulk": typeof PeopleBulkRoute; @@ -746,6 +762,7 @@ export interface FileRoutesById { "/admin/logs": typeof AdminLogsRoute; "/events/create": typeof EventsCreateRoute; "/fundraising/dbfunds": typeof FundraisingDbfundsRoute; + "/images/$imageId": typeof ImagesImageIdRoute; "/marathon/create": typeof MarathonCreateRoute; "/notifications/create": typeof NotificationsCreateRoute; "/people/bulk": typeof PeopleBulkRoute; @@ -793,6 +810,7 @@ export interface FileRouteTypes { | "/admin/logs" | "/events/create" | "/fundraising/dbfunds" + | "/images/$imageId" | "/marathon/create" | "/notifications/create" | "/people/bulk" @@ -836,6 +854,7 @@ export interface FileRouteTypes { | "/admin/logs" | "/events/create" | "/fundraising/dbfunds" + | "/images/$imageId" | "/marathon/create" | "/notifications/create" | "/people/bulk" @@ -878,6 +897,7 @@ export interface FileRouteTypes { | "/admin/logs" | "/events/create" | "/fundraising/dbfunds" + | "/images/$imageId" | "/marathon/create" | "/notifications/create" | "/people/bulk" @@ -924,6 +944,7 @@ export interface RootRouteChildren { AdminLogsRoute: typeof AdminLogsRoute; EventsCreateRoute: typeof EventsCreateRoute; FundraisingDbfundsRoute: typeof FundraisingDbfundsRoute; + ImagesImageIdRoute: typeof ImagesImageIdRoute; MarathonCreateRoute: typeof MarathonCreateRoute; NotificationsCreateRoute: typeof NotificationsCreateRoute; PeopleBulkRoute: typeof PeopleBulkRoute; @@ -964,6 +985,7 @@ const rootRouteChildren: RootRouteChildren = { AdminLogsRoute: AdminLogsRoute, EventsCreateRoute: EventsCreateRoute, FundraisingDbfundsRoute: FundraisingDbfundsRoute, + ImagesImageIdRoute: ImagesImageIdRoute, MarathonCreateRoute: MarathonCreateRoute, NotificationsCreateRoute: NotificationsCreateRoute, PeopleBulkRoute: PeopleBulkRoute, @@ -1017,6 +1039,7 @@ export const routeTree = rootRoute "/admin/logs", "/events/create", "/fundraising/dbfunds", + "/images/$imageId", "/marathon/create", "/notifications/create", "/people/bulk", @@ -1064,6 +1087,9 @@ export const routeTree = rootRoute "/fundraising/dbfunds": { "filePath": "fundraising/dbfunds.tsx" }, + "/images/$imageId": { + "filePath": "images/$imageId.tsx" + }, "/marathon/create": { "filePath": "marathon/create.tsx" }, diff --git a/packages/portal/src/routes/images/$imageId.tsx b/packages/portal/src/routes/images/$imageId.tsx new file mode 100644 index 00000000..b65a8dda --- /dev/null +++ b/packages/portal/src/routes/images/$imageId.tsx @@ -0,0 +1,46 @@ +import { PlusOutlined } from "@ant-design/icons"; +import { createFileRoute, useNavigate } from "@tanstack/react-router"; +import { Button, Flex, Typography } from "antd"; +import { useState } from "react"; + +import { CreateImagePopup } from "#elements/components/image/CreateImagePopup.js"; +import { ImagesTable } from "#elements/tables/ImagesTable.js"; + +function ListImagesPage() { + const [createImageOpen, setCreateImageOpen] = useState(false); + const { imageId } = Route.useParams(); + const navigate = useNavigate(); + + return ( + <> + + Images + + + { + setCreateImageOpen(false); + if (createdImageUuid) { + navigate({ + to: "/images/$imageId", + params: { imageId: createdImageUuid }, + }).catch(console.error); + } + }} + /> + + + ); +} + +export const Route = createFileRoute("/images/$imageId")({ + component: ListImagesPage, +}); diff --git a/yarn.lock b/yarn.lock index 841b69e5..9ed7def4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -108,23 +108,7 @@ __metadata: languageName: node linkType: hard -"@ant-design/icons@npm:^5.0.0, @ant-design/icons@npm:^5.5.1": - version: 5.5.1 - resolution: "@ant-design/icons@npm:5.5.1" - dependencies: - "@ant-design/colors": "npm:^7.0.0" - "@ant-design/icons-svg": "npm:^4.4.0" - "@babel/runtime": "npm:^7.24.8" - classnames: "npm:^2.2.6" - rc-util: "npm:^5.31.1" - peerDependencies: - react: ">=16.0.0" - react-dom: ">=16.0.0" - checksum: 10/8e5c75df40285af0b87fbb88adaa0c2f566c7fda3282233e802981034a99e968f03f39705ecc7870824d4b4b3ee5dc1b8e733a3073717441dc5c691e07c28536 - languageName: node - linkType: hard - -"@ant-design/icons@npm:^5.5.2": +"@ant-design/icons@npm:^5.0.0, @ant-design/icons@npm:^5.5.1, @ant-design/icons@npm:^5.5.2": version: 5.5.2 resolution: "@ant-design/icons@npm:5.5.2" dependencies: @@ -3165,14 +3149,7 @@ __metadata: languageName: node linkType: hard -"@eslint/object-schema@npm:^2.1.4": - version: 2.1.4 - resolution: "@eslint/object-schema@npm:2.1.4" - checksum: 10/221e8d9f281c605948cd6e030874aacce83fe097f8f9c1964787037bccf08e82b7aa9eff1850a30fffac43f1d76555727ec22a2af479d91e268e89d1e035131e - languageName: node - linkType: hard - -"@eslint/object-schema@npm:^2.1.5": +"@eslint/object-schema@npm:^2.1.4, @eslint/object-schema@npm:^2.1.5": version: 2.1.5 resolution: "@eslint/object-schema@npm:2.1.5" checksum: 10/bb07ec53357047f20de923bcd61f0306d9eee83ef41daa32e633e154a44796b5bd94670169eccb8fd8cb4ff42228a43b86953a6321f789f98194baba8207b640 @@ -8928,13 +8905,6 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-android-arm-eabi@npm:4.27.2": - version: 4.27.2 - resolution: "@rollup/rollup-android-arm-eabi@npm:4.27.2" - conditions: os=android & cpu=arm - languageName: node - linkType: hard - "@rollup/rollup-android-arm-eabi@npm:4.28.1": version: 4.28.1 resolution: "@rollup/rollup-android-arm-eabi@npm:4.28.1" @@ -8942,13 +8912,6 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-android-arm64@npm:4.27.2": - version: 4.27.2 - resolution: "@rollup/rollup-android-arm64@npm:4.27.2" - conditions: os=android & cpu=arm64 - languageName: node - linkType: hard - "@rollup/rollup-android-arm64@npm:4.28.1": version: 4.28.1 resolution: "@rollup/rollup-android-arm64@npm:4.28.1" @@ -8956,13 +8919,6 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-darwin-arm64@npm:4.27.2": - version: 4.27.2 - resolution: "@rollup/rollup-darwin-arm64@npm:4.27.2" - conditions: os=darwin & cpu=arm64 - languageName: node - linkType: hard - "@rollup/rollup-darwin-arm64@npm:4.28.1": version: 4.28.1 resolution: "@rollup/rollup-darwin-arm64@npm:4.28.1" @@ -8970,13 +8926,6 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-darwin-x64@npm:4.27.2": - version: 4.27.2 - resolution: "@rollup/rollup-darwin-x64@npm:4.27.2" - conditions: os=darwin & cpu=x64 - languageName: node - linkType: hard - "@rollup/rollup-darwin-x64@npm:4.28.1": version: 4.28.1 resolution: "@rollup/rollup-darwin-x64@npm:4.28.1" @@ -8984,13 +8933,6 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-freebsd-arm64@npm:4.27.2": - version: 4.27.2 - resolution: "@rollup/rollup-freebsd-arm64@npm:4.27.2" - conditions: os=freebsd & cpu=arm64 - languageName: node - linkType: hard - "@rollup/rollup-freebsd-arm64@npm:4.28.1": version: 4.28.1 resolution: "@rollup/rollup-freebsd-arm64@npm:4.28.1" @@ -8998,13 +8940,6 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-freebsd-x64@npm:4.27.2": - version: 4.27.2 - resolution: "@rollup/rollup-freebsd-x64@npm:4.27.2" - conditions: os=freebsd & cpu=x64 - languageName: node - linkType: hard - "@rollup/rollup-freebsd-x64@npm:4.28.1": version: 4.28.1 resolution: "@rollup/rollup-freebsd-x64@npm:4.28.1" @@ -9012,13 +8947,6 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-linux-arm-gnueabihf@npm:4.27.2": - version: 4.27.2 - resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.27.2" - conditions: os=linux & cpu=arm & libc=glibc - languageName: node - linkType: hard - "@rollup/rollup-linux-arm-gnueabihf@npm:4.28.1": version: 4.28.1 resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.28.1" @@ -9026,13 +8954,6 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-linux-arm-musleabihf@npm:4.27.2": - version: 4.27.2 - resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.27.2" - conditions: os=linux & cpu=arm & libc=musl - languageName: node - linkType: hard - "@rollup/rollup-linux-arm-musleabihf@npm:4.28.1": version: 4.28.1 resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.28.1" @@ -9040,13 +8961,6 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-linux-arm64-gnu@npm:4.27.2": - version: 4.27.2 - resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.27.2" - conditions: os=linux & cpu=arm64 & libc=glibc - languageName: node - linkType: hard - "@rollup/rollup-linux-arm64-gnu@npm:4.28.1": version: 4.28.1 resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.28.1" @@ -9054,13 +8968,6 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-linux-arm64-musl@npm:4.27.2": - version: 4.27.2 - resolution: "@rollup/rollup-linux-arm64-musl@npm:4.27.2" - conditions: os=linux & cpu=arm64 & libc=musl - languageName: node - linkType: hard - "@rollup/rollup-linux-arm64-musl@npm:4.28.1": version: 4.28.1 resolution: "@rollup/rollup-linux-arm64-musl@npm:4.28.1" @@ -9075,13 +8982,6 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-linux-powerpc64le-gnu@npm:4.27.2": - version: 4.27.2 - resolution: "@rollup/rollup-linux-powerpc64le-gnu@npm:4.27.2" - conditions: os=linux & cpu=ppc64 & libc=glibc - languageName: node - linkType: hard - "@rollup/rollup-linux-powerpc64le-gnu@npm:4.28.1": version: 4.28.1 resolution: "@rollup/rollup-linux-powerpc64le-gnu@npm:4.28.1" @@ -9089,13 +8989,6 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-linux-riscv64-gnu@npm:4.27.2": - version: 4.27.2 - resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.27.2" - conditions: os=linux & cpu=riscv64 & libc=glibc - languageName: node - linkType: hard - "@rollup/rollup-linux-riscv64-gnu@npm:4.28.1": version: 4.28.1 resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.28.1" @@ -9103,13 +8996,6 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-linux-s390x-gnu@npm:4.27.2": - version: 4.27.2 - resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.27.2" - conditions: os=linux & cpu=s390x & libc=glibc - languageName: node - linkType: hard - "@rollup/rollup-linux-s390x-gnu@npm:4.28.1": version: 4.28.1 resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.28.1" @@ -9117,13 +9003,6 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-linux-x64-gnu@npm:4.27.2": - version: 4.27.2 - resolution: "@rollup/rollup-linux-x64-gnu@npm:4.27.2" - conditions: os=linux & cpu=x64 & libc=glibc - languageName: node - linkType: hard - "@rollup/rollup-linux-x64-gnu@npm:4.28.1": version: 4.28.1 resolution: "@rollup/rollup-linux-x64-gnu@npm:4.28.1" @@ -9131,13 +9010,6 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-linux-x64-musl@npm:4.27.2": - version: 4.27.2 - resolution: "@rollup/rollup-linux-x64-musl@npm:4.27.2" - conditions: os=linux & cpu=x64 & libc=musl - languageName: node - linkType: hard - "@rollup/rollup-linux-x64-musl@npm:4.28.1": version: 4.28.1 resolution: "@rollup/rollup-linux-x64-musl@npm:4.28.1" @@ -9145,13 +9017,6 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-win32-arm64-msvc@npm:4.27.2": - version: 4.27.2 - resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.27.2" - conditions: os=win32 & cpu=arm64 - languageName: node - linkType: hard - "@rollup/rollup-win32-arm64-msvc@npm:4.28.1": version: 4.28.1 resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.28.1" @@ -9159,13 +9024,6 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-win32-ia32-msvc@npm:4.27.2": - version: 4.27.2 - resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.27.2" - conditions: os=win32 & cpu=ia32 - languageName: node - linkType: hard - "@rollup/rollup-win32-ia32-msvc@npm:4.28.1": version: 4.28.1 resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.28.1" @@ -9173,13 +9031,6 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-win32-x64-msvc@npm:4.27.2": - version: 4.27.2 - resolution: "@rollup/rollup-win32-x64-msvc@npm:4.27.2" - conditions: os=win32 & cpu=x64 - languageName: node - linkType: hard - "@rollup/rollup-win32-x64-msvc@npm:4.28.1": version: 4.28.1 resolution: "@rollup/rollup-win32-x64-msvc@npm:4.28.1" @@ -10660,16 +10511,7 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:*": - version: 22.9.0 - resolution: "@types/node@npm:22.9.0" - dependencies: - undici-types: "npm:~6.19.8" - checksum: 10/a7df3426891868b0f5fb03e46aeddd8446178233521c624a44531c92a040cf08a82d8235f7e1e02af731fd16984665d4d71f3418caf9c2788313b10f040d615d - languageName: node - linkType: hard - -"@types/node@npm:^22.10.1": +"@types/node@npm:*, @types/node@npm:^22.10.1": version: 22.10.1 resolution: "@types/node@npm:22.10.1" dependencies: @@ -10812,17 +10654,7 @@ __metadata: languageName: node linkType: hard -"@types/react@npm:*": - version: 18.3.12 - resolution: "@types/react@npm:18.3.12" - dependencies: - "@types/prop-types": "npm:*" - csstype: "npm:^3.0.2" - checksum: 10/c9bbdfeacd5347d2240e0d2cb5336bc57dbc1b9ff557b6c4024b49df83419e4955553518169d3736039f1b62608e15b35762a6c03d49bd86e33add4b43b19033 - languageName: node - linkType: hard - -"@types/react@npm:^18, @types/react@npm:~18.3.14": +"@types/react@npm:*, @types/react@npm:^18, @types/react@npm:~18.3.14": version: 18.3.14 resolution: "@types/react@npm:18.3.14" dependencies: @@ -12160,67 +11992,7 @@ __metadata: languageName: node linkType: hard -"antd@npm:^5.17.0": - version: 5.22.2 - resolution: "antd@npm:5.22.2" - dependencies: - "@ant-design/colors": "npm:^7.1.0" - "@ant-design/cssinjs": "npm:^1.21.1" - "@ant-design/cssinjs-utils": "npm:^1.1.1" - "@ant-design/icons": "npm:^5.5.1" - "@ant-design/react-slick": "npm:~1.1.2" - "@babel/runtime": "npm:^7.25.7" - "@ctrl/tinycolor": "npm:^3.6.1" - "@rc-component/color-picker": "npm:~2.0.1" - "@rc-component/mutate-observer": "npm:^1.1.0" - "@rc-component/qrcode": "npm:~1.0.0" - "@rc-component/tour": "npm:~1.15.1" - "@rc-component/trigger": "npm:^2.2.5" - classnames: "npm:^2.5.1" - copy-to-clipboard: "npm:^3.3.3" - dayjs: "npm:^1.11.11" - rc-cascader: "npm:~3.30.0" - rc-checkbox: "npm:~3.3.0" - rc-collapse: "npm:~3.9.0" - rc-dialog: "npm:~9.6.0" - rc-drawer: "npm:~7.2.0" - rc-dropdown: "npm:~4.2.0" - rc-field-form: "npm:~2.5.1" - rc-image: "npm:~7.11.0" - rc-input: "npm:~1.6.3" - rc-input-number: "npm:~9.3.0" - rc-mentions: "npm:~2.17.0" - rc-menu: "npm:~9.16.0" - rc-motion: "npm:^2.9.3" - rc-notification: "npm:~5.6.2" - rc-pagination: "npm:~4.3.0" - rc-picker: "npm:~4.8.1" - rc-progress: "npm:~4.0.0" - rc-rate: "npm:~2.13.0" - rc-resize-observer: "npm:^1.4.0" - rc-segmented: "npm:~2.5.0" - rc-select: "npm:~14.16.3" - rc-slider: "npm:~11.1.7" - rc-steps: "npm:~6.0.1" - rc-switch: "npm:~4.1.0" - rc-table: "npm:~7.48.1" - rc-tabs: "npm:~15.4.0" - rc-textarea: "npm:~1.8.2" - rc-tooltip: "npm:~6.2.1" - rc-tree: "npm:~5.10.1" - rc-tree-select: "npm:~5.24.4" - rc-upload: "npm:~4.8.1" - rc-util: "npm:^5.43.0" - scroll-into-view-if-needed: "npm:^3.1.0" - throttle-debounce: "npm:^5.0.2" - peerDependencies: - react: ">=16.9.0" - react-dom: ">=16.9.0" - checksum: 10/35f23abef7e2a7922640922128518a632f60868275daf51128cd8212c7ecfa102ad57f18de847a24aac8d3b70c5fe45f8fefb61ad01cd48ac4ccb07e10cea85a - languageName: node - linkType: hard - -"antd@npm:^5.22.3": +"antd@npm:^5.17.0, antd@npm:^5.22.3": version: 5.22.3 resolution: "antd@npm:5.22.3" dependencies: @@ -12628,20 +12400,13 @@ __metadata: languageName: node linkType: hard -"async@npm:^3.2.0": +"async@npm:^3.2.0, async@npm:^3.2.3": version: 3.2.6 resolution: "async@npm:3.2.6" checksum: 10/cb6e0561a3c01c4b56a799cc8bab6ea5fef45f069ab32500b6e19508db270ef2dffa55e5aed5865c5526e9907b1f8be61b27530823b411ffafb5e1538c86c368 languageName: node linkType: hard -"async@npm:^3.2.3": - version: 3.2.5 - resolution: "async@npm:3.2.5" - checksum: 10/323c3615c3f0ab1ac25a6f953296bc0ac3213d5e0f1c0debdb12964e55963af288d570293c11e44f7967af58c06d2a88d0ea588c86ec0fbf62fa98037f604a0f - languageName: node - linkType: hard - "asynckit@npm:^0.4.0": version: 0.4.0 resolution: "asynckit@npm:0.4.0" @@ -14907,18 +14672,7 @@ __metadata: languageName: node linkType: hard -"cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.3": - version: 7.0.5 - resolution: "cross-spawn@npm:7.0.5" - dependencies: - path-key: "npm:^3.1.0" - shebang-command: "npm:^2.0.0" - which: "npm:^2.0.1" - checksum: 10/c95062469d4bdbc1f099454d01c0e77177a3733012d41bf907a71eb8d22d2add43b5adf6a0a14ef4e7feaf804082714d6c262ef4557a1c480b86786c120d18e2 - languageName: node - linkType: hard - -"cross-spawn@npm:^7.0.5": +"cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.3, cross-spawn@npm:^7.0.5": version: 7.0.6 resolution: "cross-spawn@npm:7.0.6" dependencies: @@ -15163,20 +14917,13 @@ __metadata: languageName: node linkType: hard -"dayjs@npm:^1.10.4, dayjs@npm:^1.10.7, dayjs@npm:^1.11.10": +"dayjs@npm:^1.10.4, dayjs@npm:^1.10.7, dayjs@npm:^1.11.10, dayjs@npm:^1.11.11": version: 1.11.13 resolution: "dayjs@npm:1.11.13" checksum: 10/7374d63ab179b8d909a95e74790def25c8986e329ae989840bacb8b1888be116d20e1c4eee75a69ea0dfbae13172efc50ef85619d304ee7ca3c01d5878b704f5 languageName: node linkType: hard -"dayjs@npm:^1.11.11": - version: 1.11.12 - resolution: "dayjs@npm:1.11.12" - checksum: 10/8ee7c1e14961fd08d40b788d0c0e930dc6288b3d32911bb911b2fb31bb703c262788164fbe678ee9e50e2a35268d667b8c8ba43fd1806771c1f404c300a2b428 - languageName: node - linkType: hard - "debounce-fn@npm:^4.0.0": version: 4.0.0 resolution: "debounce-fn@npm:4.0.0" @@ -15669,14 +15416,7 @@ __metadata: languageName: node linkType: hard -"dotenv@npm:^16.0.0, dotenv@npm:^16.0.3, dotenv@npm:^16.3, dotenv@npm:^16.3.1, dotenv@npm:^16.4.5, dotenv@npm:~16.4.5": - version: 16.4.5 - resolution: "dotenv@npm:16.4.5" - checksum: 10/55a3134601115194ae0f924e54473459ed0d9fc340ae610b676e248cca45aa7c680d86365318ea964e6da4e2ea80c4514c1adab5adb43d6867fb57ff068f95c8 - languageName: node - linkType: hard - -"dotenv@npm:^16.4.7": +"dotenv@npm:^16.0.0, dotenv@npm:^16.0.3, dotenv@npm:^16.3, dotenv@npm:^16.3.1, dotenv@npm:^16.4.5, dotenv@npm:^16.4.7, dotenv@npm:~16.4.5": version: 16.4.7 resolution: "dotenv@npm:16.4.7" checksum: 10/f13bfe97db88f0df4ec505eeffb8925ec51f2d56a3d0b6d916964d8b4af494e6fb1633ba5d09089b552e77ab2a25de58d70259b2c5ed45ec148221835fc99a0c @@ -17368,46 +17108,7 @@ __metadata: languageName: node linkType: hard -"express@npm:^4.21.0, express@npm:^4.21.1": - version: 4.21.1 - resolution: "express@npm:4.21.1" - dependencies: - accepts: "npm:~1.3.8" - array-flatten: "npm:1.1.1" - body-parser: "npm:1.20.3" - content-disposition: "npm:0.5.4" - content-type: "npm:~1.0.4" - cookie: "npm:0.7.1" - cookie-signature: "npm:1.0.6" - debug: "npm:2.6.9" - depd: "npm:2.0.0" - encodeurl: "npm:~2.0.0" - escape-html: "npm:~1.0.3" - etag: "npm:~1.8.1" - finalhandler: "npm:1.3.1" - fresh: "npm:0.5.2" - http-errors: "npm:2.0.0" - merge-descriptors: "npm:1.0.3" - methods: "npm:~1.1.2" - on-finished: "npm:2.4.1" - parseurl: "npm:~1.3.3" - path-to-regexp: "npm:0.1.10" - proxy-addr: "npm:~2.0.7" - qs: "npm:6.13.0" - range-parser: "npm:~1.2.1" - safe-buffer: "npm:5.2.1" - send: "npm:0.19.0" - serve-static: "npm:1.16.2" - setprototypeof: "npm:1.2.0" - statuses: "npm:2.0.1" - type-is: "npm:~1.6.18" - utils-merge: "npm:1.0.1" - vary: "npm:~1.1.2" - checksum: 10/5d4a36dd03c1d1cce93172e9b185b5cd13a978d29ee03adc51cd278be7b4a514ae2b63e2fdaec0c00fdc95c6cfb396d9dd1da147917ffd337d6cd0778e08c9bc - languageName: node - linkType: hard - -"express@npm:^4.21.2": +"express@npm:^4.21.0, express@npm:^4.21.1, express@npm:^4.21.2": version: 4.21.2 resolution: "express@npm:4.21.2" dependencies: @@ -18035,18 +17736,7 @@ __metadata: languageName: node linkType: hard -"form-data@npm:^4.0.0": - version: 4.0.0 - resolution: "form-data@npm:4.0.0" - dependencies: - asynckit: "npm:^0.4.0" - combined-stream: "npm:^1.0.8" - mime-types: "npm:^2.1.12" - checksum: 10/7264aa760a8cf09482816d8300f1b6e2423de1b02bba612a136857413fdc96d7178298ced106817655facc6b89036c6e12ae31c9eb5bdc16aabf502ae8a5d805 - languageName: node - linkType: hard - -"form-data@npm:~4.0.0": +"form-data@npm:^4.0.0, form-data@npm:~4.0.0": version: 4.0.1 resolution: "form-data@npm:4.0.1" dependencies: @@ -18495,14 +18185,7 @@ __metadata: languageName: node linkType: hard -"globals@npm:^15.11.0, globals@npm:^15.9.0": - version: 15.12.0 - resolution: "globals@npm:15.12.0" - checksum: 10/07cac4ee7cc9befa7894be9b4d1a57f46eeedf9065939f39ffb875009394908eb7bac84147712cfd4bbabab5abc7ab98fc3a6d0fd881f9548fffa10ba2e4bf67 - languageName: node - linkType: hard - -"globals@npm:^15.13.0": +"globals@npm:^15.11.0, globals@npm:^15.13.0, globals@npm:^15.9.0": version: 15.13.0 resolution: "globals@npm:15.13.0" checksum: 10/ba84d0612d516bcc1dabdd9ce66667956e1a87401fb53be6c379f8f6a04f8e6ce415b584801ae2689a90e788e89bb38adfafc854a8a50ae8e322bb4dd35a2105 @@ -25087,13 +24770,6 @@ __metadata: languageName: node linkType: hard -"path-to-regexp@npm:0.1.10": - version: 0.1.10 - resolution: "path-to-regexp@npm:0.1.10" - checksum: 10/894e31f1b20e592732a87db61fff5b95c892a3fe430f9ab18455ebe69ee88ef86f8eb49912e261f9926fc53da9f93b46521523e33aefd9cb0a7b0d85d7096006 - languageName: node - linkType: hard - "path-to-regexp@npm:0.1.12": version: 0.1.12 resolution: "path-to-regexp@npm:0.1.12" @@ -25915,20 +25591,6 @@ __metadata: languageName: node linkType: hard -"rc-field-form@npm:~2.5.1": - version: 2.5.1 - resolution: "rc-field-form@npm:2.5.1" - dependencies: - "@babel/runtime": "npm:^7.18.0" - "@rc-component/async-validator": "npm:^5.0.3" - rc-util: "npm:^5.32.2" - peerDependencies: - react: ">=16.9.0" - react-dom: ">=16.9.0" - checksum: 10/1cc74b6c86e8c6062276f3e4984d3af3ee5d487ce0df102039dd106c40ff46087e572f108770c5e2713c76c1e8ff5fe25bbfe56b4cbbb0d740dc8c6aecbfa723 - languageName: node - linkType: hard - "rc-field-form@npm:~2.6.0": version: 2.6.0 resolution: "rc-field-form@npm:2.6.0" @@ -25976,21 +25638,7 @@ __metadata: languageName: node linkType: hard -"rc-input@npm:~1.6.0, rc-input@npm:~1.6.3": - version: 1.6.3 - resolution: "rc-input@npm:1.6.3" - dependencies: - "@babel/runtime": "npm:^7.11.1" - classnames: "npm:^2.2.1" - rc-util: "npm:^5.18.1" - peerDependencies: - react: ">=16.0.0" - react-dom: ">=16.0.0" - checksum: 10/e163a4105a5a7b18b88fc3c9cfa65fa36bf397c526843b30c0484eaab788feb0173b1f5af2fec26e6e31db4ec11b58a3619e35878bc4ea410e16e98f514ac664 - languageName: node - linkType: hard - -"rc-input@npm:~1.6.4": +"rc-input@npm:~1.6.0, rc-input@npm:~1.6.4": version: 1.6.4 resolution: "rc-input@npm:1.6.4" dependencies: @@ -26127,36 +25775,6 @@ __metadata: languageName: node linkType: hard -"rc-picker@npm:~4.8.1": - version: 4.8.2 - resolution: "rc-picker@npm:4.8.2" - dependencies: - "@babel/runtime": "npm:^7.24.7" - "@rc-component/trigger": "npm:^2.0.0" - classnames: "npm:^2.2.1" - rc-overflow: "npm:^1.3.2" - rc-resize-observer: "npm:^1.4.0" - rc-util: "npm:^5.43.0" - peerDependencies: - date-fns: ">= 2.x" - dayjs: ">= 1.x" - luxon: ">= 3.x" - moment: ">= 2.x" - react: ">=16.9.0" - react-dom: ">=16.9.0" - peerDependenciesMeta: - date-fns: - optional: true - dayjs: - optional: true - luxon: - optional: true - moment: - optional: true - checksum: 10/319ad7838ba6b9fa864e2c3e9952718bb2378ca4abd7defcefb382a7348e01e45b512649507368a88326b14732344f1e22ae23841ab181563b91d1b36f7b7009 - languageName: node - linkType: hard - "rc-progress@npm:~4.0.0": version: 4.0.0 resolution: "rc-progress@npm:4.0.0" @@ -26275,23 +25893,6 @@ __metadata: languageName: node linkType: hard -"rc-table@npm:~7.48.1": - version: 7.48.1 - resolution: "rc-table@npm:7.48.1" - dependencies: - "@babel/runtime": "npm:^7.10.1" - "@rc-component/context": "npm:^1.4.0" - classnames: "npm:^2.2.5" - rc-resize-observer: "npm:^1.1.0" - rc-util: "npm:^5.41.0" - rc-virtual-list: "npm:^3.14.2" - peerDependencies: - react: ">=16.9.0" - react-dom: ">=16.9.0" - checksum: 10/3d1690a89e71e5605246ff7ee6e77f9afe37ce451bc7bdfa15fdacc5ea2ed11857027d36f55ea406c302976c8e629f87c64eeeaaf37460312607468822eb10e7 - languageName: node - linkType: hard - "rc-table@npm:~7.49.0": version: 7.49.0 resolution: "rc-table@npm:7.49.0" @@ -26357,22 +25958,6 @@ __metadata: languageName: node linkType: hard -"rc-tree-select@npm:~5.24.4": - version: 5.24.4 - resolution: "rc-tree-select@npm:5.24.4" - dependencies: - "@babel/runtime": "npm:^7.25.7" - classnames: "npm:2.x" - rc-select: "npm:~14.16.2" - rc-tree: "npm:~5.10.1" - rc-util: "npm:^5.43.0" - peerDependencies: - react: "*" - react-dom: "*" - checksum: 10/301978847f306f897a0b5a87c251a6e7da1bd3ef97415b0b453215568e860fe50daba6ea4ea2d910aef7c69aa05f6faba56fe1d0afb9af797330cbf7c929215c - languageName: node - linkType: hard - "rc-tree-select@npm:~5.24.5": version: 5.24.5 resolution: "rc-tree-select@npm:5.24.5" @@ -27814,76 +27399,7 @@ __metadata: languageName: node linkType: hard -"rollup@npm:^4.20.0": - version: 4.27.2 - resolution: "rollup@npm:4.27.2" - dependencies: - "@rollup/rollup-android-arm-eabi": "npm:4.27.2" - "@rollup/rollup-android-arm64": "npm:4.27.2" - "@rollup/rollup-darwin-arm64": "npm:4.27.2" - "@rollup/rollup-darwin-x64": "npm:4.27.2" - "@rollup/rollup-freebsd-arm64": "npm:4.27.2" - "@rollup/rollup-freebsd-x64": "npm:4.27.2" - "@rollup/rollup-linux-arm-gnueabihf": "npm:4.27.2" - "@rollup/rollup-linux-arm-musleabihf": "npm:4.27.2" - "@rollup/rollup-linux-arm64-gnu": "npm:4.27.2" - "@rollup/rollup-linux-arm64-musl": "npm:4.27.2" - "@rollup/rollup-linux-powerpc64le-gnu": "npm:4.27.2" - "@rollup/rollup-linux-riscv64-gnu": "npm:4.27.2" - "@rollup/rollup-linux-s390x-gnu": "npm:4.27.2" - "@rollup/rollup-linux-x64-gnu": "npm:4.27.2" - "@rollup/rollup-linux-x64-musl": "npm:4.27.2" - "@rollup/rollup-win32-arm64-msvc": "npm:4.27.2" - "@rollup/rollup-win32-ia32-msvc": "npm:4.27.2" - "@rollup/rollup-win32-x64-msvc": "npm:4.27.2" - "@types/estree": "npm:1.0.6" - fsevents: "npm:~2.3.2" - dependenciesMeta: - "@rollup/rollup-android-arm-eabi": - optional: true - "@rollup/rollup-android-arm64": - optional: true - "@rollup/rollup-darwin-arm64": - optional: true - "@rollup/rollup-darwin-x64": - optional: true - "@rollup/rollup-freebsd-arm64": - optional: true - "@rollup/rollup-freebsd-x64": - optional: true - "@rollup/rollup-linux-arm-gnueabihf": - optional: true - "@rollup/rollup-linux-arm-musleabihf": - optional: true - "@rollup/rollup-linux-arm64-gnu": - optional: true - "@rollup/rollup-linux-arm64-musl": - optional: true - "@rollup/rollup-linux-powerpc64le-gnu": - optional: true - "@rollup/rollup-linux-riscv64-gnu": - optional: true - "@rollup/rollup-linux-s390x-gnu": - optional: true - "@rollup/rollup-linux-x64-gnu": - optional: true - "@rollup/rollup-linux-x64-musl": - optional: true - "@rollup/rollup-win32-arm64-msvc": - optional: true - "@rollup/rollup-win32-ia32-msvc": - optional: true - "@rollup/rollup-win32-x64-msvc": - optional: true - fsevents: - optional: true - bin: - rollup: dist/bin/rollup - checksum: 10/1e6b39cfd6158d5efea660ebb4c9514daace92c7be525573a62771c8d66aa31ff9abf32ec9a3fcf89955cde7d4d3dfefcfcc21b44097840ad09fb140334670d3 - languageName: node - linkType: hard - -"rollup@npm:^4.23.0": +"rollup@npm:^4.20.0, rollup@npm:^4.23.0": version: 4.28.1 resolution: "rollup@npm:4.28.1" dependencies: @@ -30474,13 +29990,6 @@ __metadata: languageName: node linkType: hard -"undici-types@npm:~6.19.8": - version: 6.19.8 - resolution: "undici-types@npm:6.19.8" - checksum: 10/cf0b48ed4fc99baf56584afa91aaffa5010c268b8842f62e02f752df209e3dea138b372a60a963b3b2576ed932f32329ce7ddb9cb5f27a6c83040d8cd74b7a70 - languageName: node - linkType: hard - "undici-types@npm:~6.20.0": version: 6.20.0 resolution: "undici-types@npm:6.20.0" @@ -31977,16 +31486,7 @@ __metadata: languageName: node linkType: hard -"yaml@npm:^2.3.1": - version: 2.6.0 - resolution: "yaml@npm:2.6.0" - bin: - yaml: bin.mjs - checksum: 10/f4369f667c7626c216ea81b5840fe9b530cdae4cff2d84d166ec1239e54bf332dbfac4a71bf60d121f8e85e175364a4e280a520292269b6cf9d074368309adf9 - languageName: node - linkType: hard - -"yaml@npm:^2.6.1": +"yaml@npm:^2.3.1, yaml@npm:^2.6.1": version: 2.6.1 resolution: "yaml@npm:2.6.1" bin: From 414e074fefa197c133e2dd4cba37802fd6567cec Mon Sep 17 00:00:00 2001 From: Joshua Tag Howard Date: Sun, 8 Dec 2024 20:27:35 +0000 Subject: [PATCH 15/22] Add markdown-it and its types to enhance markdown rendering capabilities --- packages/mobile/package.json | 2 ++ .../NativeBaseMarkdown/NativeBaseMarkdown.tsx | 1 - yarn.lock | 26 +++++++++++++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/packages/mobile/package.json b/packages/mobile/package.json index f076ac85..b6c58eca 100644 --- a/packages/mobile/package.json +++ b/packages/mobile/package.json @@ -87,6 +87,7 @@ "lodash": "^4.17.21", "luxon": "^3.5.0", "lz-string": "^1.5.0", + "markdown-it": "^14.1.0", "native-base": "patch:native-base@npm%3A3.4.28#../../.yarn/patches/native-base-npm-3.4.28-a8ecceae4d.patch", "react": "18.3.1", "react-dom": "~18.3.1", @@ -136,6 +137,7 @@ "@types/lodash": "^4.17.13", "@types/luxon": "^3.4.2", "@types/lz-string": "^1.5.0", + "@types/markdown-it": "^14", "@types/react": "~18.3.14", "@types/react-dom": "~18.3.2", "@types/react-native-rss-parser": "^1.4.3", diff --git a/packages/mobile/src/common/components/NativeBaseMarkdown/NativeBaseMarkdown.tsx b/packages/mobile/src/common/components/NativeBaseMarkdown/NativeBaseMarkdown.tsx index 5f5a4ed0..cfa852ca 100644 --- a/packages/mobile/src/common/components/NativeBaseMarkdown/NativeBaseMarkdown.tsx +++ b/packages/mobile/src/common/components/NativeBaseMarkdown/NativeBaseMarkdown.tsx @@ -40,7 +40,6 @@ const NativeBaseMarkdown = ({ style={style} markdownit={ // This is caused by using a reexport - MarkdownIt({ linkify: true, typographer: true, html: true }) } > diff --git a/yarn.lock b/yarn.lock index 9ed7def4..470ebf47 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10403,6 +10403,13 @@ __metadata: languageName: node linkType: hard +"@types/linkify-it@npm:^5": + version: 5.0.0 + resolution: "@types/linkify-it@npm:5.0.0" + checksum: 10/c3919044d4876f9d71d037e861745cd2485c95ac8c36a4fa67b132d4e60eb1d067e123cc7965c9cf5110eea351517d767f0d306af5e9147d6d0af87bc374ddcf + languageName: node + linkType: hard + "@types/lodash.isequal@npm:^4.5.8": version: 4.5.8 resolution: "@types/lodash.isequal@npm:4.5.8" @@ -10442,6 +10449,16 @@ __metadata: languageName: node linkType: hard +"@types/markdown-it@npm:^14": + version: 14.1.2 + resolution: "@types/markdown-it@npm:14.1.2" + dependencies: + "@types/linkify-it": "npm:^5" + "@types/mdurl": "npm:^2" + checksum: 10/ca2f239c8d59610b9f936fd40261a6ccf2fa1ae27a21816c031e5712542dcf9ee01e2fe29b31118df90716e11ade54e47d92a498e9b6488800e77ca8827255a2 + languageName: node + linkType: hard + "@types/mdast@npm:^3.0.0": version: 3.0.15 resolution: "@types/mdast@npm:3.0.15" @@ -10460,6 +10477,13 @@ __metadata: languageName: node linkType: hard +"@types/mdurl@npm:^2": + version: 2.0.0 + resolution: "@types/mdurl@npm:2.0.0" + checksum: 10/78746e96c655ceed63db06382da466fd52c7e9dc54d60b12973dfdd110cae06b9439c4b90e17bb8d4461109184b3ea9f3e9f96b3e4bf4aa9fe18b6ac35f283c8 + languageName: node + linkType: hard + "@types/mime@npm:^1": version: 1.3.5 resolution: "@types/mime@npm:1.3.5" @@ -11042,6 +11066,7 @@ __metadata: "@types/lodash": "npm:^4.17.13" "@types/luxon": "npm:^3.4.2" "@types/lz-string": "npm:^1.5.0" + "@types/markdown-it": "npm:^14" "@types/react": "npm:~18.3.14" "@types/react-dom": "npm:~18.3.2" "@types/react-native-rss-parser": "npm:^1.4.3" @@ -11086,6 +11111,7 @@ __metadata: lodash: "npm:^4.17.21" luxon: "npm:^3.5.0" lz-string: "npm:^1.5.0" + markdown-it: "npm:^14.1.0" metro-babel-register: "npm:^0.81.0" native-base: "patch:native-base@npm%3A3.4.28#../../.yarn/patches/native-base-npm-3.4.28-a8ecceae4d.patch" react: "npm:18.3.1" From 952f191b7d4af9b45304fa539650cb0b38907552 Mon Sep 17 00:00:00 2001 From: Joshua Tag Howard Date: Sun, 8 Dec 2024 21:32:25 +0000 Subject: [PATCH 16/22] Refactor authorization logic to simplify code --- .../lib/authorization/accessControl.test.ts | 1 - .../common/lib/authorization/accessControl.ts | 300 ++++++++++-------- 2 files changed, 174 insertions(+), 127 deletions(-) diff --git a/packages/common/lib/authorization/accessControl.test.ts b/packages/common/lib/authorization/accessControl.test.ts index 753afd38..c7aa61a0 100644 --- a/packages/common/lib/authorization/accessControl.test.ts +++ b/packages/common/lib/authorization/accessControl.test.ts @@ -304,7 +304,6 @@ describe("Committee chair or coordinator authorization", (test) => { test("has correct permissions for committee chair or coordinator", ({ expect, }) => { - expect(ability).can("manage", "CommitteeNode", "."); expect(ability).can("manage", "EventNode", "."); expect(ability).can("manage", "FeedNode", "."); expect(ability).can("manage", "ImageNode", "."); diff --git a/packages/common/lib/authorization/accessControl.ts b/packages/common/lib/authorization/accessControl.ts index 0fab719a..17ff01f0 100644 --- a/packages/common/lib/authorization/accessControl.ts +++ b/packages/common/lib/authorization/accessControl.ts @@ -11,6 +11,7 @@ import { } from "@casl/ability"; import validator from "validator"; +import type { EffectiveCommitteeRole } from "../api/resources/index.js"; import { parseGlobalId, type PersonNode, @@ -139,52 +140,190 @@ export function getAuthorizationFor({ teamMemberships, effectiveCommitteeRoles, }: CaslParam): AppAbility { - const { - can: allow, - // cannot: forbid, - build, - } = new AbilityBuilder(createMongoAbility); - - function doBuild() { - return build(caslOptions); - } + const { can: allow, build } = new AbilityBuilder( + createMongoAbility + ); + // All users may read active configurations and get device information (with that device's uuid) allow("readActive", ["ConfigurationNode"], "."); allow("get", ["DeviceNode"], "."); - if (accessLevel === AccessLevel.None) { - return doBuild(); + if (accessLevel > AccessLevel.None) { + if (accessLevel === AccessLevel.SuperAdmin) { + // Super admins may manage all resources + allow( + "manage", + "all", + Object.values(extraFieldsByResource).reduce( + (acc, fields) => acc.concat(Object.keys(fields)), + ["."] + ) + ); + } else { + // All users may read active feeds and marathon info + allow( + "readActive", + ["FeedNode", "MarathonNode", "MarathonHourNode"], + "." + ); + // All users may read committees, events, and images + allow("read", ["CommitteeNode", "EventNode", "ImageNode"], "."); + // All users may list teams + allow("list", ["TeamNode"], "."); + + applyAccessLevelPermissions(accessLevel, allow); + applyCommitteePermissions(effectiveCommitteeRoles, allow); + + if (userId != null) applyUserPermissions(userId, allow); + + if (teamMemberships.length > 0) + applyTeamPermissions(teamMemberships, allow); + } } - if (accessLevel === AccessLevel.SuperAdmin) { - allow( - "manage", - "all", - Object.values(extraFieldsByResource).reduce( - (acc, fields) => acc.concat(Object.keys(fields)), - ["."] - ) - ); - return doBuild(); + + return build(caslOptions); +} + +function applyTeamPermissions( + teamMemberships: SimpleTeamMembership[], + allow: AbilityBuilder["can"] +) { + const authTeamMemberships: string[] = []; + const authTeamCaptaincies: string[] = []; + for (const membership of teamMemberships) { + if (membership.position === MembershipPositionType.Captain) { + authTeamCaptaincies.push(membership.teamId); + } + authTeamMemberships.push(membership.teamId); } - allow("readActive", ["FeedNode", "MarathonNode", "MarathonHourNode"], "."); - allow("read", ["CommitteeNode", "EventNode", "ImageNode"], "."); - allow("list", ["TeamNode"], "."); + // Members of a team may read the team's information and fundraising total + allow("get", "TeamNode", [".", ".fundraisingTotal"], { + id: { $in: authTeamMemberships }, + }); + // Members of a team may read the team's members + allow("list", "TeamNode", [".members"], { + id: { $in: authTeamMemberships }, + }); + + // Captains of a team may manage the team's fundraising assignments + allow(["modify", "create"], "TeamNode", [".fundraisingAssignments"], { + id: { $in: authTeamCaptaincies }, + }); + // Captains of a team may get the team's solicitation code + allow("get", "TeamNode", [".solicitationCode"], { + id: { $in: authTeamCaptaincies }, + }); +} + +function applyUserPermissions( + userId: string, + allow: AbilityBuilder["can"] +) { + const parsedUserId = validator.isUUID(userId) + ? userId + : parseGlobalId(userId).unwrap().id; + + // Users may read their own information + allow("get", "PersonNode", ["."], { + id: { + $eq: parsedUserId, + }, + }); + // Users may read their own memberships and fundraising assignments + allow("list", "PersonNode", [".memberships", ".fundraisingAssignments"], { + id: { + $eq: parsedUserId, + }, + }); +} + +function applyCommitteePermissions( + effectiveCommitteeRoles: EffectiveCommitteeRole[], + allow: AbilityBuilder["can"] +) { + for (const { identifier, role } of effectiveCommitteeRoles) { + // Members of vice committee may read all members and teams + // Coords/Chairs of vice committee may manage all members and teams + if (identifier === CommitteeIdentifier.viceCommittee) { + allow(role === CommitteeRole.Member ? "read" : "manage", "PersonNode", [ + ".", + ".memberships", + ]); + allow(role === CommitteeRole.Member ? "read" : "manage", "TeamNode", [ + ".", + ".members", + ]); + } + + // Coords/Chairs of vice, community, tech, and marketing committees may deploy notifications + if ( + role !== CommitteeRole.Member && + (identifier === CommitteeIdentifier.viceCommittee || + identifier === CommitteeIdentifier.communityDevelopmentCommittee || + identifier === CommitteeIdentifier.techCommittee || + identifier === CommitteeIdentifier.marketingCommittee) + ) { + allow("deploy", "NotificationNode", "."); + } + // Members of dancer relations committee may manage point opportunities, point entries, and team members + if (identifier === CommitteeIdentifier.dancerRelationsCommittee) { + allow("manage", ["PointOpportunityNode", "PointEntryNode"], "."); + allow("manage", "TeamNode", ".members"); + } + + // Members of programming committee may manage marathon hours + if (identifier === CommitteeIdentifier.programmingCommittee) { + allow("manage", "MarathonHourNode", "."); + } + + // Members of fundraising committee may manage fundraising entries, daily department notifications, solicitation codes, and fundraising assignments + if (identifier === CommitteeIdentifier.fundraisingCommittee) { + allow( + "manage", + [ + "FundraisingEntryNode", + "DailyDepartmentNotificationNode", + "SolicitationCodeNode", + "FundraisingAssignmentNode", + ], + "." + ); + allow("manage", "TeamNode", [ + ".fundraisingAssignments", + ".solicitationCode", + ]); + allow("read", "TeamNode", ".fundraisingTotal"); + } + } +} + +function applyAccessLevelPermissions( + accessLevel: number, + allow: AbilityBuilder["can"] +) { + // Coords/Chairs of any committee may: if (accessLevel >= AccessLevel.CommitteeChairOrCoordinator) { + // Manage committees, events, feeds, images, and memberships allow( "manage", - ["CommitteeNode", "EventNode", "FeedNode", "ImageNode", "MembershipNode"], + ["EventNode", "FeedNode", "ImageNode", "MembershipNode"], "." ); + // Manage people and their memberships allow("manage", "PersonNode", [".", ".memberships"]); + // Read marathon info allow("read", ["MarathonHourNode", "MarathonNode"], "."); + // Create (but not send) notifications allow(["modify", "create"], "NotificationNode", "."); + // Read notifications allow("read", "NotificationNode", [ ".", ".deliveryIssue", ".deliveryIssueAcknowledgedAt", ]); + // Read notification deliveries allow("read", "NotificationDeliveryNode", [ ".", ".receiptCheckedAt", @@ -193,7 +332,9 @@ export function getAuthorizationFor({ ]); } + // Admins may: if (accessLevel >= AccessLevel.Admin) { + // Manage configurations, marathons, marathon hours, fundraising assignments, and fundraising entries allow( "manage", [ @@ -205,107 +346,14 @@ export function getAuthorizationFor({ ], "." ); + // Manage teams and their assignments and solicitation code + allow("manage", "TeamNode", [ + ".fundraisingAssignments", + ".solicitationCode", + ]); + // Read fundraising totals + allow("read", "TeamNode", ".fundraisingTotal"); + // Deploy notifications allow("deploy", "NotificationNode", "."); } - - for (const { identifier, role } of effectiveCommitteeRoles) { - switch (identifier) { - case CommitteeIdentifier.viceCommittee: { - allow(role === CommitteeRole.Member ? "read" : "manage", "PersonNode", [ - ".", - ".memberships", - ]); - allow(role === CommitteeRole.Member ? "read" : "manage", "TeamNode", [ - ".", - ".members", - ]); - // fallthrough - } - case CommitteeIdentifier.communityDevelopmentCommittee: - case CommitteeIdentifier.techCommittee: - case CommitteeIdentifier.marketingCommittee: { - if (role !== CommitteeRole.Member) { - allow("deploy", "NotificationNode", "."); - } - break; - } - case CommitteeIdentifier.dancerRelationsCommittee: { - allow("manage", ["PointOpportunityNode", "PointEntryNode"], "."); - allow("manage", "TeamNode", ".members"); - break; - } - case CommitteeIdentifier.programmingCommittee: { - if (role !== CommitteeRole.Member) { - allow("manage", "MarathonHourNode", "."); - } - break; - } - case CommitteeIdentifier.fundraisingCommittee: { - allow( - "manage", - [ - "FundraisingEntryNode", - "DailyDepartmentNotificationNode", - "SolicitationCodeNode", - "FundraisingAssignmentNode", - ], - "." - ); - allow("manage", "TeamNode", [ - ".fundraisingAssignments", - ".solicitationCode", - ]); - allow("read", "TeamNode", ".fundraisingTotal"); - break; - } - } - } - - const parsedUserId = userId - ? validator.isUUID(userId) - ? userId - : parseGlobalId(userId).unwrap().id - : null; - - if (parsedUserId) { - allow("get", "PersonNode", ["."], { - id: { - $eq: parsedUserId, - }, - }); - allow("list", "PersonNode", [".memberships", ".fundraisingAssignments"], { - id: { - $eq: parsedUserId, - }, - }); - } - - const authTeamMemberships = teamMemberships.map( - (membership) => membership.teamId - ); - - if (authTeamMemberships.length > 0) { - allow("get", "TeamNode", [".", ".fundraisingTotal"], { - id: { $in: authTeamMemberships }, - }); - allow("list", "TeamNode", [".members"], { - id: { $in: authTeamMemberships }, - }); - } - - const authTeamCaptaincies = teamMemberships - .filter( - (membership) => membership.position === MembershipPositionType.Captain - ) - .map((membership) => membership.teamId); - if (authTeamCaptaincies.length > 0) { - allow(["modify", "create"], "TeamNode", [".fundraisingAssignments"], { - id: { $in: authTeamCaptaincies }, - }); - allow("get", "TeamNode", [".solicitationCode"], { - id: { $in: authTeamCaptaincies }, - }); - } - - return doBuild(); } From 6fbd759e29edefbf30b490bbef3fdb5876afdcfd Mon Sep 17 00:00:00 2001 From: Joshua Tag Howard Date: Sun, 8 Dec 2024 21:32:35 +0000 Subject: [PATCH 17/22] Enhance AccessControlAuthorized decorator to support overloaded parameters and improve metadata collection --- .../lib/authorization/AccessControlParam.ts | 78 ++++++++++++++++++- 1 file changed, 76 insertions(+), 2 deletions(-) diff --git a/packages/common/lib/authorization/AccessControlParam.ts b/packages/common/lib/authorization/AccessControlParam.ts index 6aa6af3c..513ebea5 100644 --- a/packages/common/lib/authorization/AccessControlParam.ts +++ b/packages/common/lib/authorization/AccessControlParam.ts @@ -1,4 +1,6 @@ -import { Authorized } from "type-graphql"; +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable @typescript-eslint/no-unsafe-function-type */ +import { getMetadataStorage, SymbolKeysNotSupportedError } from "type-graphql"; import type { AppAbility } from "./accessControl.js"; @@ -7,8 +9,80 @@ export type AccessControlParam = ? Parameters | [Parameters[0]] : Parameters; +export function getArrayFromOverloadedRest( + overloadedArray: (T | readonly T[])[] +): T[] { + const items: T[] = Array.isArray(overloadedArray[0]) + ? (overloadedArray[0] as T[]) + : (overloadedArray as T[]); + return items; +} + +const authSummary: Record< + string, + Record< + string, + { + action: string; + subject: string; + field: string; + } + > +> = {}; + +// setTimeout(() => { +// console.log( +// Object.entries(authSummary) +// .map( +// ([k, v]) => +// `

${k}

${Object.entries(v) +// .map( +// ([n, { action, subject, field }]) => +// `
${n}
${action} ${subject}${field}
` +// ) +// .join("")}
` +// ) +// .join("") +// ); +// }, 3000); + export function AccessControlAuthorized( ...check: AccessControlParam ): PropertyDecorator & MethodDecorator & ClassDecorator { - return Authorized([check]); + return ( + target: Function | object, + propertyKey?: string | symbol, + _descriptor?: TypedPropertyDescriptor + ) => { + if (propertyKey == null) { + getMetadataStorage().collectAuthorizedResolverMetadata({ + target: target as Function, + roles: [check], + }); + return; + } + + if (typeof propertyKey === "symbol") { + throw new SymbolKeysNotSupportedError(); + } + + authSummary[target.constructor.name] = { + ...authSummary[target.constructor.name], + [propertyKey]: { + action: check[0], + subject: check[1] + ? typeof check[1] === "string" + ? check[1] + : `${check[1].kind}[id=${check[1].id}]` + : target.constructor.name.replace(/Resolver$/, "Node"), + field: check[2] ?? ".", + }, + }; + + getMetadataStorage().collectAuthorizedFieldMetadata({ + target: target.constructor, + fieldName: propertyKey, + roles: [check], + }); + }; } From 61c3433a7b68567da68ab90bf2fc4aaf588f0fad Mon Sep 17 00:00:00 2001 From: Joshua Tag Howard Date: Sun, 8 Dec 2024 21:52:45 +0000 Subject: [PATCH 18/22] Disable paging schema from Instagram feed validation --- .../lib/external-apis/feed/instagramfeed.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/server/src/lib/external-apis/feed/instagramfeed.ts b/packages/server/src/lib/external-apis/feed/instagramfeed.ts index 4c51fcd3..98ea8f14 100644 --- a/packages/server/src/lib/external-apis/feed/instagramfeed.ts +++ b/packages/server/src/lib/external-apis/feed/instagramfeed.ts @@ -29,15 +29,15 @@ const feedSchema = z.object({ timestamp: z.string().transform((iso) => DateTime.fromISO(iso)), }) ), - paging: z.object({ - cursors: z.object({ - before: z.string(), - - after: z.string(), - }), - next: z.string().url().optional(), - previous: z.string().url().optional(), - }), + // paging: z.object({ + // cursors: z.object({ + // before: z.string(), + + // after: z.string(), + // }), + // next: z.string().url().optional(), + // previous: z.string().url().optional(), + // }), }); export type InstagramFeedItem = z.TypeOf["data"][number]; From df9dde4b11e6793a4270bcd41c211b059e0e2d5f Mon Sep 17 00:00:00 2001 From: Joshua Tag Howard Date: Sun, 8 Dec 2024 21:53:01 +0000 Subject: [PATCH 19/22] Refactor AccessControlAuthorized to use indirect TypeGraphql imports --- .../common/lib/authorization/AccessControlParam.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/packages/common/lib/authorization/AccessControlParam.ts b/packages/common/lib/authorization/AccessControlParam.ts index 513ebea5..81a70c9f 100644 --- a/packages/common/lib/authorization/AccessControlParam.ts +++ b/packages/common/lib/authorization/AccessControlParam.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-unsafe-function-type */ -import { getMetadataStorage, SymbolKeysNotSupportedError } from "type-graphql"; +import * as TypeGraphql from "type-graphql"; import type { AppAbility } from "./accessControl.js"; @@ -49,13 +49,18 @@ const authSummary: Record< export function AccessControlAuthorized( ...check: AccessControlParam ): PropertyDecorator & MethodDecorator & ClassDecorator { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (!TypeGraphql.getMetadataStorage) { + return () => undefined; + } + return ( target: Function | object, propertyKey?: string | symbol, _descriptor?: TypedPropertyDescriptor ) => { if (propertyKey == null) { - getMetadataStorage().collectAuthorizedResolverMetadata({ + TypeGraphql.getMetadataStorage().collectAuthorizedResolverMetadata({ target: target as Function, roles: [check], }); @@ -63,7 +68,7 @@ export function AccessControlAuthorized( } if (typeof propertyKey === "symbol") { - throw new SymbolKeysNotSupportedError(); + throw new TypeGraphql.SymbolKeysNotSupportedError(); } authSummary[target.constructor.name] = { @@ -79,7 +84,7 @@ export function AccessControlAuthorized( }, }; - getMetadataStorage().collectAuthorizedFieldMetadata({ + TypeGraphql.getMetadataStorage().collectAuthorizedFieldMetadata({ target: target.constructor, fieldName: propertyKey, roles: [check], From 7c9dd9e274a91a86002d18eec9340f381099a4cc Mon Sep 17 00:00:00 2001 From: Joshua Tag Howard Date: Sun, 8 Dec 2024 21:53:08 +0000 Subject: [PATCH 20/22] Add permission to allow listing PointEntryNode in applyCommitteePermissions function --- packages/common/lib/authorization/accessControl.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/common/lib/authorization/accessControl.ts b/packages/common/lib/authorization/accessControl.ts index 17ff01f0..6cab4f2d 100644 --- a/packages/common/lib/authorization/accessControl.ts +++ b/packages/common/lib/authorization/accessControl.ts @@ -254,6 +254,7 @@ function applyCommitteePermissions( ".", ".members", ]); + allow("list", "PointEntryNode", "."); } // Coords/Chairs of vice, community, tech, and marketing committees may deploy notifications From 14a5f93e9dd05d1e6dcadf85474c2ae11d306104 Mon Sep 17 00:00:00 2001 From: Joshua Tag Howard Date: Sun, 8 Dec 2024 21:53:12 +0000 Subject: [PATCH 21/22] Refactor access control logic and update resource meta properties to use modelName --- .../portal/src/config/refine/authorization.ts | 45 ++++++++++++------- .../portal/src/config/refine/resources.tsx | 11 ++--- .../src/elements/singletons/ConfigModal.tsx | 2 +- 3 files changed, 36 insertions(+), 22 deletions(-) diff --git a/packages/portal/src/config/refine/authorization.ts b/packages/portal/src/config/refine/authorization.ts index cb3d5947..7fd856e3 100644 --- a/packages/portal/src/config/refine/authorization.ts +++ b/packages/portal/src/config/refine/authorization.ts @@ -12,23 +12,36 @@ export const accessControlProvider: AccessControlProvider = { return Promise.resolve({ can: false }); } + const ok = loginState.value.ability.can( + action === "clone" + ? "create" + : action === "edit" + ? "update" + : action === "show" + ? "get" + : (action as Action), + params?.resource?.meta?.modelName + ? { + id: params.id ? String(params.id) : undefined, + kind: params.resource.meta.modelName as "FundraisingAssignmentNode", + } + : "all" + ); + + console.log("Checking access control", { + authorized: ok, + action, + subject: params?.resource?.meta?.modelName + ? { + id: params.id ? String(params.id) : undefined, + kind: params.resource.meta.modelName as "FundraisingAssignmentNode", + } + : "all", + params, + }); + return Promise.resolve({ - can: loginState.value.ability.can( - action === "clone" - ? "create" - : action === "edit" - ? "update" - : action === "show" - ? "get" - : (action as Action), - params?.resource?.meta?.modelName - ? { - id: params.id ? String(params.id) : undefined, - kind: params.resource.meta - .modelName as "FundraisingAssignmentNode", - } - : "all" - ), + can: ok, }); }, options: { diff --git a/packages/portal/src/config/refine/resources.tsx b/packages/portal/src/config/refine/resources.tsx index 1d5c1a3e..e40a4687 100644 --- a/packages/portal/src/config/refine/resources.tsx +++ b/packages/portal/src/config/refine/resources.tsx @@ -18,7 +18,7 @@ export const refineResources: ResourceProps[] = [ meta: { icon: , label: "Events", - nodeName: "EventNode", + modelName: "EventNode", canDelete: true, }, create: "/events/create", @@ -31,7 +31,7 @@ export const refineResources: ResourceProps[] = [ meta: { icon: , label: "Teams", - nodeName: "TeamNode", + modelName: "TeamNode", canDelete: true, }, create: "/teams/create", @@ -44,13 +44,14 @@ export const refineResources: ResourceProps[] = [ meta: { icon: , label: "Fundraising", + modelName: "FundraisingEntryNode", }, }, { name: "fundraising", meta: { label: "Fundraising", - nodeName: "FundraisingEntryNode", + modelName: "FundraisingEntryNode", parent: "fundraising-group", }, create: "/fundraising/create", @@ -64,7 +65,7 @@ export const refineResources: ResourceProps[] = [ meta: { label: "Solicitation Codes", parent: "fundraising-group", - nodeName: "SolicitationCodeNode", + modelName: "SolicitationCodeNode", }, create: "/fundraising/solicitation-code/create", edit: "/fundraising/solicitation-code/:id/edit", @@ -76,7 +77,7 @@ export const refineResources: ResourceProps[] = [ meta: { label: "Uploaded DDNs", parent: "fundraising-group", - nodeName: "DailyDepartmentNotificationNode", + modelName: "DailyDepartmentNotificationNode", }, create: "/fundraising/ddn/create", edit: "/fundraising/ddn/:id/edit", diff --git a/packages/portal/src/elements/singletons/ConfigModal.tsx b/packages/portal/src/elements/singletons/ConfigModal.tsx index 25f18f68..f1e00196 100644 --- a/packages/portal/src/elements/singletons/ConfigModal.tsx +++ b/packages/portal/src/elements/singletons/ConfigModal.tsx @@ -20,7 +20,7 @@ export const ConfigModal = ({ useContext(marathonContext); return ( - +

Select Marathon