From 3a759d7d0a1a6a916c5f35146aee7de4de110546 Mon Sep 17 00:00:00 2001 From: jmgasper Date: Wed, 7 Oct 2026 09:05:39 +1100 Subject: [PATCH 1/6] feat: add public forum ACLs, catalog and insert-only Vanilla migration --- README.md | 10 + docs/public-forums.md | 128 + package.json | 3 +- packages/forums-prisma-client/edge.js | 50 +- .../forums-prisma-client/index-browser.js | 42 +- packages/forums-prisma-client/index.d.ts | 4154 ++++++++++++++++- packages/forums-prisma-client/index.js | 50 +- packages/forums-prisma-client/package.json | 2 +- packages/forums-prisma-client/schema.prisma | 40 + .../migration.sql | 20 + prisma/schema.prisma | 38 + .../decorators/optional-read.decorator.ts | 8 + src/auth/guards/token-roles.guard.ts | 5 + src/forums/dto/forums-read.dto.ts | 2 + src/forums/dto/public-forums.dto.ts | 39 + .../forums-access-policy.service.spec.ts | 2 + src/forums/forums-access-policy.service.ts | 37 +- src/forums/forums-access.types.ts | 2 + src/forums/forums-read-query.service.ts | 2 +- src/forums/forums-read.service.ts | 36 +- .../forums-watch-notification.service.spec.ts | 1 + .../forums-watch-notification.service.ts | 2 +- src/forums/forums.module.ts | 7 +- .../import/public-vanilla-import.spec.ts | 136 + src/forums/import/public-vanilla-import.ts | 708 +++ .../public-forum-access.service.spec.ts | 114 + src/forums/public-forum-access.service.ts | 80 + src/forums/public-forums.controller.ts | 48 + src/forums/public-forums.integration.spec.ts | 415 ++ src/forums/public-forums.service.ts | 247 + src/forums/topics.controller.ts | 9 +- src/scripts/public-vanilla-import.ts | 92 + 32 files changed, 6397 insertions(+), 132 deletions(-) create mode 100644 docs/public-forums.md create mode 100644 prisma/migrations/20261007000000_public_forums/migration.sql create mode 100644 src/auth/decorators/optional-read.decorator.ts create mode 100644 src/forums/dto/public-forums.dto.ts create mode 100644 src/forums/import/public-vanilla-import.spec.ts create mode 100644 src/forums/import/public-vanilla-import.ts create mode 100644 src/forums/public-forum-access.service.spec.ts create mode 100644 src/forums/public-forum-access.service.ts create mode 100644 src/forums/public-forums.controller.ts create mode 100644 src/forums/public-forums.integration.spec.ts create mode 100644 src/forums/public-forums.service.ts create mode 100644 src/scripts/public-vanilla-import.ts diff --git a/README.md b/README.md index 1410d93..4341a18 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,8 @@ and post-commit watch notifications. - `GET /v6/forums/health` - `GET /v6/forums/readiness` - `GET /v6/forums/api-docs` +- `GET /v6/forums/public/categories` +- `GET /v6/forums/public/topics` - `GET /v6/forums/topics` - `GET /v6/forums/topics/challenges/:challengeId` - `GET /v6/forums/topics/:topicId/children` @@ -141,6 +143,14 @@ PORT=3000 Health and readiness checks intentionally remain DB-only; they do not validate event-bus, SendGrid template, producer, or outbound Auth0 readiness. +## Public forums + +See [Public forums and migration](docs/public-forums.md) for guest reads, category +ACLs, target-member mapping, insert-only Vanilla import and cutover checks. +Public and general read routes accept anonymous callers, while mutations and +challenge lists still require authentication. Restricted category content is +filtered before statistics, search, pagination, watches and notifications. + ## Authentication Bearer tokens are validated with `tc-core-library-js` and normalized before guards diff --git a/docs/public-forums.md b/docs/public-forums.md new file mode 100644 index 0000000..4f2da52 --- /dev/null +++ b/docs/public-forums.md @@ -0,0 +1,128 @@ +# Public forums + +Public categories and threads use the same Topic, Post, TopicClosure, watch, +read-state, moderation, reaction and notification services as challenge forums. +The public catalog is a projection over that engine, not a second post store. + +## HTTP contract + +- `GET /v6/forums/public/categories` returns the visible category tree, descriptions, + ordering, authorized subtree counts/participants, latest activity and `canCreate`. +- `GET /v6/forums/public/topics` accepts `categoryId`, `search`, `watching=true`, + `sort=active|recent|oldest`, `page` and `perPage` (maximum 100). Search includes + non-deleted post content. Visibility is applied before totals and pagination. +- Existing general topic list, children and detail GET routes accept guests. + Challenge-specific lists still require authentication, and challenge visibility + still requires the existing challenge/resource checks. Supplied invalid tokens + are rejected; a missing token creates a non-elevated anonymous principal. +- Detail includes `permissions` for create-post, create-topic, watch, update and + delete. Clients should also respect `topic.locked`. +- Existing topic/post commands implement creation, replies, edits, reactions, + watches and mark-read. Every mutation requires authentication and policy checks. + Creating a public thread uses `parentTopicId` with the category ID and no + `challengeId`. Ordinary members cannot create category roots or delete content. + +`PublicForumCategory` holds ordered metadata and separate `readRoles`, +`createRoles` and `replyRoles` arrays. Entries within an array are alternatives; +every category ancestor must allow reading. `$public` permits guests, +`$authenticated` permits members, and other entries match normalized token roles +or imported forum-only membership rows. Empty arrays deny ordinary members. +Administrators and appropriately scoped machines retain elevated access. +Category containers cannot receive threads or posts. Threads cannot receive +child threads. Watches on categories cover descendant threads through the +existing closure-based notification recipient lookup. Recipient authorization +checks the category ACL again at delivery time. + +`PublicForumRoleMember` preserves Vanilla-only memberships without granting +Identity API roles or access to any other application. Copilot Discussions and +Reviewer Discussions explicitly require `copilot` and `reviewer`, respectively. +Other Private descendants retain their source permission categories and custom +memberships. A private ancestor cannot be bypassed by a permissive child. + +## Insert-only migration + +The additive Prisma migration creates three public metadata/provenance tables. +It does not alter or reset existing Topic/Post/challenge rows. Deploy the new API +**before importing**: older API versions do not understand public category ACLs. +Do not roll the service back to an older policy implementation while imported +restricted content is present. Roll forward with a fix instead. + +The public importer is separate from `import:vanilla` (the challenge importer). +It reads production MySQL in a read-only, repeatable-read consistent snapshot. +It excludes the challenge category subtree, all group-linked ancestry and the +obsolete empty Groups container. A group-linked discussion inside an otherwise +public category aborts the import for review. + +Preserved data includes category ancestry/order/descriptions, thread titles, +starter posts, nested replies, source timestamps, announcements, locks, author +handles, Markdown/mention/attachment URLs, known-member bookmarks, category +follows and read states. The two observed Rich-format source posts are converted +from Quill text/link operations; unsupported formats or embeds stop the import. +Referenced native Vanilla Media rows also stop the import until their files have +an explicit copy plan. The audited source has no referenced native media and no +reactions; its 99 Filestack links remain unchanged. + +Authors are mapped by unique lowercase handle against **target-environment** +Members data. Missing authors keep their source handle and a non-login +`vanilla:` identity. Missing target members' watches/read states are omitted +and reported; they must never be attached to a coincidentally equal numeric ID. +Only mapped target members receive forum-only role memberships. Global Vanilla +member/IP bans and challenge data are not imported. Existing view metrics remain +the shared API's distinct-reader counts, rather than Vanilla's cumulative hit +counter. Source-wide user identities/credentials are not copied. + +Deterministic `pf…` IDs and immutable provenance make an unchanged rerun a no-op. +The importer refuses unowned ID collisions, changed source projections, changed +target content, invalid reply parents and cyclic ancestry. It never overwrites a +user edit. All inserts occur in one serializable transaction with an advisory +lock. Challenge Topic, Post, TopicClosure, watch and read-state fingerprints must +match before and after or the transaction rolls back. Dry runs use read-only +transactions and make no data changes. + +### Commands + +Run `nvm use` in this project, install with `pnpm install --frozen-lockfile`, and +run `pnpm build`. Supply credentials through the environment or a secrets manager: + +```sh +# VANILLA_DB_URL points to the authorized production Vanilla source. +# FORUMS_DATABASE_URL and MEMBER_DB_URL both point to the intended target tenant. +# PUBLIC_IMPORT_TARGET_HOST must exactly match both target URL hostnames. +pnpm import:vanilla:public --report=/secure/path/public-forums-dry-run.json +pnpm import:vanilla:public --apply --report=/secure/path/public-forums-applied.json +pnpm import:vanilla:public --apply --report=/secure/path/public-forums-rerun.json +``` + +Reports are owner-readable files containing counts, a plan digest, missing source +author IDs and challenge fingerprints, without credentials or post content. UTC +is used for migration timestamps. Keep reports outside the repository. Check +that dry-run counts match source inventory, apply fingerprints match and the +immediate rerun inserts zero rows. If a source/target changes later, the conflict +requires reconciliation; the tool does not silently refresh already imported +content. Preserve the original reports to distinguish subsequent user activity. + +For production cutover, freeze Vanilla posting, repeat the source/target audit +against production Members, deploy the API first, run dry-run/apply/rerun, verify +anonymous/member/copilot/reviewer/private access and then switch the UI entry +point. Production import is a separate step after dev testing is accepted. + +## Verification + +`pnpm lint`, `pnpm build` and `pnpm test --runInBand` run normal checks. The real +PostgreSQL/HTTP suite also verifies anonymous read-only behavior, private search +and watch filtering, token errors, role/local-membership boundaries, member +commands, full-post search, migration reruns, conflict rollback and unchanged +challenge fingerprints. + +Use a disposable local database whose name ends in `_http_test`, apply Prisma +migrations, then run: + +```sh +PUBLIC_FORUMS_TEST_DATABASE_URL=postgresql://postgres@127.0.0.1:55439/public_forums_http_test \ + pnpm test --runInBand +``` + +That suite truncates its dedicated local test database, rejects remote hosts and +other database names, uses real SQL and policies, and substitutes JWT validation +and email publishing so it cannot notify real users. The standalone unit suite +runs without a database. diff --git a/package.json b/package.json index ec7528b..802176c 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,8 @@ "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", - "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand" + "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", + "import:vanilla:public": "node dist/scripts/public-vanilla-import.js" }, "dependencies": { "@nestjs/common": "^11.1.28", diff --git a/packages/forums-prisma-client/edge.js b/packages/forums-prisma-client/edge.js index efe60b3..ddd73b2 100644 --- a/packages/forums-prisma-client/edge.js +++ b/packages/forums-prisma-client/edge.js @@ -169,11 +169,42 @@ exports.Prisma.TopicReadStateScalarFieldEnum = { updatedAt: 'updatedAt' }; +exports.Prisma.PublicForumCategoryScalarFieldEnum = { + topicId: 'topicId', + description: 'description', + displayAs: 'displayAs', + sortOrder: 'sortOrder', + readRoles: 'readRoles', + createRoles: 'createRoles', + replyRoles: 'replyRoles', + legacyId: 'legacyId', + legacySlug: 'legacySlug', + source: 'source' +}; + +exports.Prisma.PublicForumRoleMemberScalarFieldEnum = { + roleName: 'roleName', + memberId: 'memberId' +}; + +exports.Prisma.PublicForumImportRecordScalarFieldEnum = { + entityType: 'entityType', + sourceId: 'sourceId', + targetId: 'targetId', + sourceHash: 'sourceHash', + targetHash: 'targetHash', + createdAt: 'createdAt' +}; + exports.Prisma.SortOrder = { asc: 'asc', desc: 'desc' }; +exports.Prisma.JsonNullValueInput = { + JsonNull: Prisma.JsonNull +}; + exports.Prisma.QueryMode = { default: 'default', insensitive: 'insensitive' @@ -183,6 +214,12 @@ exports.Prisma.NullsOrder = { first: 'first', last: 'last' }; + +exports.Prisma.JsonNullValueFilter = { + DbNull: Prisma.DbNull, + JsonNull: Prisma.JsonNull, + AnyNull: Prisma.AnyNull +}; exports.PostReactionType = exports.$Enums.PostReactionType = { THUMBS_UP: 'THUMBS_UP', THUMBS_DOWN: 'THUMBS_DOWN' @@ -196,7 +233,10 @@ exports.Prisma.ModelName = { PostReaction: 'PostReaction', TopicClosure: 'TopicClosure', TopicWatch: 'TopicWatch', - TopicReadState: 'TopicReadState' + TopicReadState: 'TopicReadState', + PublicForumCategory: 'PublicForumCategory', + PublicForumRoleMember: 'PublicForumRoleMember', + PublicForumImportRecord: 'PublicForumImportRecord' }; /** * Create the Client @@ -206,14 +246,14 @@ const config = { "clientVersion": "7.9.0", "engineVersion": "e922089b7d7502aff4249d5da3420f6fa55fc6ad", "activeProvider": "postgresql", - "inlineSchema": "generator client {\n provider = \"prisma-client-js\"\n output = \"./generated/client\"\n}\n\ngenerator externalClient {\n provider = \"prisma-client-js\"\n output = \"../packages/forums-prisma-client\"\n binaryTargets = [\"native\", \"debian-openssl-3.0.x\"]\n}\n\ndatasource db {\n provider = \"postgresql\"\n schemas = [\"forums\"]\n}\n\nenum PostReactionType {\n THUMBS_UP\n THUMBS_DOWN\n\n @@schema(\"forums\")\n}\n\nmodel Topic {\n id String @id @default(dbgenerated(\"nanoid()\")) @db.VarChar(14)\n parentTopicId String? @db.VarChar(14)\n challengeId String? @db.VarChar(64)\n roleName String? @db.VarChar(128)\n title String @db.VarChar(255)\n isAnnouncement Boolean @default(false)\n locked Boolean @default(false)\n lockedAt DateTime?\n lockedByMemberId String? @db.VarChar(64)\n authorMemberId String @db.VarChar(64)\n authorHandle String @db.VarChar(128)\n createdAt DateTime @default(now())\n updatedAt DateTime @default(now()) @updatedAt\n deletedAt DateTime?\n deletedByMemberId String? @db.VarChar(64)\n\n parentTopic Topic? @relation(\"TopicChildren\", fields: [parentTopicId], references: [id])\n childTopics Topic[] @relation(\"TopicChildren\")\n posts Post[]\n ancestorClosures TopicClosure[] @relation(\"TopicClosureAncestor\")\n descendantClosures TopicClosure[] @relation(\"TopicClosureDescendant\")\n watches TopicWatch[]\n readStates TopicReadState[]\n\n @@index([parentTopicId])\n @@index([challengeId])\n @@index([roleName])\n @@index([deletedAt])\n @@index([isAnnouncement, createdAt])\n @@schema(\"forums\")\n}\n\nmodel MemberBan {\n id String @id @default(dbgenerated(\"nanoid()\")) @db.VarChar(14)\n memberId String @db.VarChar(64)\n createdAt DateTime @default(now())\n createdByMemberId String? @db.VarChar(64)\n removedAt DateTime?\n removedByMemberId String? @db.VarChar(64)\n\n @@index([memberId])\n @@index([removedAt])\n @@schema(\"forums\")\n}\n\nmodel IpBan {\n id String @id @default(dbgenerated(\"nanoid()\")) @db.VarChar(14)\n ipAddress String @db.VarChar(45)\n createdAt DateTime @default(now())\n createdByMemberId String? @db.VarChar(64)\n removedAt DateTime?\n removedByMemberId String? @db.VarChar(64)\n\n @@index([ipAddress])\n @@index([removedAt])\n @@schema(\"forums\")\n}\n\nmodel Post {\n id String @id @default(dbgenerated(\"nanoid()\")) @db.VarChar(14)\n topicId String @db.VarChar(14)\n parentType String @db.VarChar(16)\n parentId String @db.VarChar(14)\n authorMemberId String @db.VarChar(64)\n authorHandle String @db.VarChar(128)\n content String? @db.Text\n createdAt DateTime @default(now())\n updatedAt DateTime @default(now()) @updatedAt\n deletedAt DateTime?\n deletedByMemberId String? @db.VarChar(64)\n\n topic Topic @relation(fields: [topicId], references: [id], onDelete: Cascade)\n reactions PostReaction[]\n\n @@index([topicId, createdAt])\n @@index([topicId, parentType, parentId])\n @@index([deletedAt])\n @@schema(\"forums\")\n}\n\nmodel PostReaction {\n postId String @db.VarChar(14)\n memberId String @db.VarChar(64)\n reaction PostReactionType\n createdAt DateTime @default(now())\n updatedAt DateTime @default(now()) @updatedAt\n\n post Post @relation(fields: [postId], references: [id], onDelete: Cascade)\n\n @@id([postId, memberId])\n @@index([memberId])\n @@index([postId, reaction])\n @@schema(\"forums\")\n}\n\nmodel TopicClosure {\n ancestorTopicId String @db.VarChar(14)\n descendantTopicId String @db.VarChar(14)\n depth Int\n\n ancestorTopic Topic @relation(\"TopicClosureAncestor\", fields: [ancestorTopicId], references: [id], onDelete: Cascade)\n descendantTopic Topic @relation(\"TopicClosureDescendant\", fields: [descendantTopicId], references: [id], onDelete: Cascade)\n\n @@id([ancestorTopicId, descendantTopicId])\n @@index([ancestorTopicId, depth])\n @@index([descendantTopicId, depth])\n @@schema(\"forums\")\n}\n\nmodel TopicWatch {\n topicId String @db.VarChar(14)\n memberId String @db.VarChar(64)\n createdAt DateTime @default(now())\n\n topic Topic @relation(fields: [topicId], references: [id], onDelete: Cascade)\n\n @@id([topicId, memberId])\n @@index([memberId])\n @@schema(\"forums\")\n}\n\nmodel TopicReadState {\n topicId String @db.VarChar(14)\n memberId String @db.VarChar(64)\n lastReadAt DateTime @default(now())\n updatedAt DateTime @default(now()) @updatedAt\n\n topic Topic @relation(fields: [topicId], references: [id], onDelete: Cascade)\n\n @@id([topicId, memberId])\n @@index([memberId])\n @@index([lastReadAt])\n @@schema(\"forums\")\n}\n" + "inlineSchema": "generator client {\n provider = \"prisma-client-js\"\n output = \"./generated/client\"\n}\n\ngenerator externalClient {\n provider = \"prisma-client-js\"\n output = \"../packages/forums-prisma-client\"\n binaryTargets = [\"native\", \"debian-openssl-3.0.x\"]\n}\n\ndatasource db {\n provider = \"postgresql\"\n schemas = [\"forums\"]\n}\n\nenum PostReactionType {\n THUMBS_UP\n THUMBS_DOWN\n\n @@schema(\"forums\")\n}\n\nmodel Topic {\n id String @id @default(dbgenerated(\"nanoid()\")) @db.VarChar(14)\n parentTopicId String? @db.VarChar(14)\n challengeId String? @db.VarChar(64)\n roleName String? @db.VarChar(128)\n title String @db.VarChar(255)\n isAnnouncement Boolean @default(false)\n locked Boolean @default(false)\n lockedAt DateTime?\n lockedByMemberId String? @db.VarChar(64)\n authorMemberId String @db.VarChar(64)\n authorHandle String @db.VarChar(128)\n createdAt DateTime @default(now())\n updatedAt DateTime @default(now()) @updatedAt\n deletedAt DateTime?\n deletedByMemberId String? @db.VarChar(64)\n\n parentTopic Topic? @relation(\"TopicChildren\", fields: [parentTopicId], references: [id])\n childTopics Topic[] @relation(\"TopicChildren\")\n posts Post[]\n ancestorClosures TopicClosure[] @relation(\"TopicClosureAncestor\")\n descendantClosures TopicClosure[] @relation(\"TopicClosureDescendant\")\n watches TopicWatch[]\n readStates TopicReadState[]\n\n @@index([parentTopicId])\n @@index([challengeId])\n @@index([roleName])\n @@index([deletedAt])\n @@index([isAnnouncement, createdAt])\n @@schema(\"forums\")\n}\n\nmodel MemberBan {\n id String @id @default(dbgenerated(\"nanoid()\")) @db.VarChar(14)\n memberId String @db.VarChar(64)\n createdAt DateTime @default(now())\n createdByMemberId String? @db.VarChar(64)\n removedAt DateTime?\n removedByMemberId String? @db.VarChar(64)\n\n @@index([memberId])\n @@index([removedAt])\n @@schema(\"forums\")\n}\n\nmodel IpBan {\n id String @id @default(dbgenerated(\"nanoid()\")) @db.VarChar(14)\n ipAddress String @db.VarChar(45)\n createdAt DateTime @default(now())\n createdByMemberId String? @db.VarChar(64)\n removedAt DateTime?\n removedByMemberId String? @db.VarChar(64)\n\n @@index([ipAddress])\n @@index([removedAt])\n @@schema(\"forums\")\n}\n\nmodel Post {\n id String @id @default(dbgenerated(\"nanoid()\")) @db.VarChar(14)\n topicId String @db.VarChar(14)\n parentType String @db.VarChar(16)\n parentId String @db.VarChar(14)\n authorMemberId String @db.VarChar(64)\n authorHandle String @db.VarChar(128)\n content String? @db.Text\n createdAt DateTime @default(now())\n updatedAt DateTime @default(now()) @updatedAt\n deletedAt DateTime?\n deletedByMemberId String? @db.VarChar(64)\n\n topic Topic @relation(fields: [topicId], references: [id], onDelete: Cascade)\n reactions PostReaction[]\n\n @@index([topicId, createdAt])\n @@index([topicId, parentType, parentId])\n @@index([deletedAt])\n @@schema(\"forums\")\n}\n\nmodel PostReaction {\n postId String @db.VarChar(14)\n memberId String @db.VarChar(64)\n reaction PostReactionType\n createdAt DateTime @default(now())\n updatedAt DateTime @default(now()) @updatedAt\n\n post Post @relation(fields: [postId], references: [id], onDelete: Cascade)\n\n @@id([postId, memberId])\n @@index([memberId])\n @@index([postId, reaction])\n @@schema(\"forums\")\n}\n\nmodel TopicClosure {\n ancestorTopicId String @db.VarChar(14)\n descendantTopicId String @db.VarChar(14)\n depth Int\n\n ancestorTopic Topic @relation(\"TopicClosureAncestor\", fields: [ancestorTopicId], references: [id], onDelete: Cascade)\n descendantTopic Topic @relation(\"TopicClosureDescendant\", fields: [descendantTopicId], references: [id], onDelete: Cascade)\n\n @@id([ancestorTopicId, descendantTopicId])\n @@index([ancestorTopicId, depth])\n @@index([descendantTopicId, depth])\n @@schema(\"forums\")\n}\n\nmodel TopicWatch {\n topicId String @db.VarChar(14)\n memberId String @db.VarChar(64)\n createdAt DateTime @default(now())\n\n topic Topic @relation(fields: [topicId], references: [id], onDelete: Cascade)\n\n @@id([topicId, memberId])\n @@index([memberId])\n @@schema(\"forums\")\n}\n\nmodel TopicReadState {\n topicId String @db.VarChar(14)\n memberId String @db.VarChar(64)\n lastReadAt DateTime @default(now())\n updatedAt DateTime @default(now()) @updatedAt\n\n topic Topic @relation(fields: [topicId], references: [id], onDelete: Cascade)\n\n @@id([topicId, memberId])\n @@index([memberId])\n @@index([lastReadAt])\n @@schema(\"forums\")\n}\n\n// Presentation and access rules for imported public category topics only.\nmodel PublicForumCategory {\n topicId String @id @db.VarChar(14)\n description String @default(\"\") @db.Text\n displayAs String @db.VarChar(16)\n sortOrder Int\n readRoles String[]\n createRoles String[]\n replyRoles String[]\n legacyId Int @unique\n legacySlug String @db.Text\n source Json\n\n @@schema(\"forums\")\n}\n\n// Vanilla-only role assignments never grant identity-wide roles.\nmodel PublicForumRoleMember {\n roleName String @db.VarChar(128)\n memberId String @db.VarChar(64)\n\n @@id([roleName, memberId])\n @@index([memberId])\n @@schema(\"forums\")\n}\n\n// Immutable provenance makes imports insert-only and safely repeatable.\nmodel PublicForumImportRecord {\n entityType String @db.VarChar(32)\n sourceId String @db.VarChar(64)\n targetId String @db.VarChar(64)\n sourceHash String @db.VarChar(64)\n targetHash String @db.VarChar(64)\n createdAt DateTime @default(now())\n\n @@id([entityType, sourceId])\n @@unique([entityType, targetId])\n @@schema(\"forums\")\n}\n" } -config.runtimeDataModel = JSON.parse("{\"models\":{\"Topic\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"parentTopicId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"challengeId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"roleName\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"title\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"isAnnouncement\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"locked\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"lockedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"lockedByMemberId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"authorMemberId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"authorHandle\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"deletedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"deletedByMemberId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"parentTopic\",\"kind\":\"object\",\"type\":\"Topic\",\"relationName\":\"TopicChildren\"},{\"name\":\"childTopics\",\"kind\":\"object\",\"type\":\"Topic\",\"relationName\":\"TopicChildren\"},{\"name\":\"posts\",\"kind\":\"object\",\"type\":\"Post\",\"relationName\":\"PostToTopic\"},{\"name\":\"ancestorClosures\",\"kind\":\"object\",\"type\":\"TopicClosure\",\"relationName\":\"TopicClosureAncestor\"},{\"name\":\"descendantClosures\",\"kind\":\"object\",\"type\":\"TopicClosure\",\"relationName\":\"TopicClosureDescendant\"},{\"name\":\"watches\",\"kind\":\"object\",\"type\":\"TopicWatch\",\"relationName\":\"TopicToTopicWatch\"},{\"name\":\"readStates\",\"kind\":\"object\",\"type\":\"TopicReadState\",\"relationName\":\"TopicToTopicReadState\"}],\"dbName\":null},\"MemberBan\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"memberId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"createdByMemberId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"removedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"removedByMemberId\",\"kind\":\"scalar\",\"type\":\"String\"}],\"dbName\":null},\"IpBan\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"ipAddress\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"createdByMemberId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"removedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"removedByMemberId\",\"kind\":\"scalar\",\"type\":\"String\"}],\"dbName\":null},\"Post\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"topicId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"parentType\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"parentId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"authorMemberId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"authorHandle\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"content\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"deletedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"deletedByMemberId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"topic\",\"kind\":\"object\",\"type\":\"Topic\",\"relationName\":\"PostToTopic\"},{\"name\":\"reactions\",\"kind\":\"object\",\"type\":\"PostReaction\",\"relationName\":\"PostToPostReaction\"}],\"dbName\":null},\"PostReaction\":{\"fields\":[{\"name\":\"postId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"memberId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"reaction\",\"kind\":\"enum\",\"type\":\"PostReactionType\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"post\",\"kind\":\"object\",\"type\":\"Post\",\"relationName\":\"PostToPostReaction\"}],\"dbName\":null},\"TopicClosure\":{\"fields\":[{\"name\":\"ancestorTopicId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"descendantTopicId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"depth\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"ancestorTopic\",\"kind\":\"object\",\"type\":\"Topic\",\"relationName\":\"TopicClosureAncestor\"},{\"name\":\"descendantTopic\",\"kind\":\"object\",\"type\":\"Topic\",\"relationName\":\"TopicClosureDescendant\"}],\"dbName\":null},\"TopicWatch\":{\"fields\":[{\"name\":\"topicId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"memberId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"topic\",\"kind\":\"object\",\"type\":\"Topic\",\"relationName\":\"TopicToTopicWatch\"}],\"dbName\":null},\"TopicReadState\":{\"fields\":[{\"name\":\"topicId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"memberId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"lastReadAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"topic\",\"kind\":\"object\",\"type\":\"Topic\",\"relationName\":\"TopicToTopicReadState\"}],\"dbName\":null}},\"enums\":{},\"types\":{}}") +config.runtimeDataModel = JSON.parse("{\"models\":{\"Topic\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"parentTopicId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"challengeId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"roleName\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"title\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"isAnnouncement\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"locked\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"lockedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"lockedByMemberId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"authorMemberId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"authorHandle\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"deletedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"deletedByMemberId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"parentTopic\",\"kind\":\"object\",\"type\":\"Topic\",\"relationName\":\"TopicChildren\"},{\"name\":\"childTopics\",\"kind\":\"object\",\"type\":\"Topic\",\"relationName\":\"TopicChildren\"},{\"name\":\"posts\",\"kind\":\"object\",\"type\":\"Post\",\"relationName\":\"PostToTopic\"},{\"name\":\"ancestorClosures\",\"kind\":\"object\",\"type\":\"TopicClosure\",\"relationName\":\"TopicClosureAncestor\"},{\"name\":\"descendantClosures\",\"kind\":\"object\",\"type\":\"TopicClosure\",\"relationName\":\"TopicClosureDescendant\"},{\"name\":\"watches\",\"kind\":\"object\",\"type\":\"TopicWatch\",\"relationName\":\"TopicToTopicWatch\"},{\"name\":\"readStates\",\"kind\":\"object\",\"type\":\"TopicReadState\",\"relationName\":\"TopicToTopicReadState\"}],\"dbName\":null},\"MemberBan\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"memberId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"createdByMemberId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"removedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"removedByMemberId\",\"kind\":\"scalar\",\"type\":\"String\"}],\"dbName\":null},\"IpBan\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"ipAddress\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"createdByMemberId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"removedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"removedByMemberId\",\"kind\":\"scalar\",\"type\":\"String\"}],\"dbName\":null},\"Post\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"topicId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"parentType\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"parentId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"authorMemberId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"authorHandle\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"content\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"deletedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"deletedByMemberId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"topic\",\"kind\":\"object\",\"type\":\"Topic\",\"relationName\":\"PostToTopic\"},{\"name\":\"reactions\",\"kind\":\"object\",\"type\":\"PostReaction\",\"relationName\":\"PostToPostReaction\"}],\"dbName\":null},\"PostReaction\":{\"fields\":[{\"name\":\"postId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"memberId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"reaction\",\"kind\":\"enum\",\"type\":\"PostReactionType\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"post\",\"kind\":\"object\",\"type\":\"Post\",\"relationName\":\"PostToPostReaction\"}],\"dbName\":null},\"TopicClosure\":{\"fields\":[{\"name\":\"ancestorTopicId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"descendantTopicId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"depth\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"ancestorTopic\",\"kind\":\"object\",\"type\":\"Topic\",\"relationName\":\"TopicClosureAncestor\"},{\"name\":\"descendantTopic\",\"kind\":\"object\",\"type\":\"Topic\",\"relationName\":\"TopicClosureDescendant\"}],\"dbName\":null},\"TopicWatch\":{\"fields\":[{\"name\":\"topicId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"memberId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"topic\",\"kind\":\"object\",\"type\":\"Topic\",\"relationName\":\"TopicToTopicWatch\"}],\"dbName\":null},\"TopicReadState\":{\"fields\":[{\"name\":\"topicId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"memberId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"lastReadAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"topic\",\"kind\":\"object\",\"type\":\"Topic\",\"relationName\":\"TopicToTopicReadState\"}],\"dbName\":null},\"PublicForumCategory\":{\"fields\":[{\"name\":\"topicId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"description\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"displayAs\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"sortOrder\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"readRoles\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createRoles\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"replyRoles\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"legacyId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"legacySlug\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"source\",\"kind\":\"scalar\",\"type\":\"Json\"}],\"dbName\":null},\"PublicForumRoleMember\":{\"fields\":[{\"name\":\"roleName\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"memberId\",\"kind\":\"scalar\",\"type\":\"String\"}],\"dbName\":null},\"PublicForumImportRecord\":{\"fields\":[{\"name\":\"entityType\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"sourceId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"targetId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"sourceHash\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"targetHash\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null}},\"enums\":{},\"types\":{}}") defineDmmfProperty(exports.Prisma, config.runtimeDataModel) config.parameterizationSchema = { - strings: JSON.parse("[\"where\",\"parentTopic\",\"orderBy\",\"cursor\",\"childTopics\",\"topic\",\"post\",\"reactions\",\"_count\",\"posts\",\"ancestorTopic\",\"descendantTopic\",\"ancestorClosures\",\"descendantClosures\",\"watches\",\"readStates\",\"Topic.findUnique\",\"Topic.findUniqueOrThrow\",\"Topic.findFirst\",\"Topic.findFirstOrThrow\",\"Topic.findMany\",\"data\",\"Topic.createOne\",\"Topic.createMany\",\"Topic.createManyAndReturn\",\"Topic.updateOne\",\"Topic.updateMany\",\"Topic.updateManyAndReturn\",\"create\",\"update\",\"Topic.upsertOne\",\"Topic.deleteOne\",\"Topic.deleteMany\",\"having\",\"_min\",\"_max\",\"Topic.groupBy\",\"Topic.aggregate\",\"MemberBan.findUnique\",\"MemberBan.findUniqueOrThrow\",\"MemberBan.findFirst\",\"MemberBan.findFirstOrThrow\",\"MemberBan.findMany\",\"MemberBan.createOne\",\"MemberBan.createMany\",\"MemberBan.createManyAndReturn\",\"MemberBan.updateOne\",\"MemberBan.updateMany\",\"MemberBan.updateManyAndReturn\",\"MemberBan.upsertOne\",\"MemberBan.deleteOne\",\"MemberBan.deleteMany\",\"MemberBan.groupBy\",\"MemberBan.aggregate\",\"IpBan.findUnique\",\"IpBan.findUniqueOrThrow\",\"IpBan.findFirst\",\"IpBan.findFirstOrThrow\",\"IpBan.findMany\",\"IpBan.createOne\",\"IpBan.createMany\",\"IpBan.createManyAndReturn\",\"IpBan.updateOne\",\"IpBan.updateMany\",\"IpBan.updateManyAndReturn\",\"IpBan.upsertOne\",\"IpBan.deleteOne\",\"IpBan.deleteMany\",\"IpBan.groupBy\",\"IpBan.aggregate\",\"Post.findUnique\",\"Post.findUniqueOrThrow\",\"Post.findFirst\",\"Post.findFirstOrThrow\",\"Post.findMany\",\"Post.createOne\",\"Post.createMany\",\"Post.createManyAndReturn\",\"Post.updateOne\",\"Post.updateMany\",\"Post.updateManyAndReturn\",\"Post.upsertOne\",\"Post.deleteOne\",\"Post.deleteMany\",\"Post.groupBy\",\"Post.aggregate\",\"PostReaction.findUnique\",\"PostReaction.findUniqueOrThrow\",\"PostReaction.findFirst\",\"PostReaction.findFirstOrThrow\",\"PostReaction.findMany\",\"PostReaction.createOne\",\"PostReaction.createMany\",\"PostReaction.createManyAndReturn\",\"PostReaction.updateOne\",\"PostReaction.updateMany\",\"PostReaction.updateManyAndReturn\",\"PostReaction.upsertOne\",\"PostReaction.deleteOne\",\"PostReaction.deleteMany\",\"PostReaction.groupBy\",\"PostReaction.aggregate\",\"TopicClosure.findUnique\",\"TopicClosure.findUniqueOrThrow\",\"TopicClosure.findFirst\",\"TopicClosure.findFirstOrThrow\",\"TopicClosure.findMany\",\"TopicClosure.createOne\",\"TopicClosure.createMany\",\"TopicClosure.createManyAndReturn\",\"TopicClosure.updateOne\",\"TopicClosure.updateMany\",\"TopicClosure.updateManyAndReturn\",\"TopicClosure.upsertOne\",\"TopicClosure.deleteOne\",\"TopicClosure.deleteMany\",\"_avg\",\"_sum\",\"TopicClosure.groupBy\",\"TopicClosure.aggregate\",\"TopicWatch.findUnique\",\"TopicWatch.findUniqueOrThrow\",\"TopicWatch.findFirst\",\"TopicWatch.findFirstOrThrow\",\"TopicWatch.findMany\",\"TopicWatch.createOne\",\"TopicWatch.createMany\",\"TopicWatch.createManyAndReturn\",\"TopicWatch.updateOne\",\"TopicWatch.updateMany\",\"TopicWatch.updateManyAndReturn\",\"TopicWatch.upsertOne\",\"TopicWatch.deleteOne\",\"TopicWatch.deleteMany\",\"TopicWatch.groupBy\",\"TopicWatch.aggregate\",\"TopicReadState.findUnique\",\"TopicReadState.findUniqueOrThrow\",\"TopicReadState.findFirst\",\"TopicReadState.findFirstOrThrow\",\"TopicReadState.findMany\",\"TopicReadState.createOne\",\"TopicReadState.createMany\",\"TopicReadState.createManyAndReturn\",\"TopicReadState.updateOne\",\"TopicReadState.updateMany\",\"TopicReadState.updateManyAndReturn\",\"TopicReadState.upsertOne\",\"TopicReadState.deleteOne\",\"TopicReadState.deleteMany\",\"TopicReadState.groupBy\",\"TopicReadState.aggregate\",\"AND\",\"OR\",\"NOT\",\"topicId\",\"memberId\",\"lastReadAt\",\"updatedAt\",\"equals\",\"in\",\"notIn\",\"lt\",\"lte\",\"gt\",\"gte\",\"not\",\"contains\",\"startsWith\",\"endsWith\",\"createdAt\",\"ancestorTopicId\",\"descendantTopicId\",\"depth\",\"postId\",\"PostReactionType\",\"reaction\",\"id\",\"parentType\",\"parentId\",\"authorMemberId\",\"authorHandle\",\"content\",\"deletedAt\",\"deletedByMemberId\",\"ipAddress\",\"createdByMemberId\",\"removedAt\",\"removedByMemberId\",\"parentTopicId\",\"challengeId\",\"roleName\",\"title\",\"isAnnouncement\",\"locked\",\"lockedAt\",\"lockedByMemberId\",\"topicId_memberId\",\"ancestorTopicId_descendantTopicId\",\"postId_memberId\",\"every\",\"some\",\"none\",\"is\",\"isNot\",\"connectOrCreate\",\"upsert\",\"createMany\",\"set\",\"disconnect\",\"delete\",\"connect\",\"updateMany\",\"deleteMany\",\"increment\",\"decrement\",\"multiply\",\"divide\"]"), - graph: "1QNFgAEZAQAAjwIAIAQAAJACACAJAACRAgAgDAAAkgIAIA0AAJICACAOAACTAgAgDwAAlAIAIJgBAACNAgAwmQEAAAMAEJoBAACNAgAwngFAAPYBACGqAUAA9gEAIbEBAQAAAAG0AQEA9QEAIbUBAQD1AQAhtwFAAPgBACG4AQEA9wEAIb0BAQD3AQAhvgEBAPcBACG_AQEA9wEAIcABAQD1AQAhwQEgAI4CACHCASAAjgIAIcMBQAD4AQAhxAEBAPcBACEBAAAAAQAgGQEAAI8CACAEAACQAgAgCQAAkQIAIAwAAJICACANAACSAgAgDgAAkwIAIA8AAJQCACCYAQAAjQIAMJkBAAADABCaAQAAjQIAMJ4BQAD2AQAhqgFAAPYBACGxAQEA9QEAIbQBAQD1AQAhtQEBAPUBACG3AUAA-AEAIbgBAQD3AQAhvQEBAPcBACG-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_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_ABCaAQAA6wEAMJsBAQDbAQAhngFAANwBACGqAUAA3AEAIbEBAQDbAQAhsgEBANsBACGzAQEA2wEAIbQBAQDbAQAhtQEBANsBACG2AQEA7AEAIbcBQADtAQAhuAEBAOwBACEDAAAABwAgAgAAfgAwIQAAfwAgAwAAAAcAIAIAAAgAMAMAAAkAIAEAAAANACABAAAADQAgAwAAAAsAIAIAAAwAMAMAAA0AIAMAAAALACACAAAMADADAAANACADAAAACwAgAgAADAAwAwAADQAgBgYAALACACCcAQEAAAABngFAAAAAAaoBQAAAAAGuAQEAAAABsAEAAACwAQIBFQAAhwEAIAWcAQEAAAABngFAAAAAAaoBQAAAAAGuAQEAAAABsAEAAACwAQIBFQAAiQEAMAEVAACJAQAwBgYAAK8CACCcAQEAmAIAIZ4BQACZAgAhqgFAAJkCACGuAQEAmAIAIbABAACuArABIgIAAAANACAVAACMAQAgBZwBAQCYAgAhngFAAJkCACGqAUAAmQIAIa4BAQCYAgAhsAEAAK4CsAEiAgAAAAsAIBUAAI4BACACAAAACwAgFQAAjgEAIAMAAAANACAcAACHAQAgHQAAjAEAIAEAAAANACABAAAACwAgAwgAAKsCACAiAACtAgAgIwAArAIAIAiYAQAA5wEAMJkBAACVAQAQmgEAAOcBADCcAQEA2wEAIZ4BQADcAQAhqgFAANwBACGuAQEA2wEAIbABAADoAbABIgMAAAALACACAACUAQAwIQAAlQEAIAMAAAALACACAAAMADADAAANACABAAAAEgAgAQAAABIAIAMAAAAQACACAAARADADAAASACADAAAAEAAgAgAAEQAwAwAAEgAgAwAAABAAIAIAABEAMAMAABIAIAUKAACpAgAgCwAAqgIAIKsBAQAAAAGsAQEAAAABrQECAAAAAQEVAACdAQAgA6sBAQAAAAGsAQEAAAABrQECAAAAAQEVAACfAQAwARUAAJ8BADAFCgAApwIAIAsAAKgCACCrAQEAmAIAIawBAQCYAgAhrQECAKYCACECAAAAEgAgFQAAogEAIAOrAQEAmAIAIawBAQCYAgAhrQECAKYCACECAAAAEAAgFQAApAEAIAIAAAAQACAVAACkAQAgAwAAABIAIBwAAJ0BACAdAACiAQAgAQAAABIAIAEAAAAQ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AAABoAEBAAAABaEBAQAAAAWiAQEAAAABowEBAAAAAaQBAQAAAAGlAQEAAAABpgEBAPIBACGnAQEAAAABqAEBAAAAAakBAQAAAAEJmAEAAPMBADCZAQAAaQAQmgEAAPMBADCqAUAA3AEAIbEBAQDbAQAhuQEBANsBACG6AQEA7AEAIbsBQADtAQAhvAEBAOwBACEJmAEAAPQBADCZAQAAVgAQmgEAAPQBADCqAUAA9gEAIbEBAQD1AQAhuQEBAPUBACG6AQEA9wEAIbsBQAD4AQAhvAEBAPcBACELnwEBAAAAAaABAQAAAAShAQEAAAAEogEBAAAAAaMBAQAAAAGkAQEAAAABpQEBAAAAAaYBAQDhAQAhpwEBAAAAAagBAQAAAAGpAQEAAAABCJ8BQAAAAAGgAUAAAAAEoQFAAAAABKIBQAAAAAGjAUAAAAABpAFAAAAAAaUBQAAAAAGmAUAA3wEAIQufAQEAAAABoAEBAAAABaEBAQAAAAWiAQEAAAABowEBAAAAAaQBAQAAAAGlAQEAAAABpgEBAPIBACGnAQEAAAABqAEBAAAAAakBAQAAAAEInwFAAAAAAaABQAAAAAWhAUAAAAAFogFAAAAAAaMBQAAAAAGkAUAAAAABpQFAAAAAAaYBQADwAQAhCZgBAAD5AQAwmQEAAFAAEJoBAAD5AQAwnAEBANsBACGqAUAA3AEAIbEBAQDbAQAhugEBAOwBACG7AUAA7QEAIbwBAQDsAQAhCZgBAAD6AQAwmQEAAD0AEJoBAAD6AQAwnAEBAPUBACGqAUAA9gEAIbEBAQD1AQAhugEBAPcBACG7AUAA-AEAIbwBAQD3AQAhEpgBAAD7AQAwmQEAADcAEJoBAAD7AQAwngFAANwBACGqAUAA3AEAIbEBAQDbAQAhtAEBANsBACG1AQEA2wEAIbcBQADtAQAhuAEBAOwBACG9AQEA7AEAIb4BAQDsAQAhvwEBAOwBACHAAQEA2wEAIcEBIAD8AQAhwgEgAPwBACHDAUAA7QEAIcQBAQDsAQAhBQgAAN4BACAiAAD-AQAgIwAA_gEAIJ8BIAAAAAGmASAA_QEAIQUIAADeAQAgIgAA_gEAICMAAP4BACCfASAAAAABpgEgAP0BACECnwEgAAAAAaYBIAD-AQAhApsBAQAAAAGcAQEAAAABCAUAAIECACCYAQAAgAIAMJkBAAAZABCaAQAAgAIAMJsBAQD1AQAhnAEBAPUBACGdAUAA9gEAIZ4BQAD2AQAhGwEAAI8CACAEAACQAgAgCQAAkQIAIAwAAJICACANAACSAgAgDgAAkwIAIA8AAJQCACCYAQAAjQIAMJkBAAADABCaAQAAjQIAMJ4BQAD2AQAhqgFAAPYBACGxAQEA9QEAIbQBAQD1AQAhtQEBAPUBACG3AUAA-AEAIbgBAQD3AQAhvQEBAPcBACG-AQEA9wEAIb8BAQD3AQAhwAEBAPUBACHBASAAjgIAIcIBIACOAgAhwwFAAPgBACHEAQEA9wEAIcsBAAADACDMAQAAAwAgApsBAQAAAAGcAQEAAAABBwUAAIECACCYAQAAgwIAMJkBAAAVABCaAQAAgwIAMJsBAQD1AQAhnAEBAPUBACGqAUAA9gEAIQKrAQEAAAABrAEBAAAAAQgKAACBAgAgCwAAgQIAIJgBAACFAgAwmQEAABAAEJoBAACFAgAwqwEBAPUBACGsAQEA9QEAIa0BAgCGAgAhCJ8BAgAAAAGgAQIAAAAEoQECAAAABKIBAgAAAAGjAQIAAAABpAECAAAAAaUBAgAAAAGmAQIA3gEAIQKcAQEAAAABrgEBAAAAAQkGAACKAgAgmAEAAIgCADCZAQAACwAQmgEAAIgCADCcAQEA9QEAIZ4BQAD2AQAhqgFAAPYBACGuAQEA9QEAIbABAACJArABIgSfAQAAALABAqABAAAAsAEIoQEAAACwAQimAQAA6gGwASISBQAAgQIAIAcAAIwCACCYAQAAiwIAMJkBAAAHABCaAQAAiwIAMJsBAQD1AQAhngFAAPYBACGqAUAA9gEAIbEBAQD1AQAhsgEBAPUBACGzAQEA9QEAIbQBAQD1AQAhtQEBAPUBACG2AQEA9wEAIbcBQAD4AQAhuAEBAPcBACHLAQAABwAgzAEAAAcAIBAFAACBAgAgBwAAjAIAIJgBAACLAgAwmQEAAAcAEJoBAACLAgAwmwEBAPUBACGeAUAA9gEAIaoBQAD2AQAhsQEBAPUBACGyAQEA9QEAIbMBAQD1AQAhtAEBAPUBACG1AQEA9QEAIbYBAQD3AQAhtwFAAPgBACG4AQEA9wEAIQPIAQAACwAgyQEAAAsAIMoBAAALACAZAQAAjwIAIAQAAJACACAJAACRAgAgDAAAkgIAIA0AAJICACAOAACTAgAgDwAAlAIAIJgBAACNAgAwmQEAAAMAEJoBAACNAgAwngFAAPYBACGqAUAA9gEAIbEBAQD1AQAhtAEBAPUBACG1AQEA9QEAIbcBQAD4AQAhuAEBAPcBACG9AQEA9wEAIb4BAQD3AQAhvwEBAPcBACHAAQEA9QEAIcEBIACOAgAhwgEgAI4CACHDAUAA-AEAIcQBAQD3AQAhAp8BIAAAAAGmASAA_gEAIRsBAACPAgAgBAAAkAIAIAkAAJECACAMAACSAgAgDQAAkgIAIA4AAJMCACAPAACUAgAgmAEAAI0CADCZAQAAAwAQmgEAAI0CADCeAUAA9gEAIaoBQAD2AQAhsQEBAPUBACG0AQEA9QEAIbUBAQD1AQAhtwFAAPgBACG4AQEA9wEAIb0BAQD3AQAhvgEBAPcBACG_AQEA9wEAIcABAQD1AQAhwQEgAI4CACHCASAAjgIAIcMBQAD4AQAhxAEBAPcBACHLAQAAAwAgzAEAAAMAIAPIAQAAAwAgyQEAAAMAIMoBAAADACADyAEAAAcAIMkBAAAHACDKAQAABwAgA8gBAAAQACDJAQAAEAAgygEAABAAIAPIAQAAFQAgyQEAABUAIMoBAAAVACADyAEAABkAIMkBAAAZACDKAQAAGQAgAAAAAdABAQAAAAEB0AFAAAAAAQUcAADRAwAgHQAA1AMAIM0BAADSAwAgzgEAANMDACDTAQAAAQAgAxwAANEDACDNAQAA0gMAINMBAAABACAAAAAFHAAAzAMAIB0AAM8DACDNAQAAzQMAIM4BAADOAwAg0wEAAAEAIAMcAADMAwAgzQEAAM0DACDTAQAAAQAgAAAAAAAF0AECAAAAAdYBAgAAAAHXAQIAAAAB2AECAAAAAdkBAgAAAAEFHAAAxAMAIB0AAMoDACDNAQAAxQMAIM4BAADJAwAg0wEAAAEAIAUcAADCAwAgHQAAxwMAIM0BAADDAwAgzgEAAMYDACDTAQAAAQAgAxwAAMQDACDNAQAAxQMAINMBAAABACADHAAAwgMAIM0BAADDAwAg0wEAAAEAIAAAAAHQAQAAALABAgUcAAC9AwAgHQAAwAMAIM0BAAC-AwAgzgEAAL8DACDTAQAACQAgAxwAAL0DACDNAQAAvgMAINMBAAAJACAAAAAAAdABAQAAAAEB0AFAAAAAAQUcAAC3AwAgHQAAuwMAIM0BAAC4AwAgzgEAALoDACDTAQAAAQAgCxwAALkCADAdAAC-AgAwzQEAALoCADDOAQAAuwIAMM8BAAC8AgAg0AEAAL0CADDRAQAAvQIAMNIBAAC9AgAw0wEAAL0CADDUAQAAvwIAMNUBAADAAgAwBJwBAQAAAAGeAUAAAAABqgFAAAAAAbABAAAAsAECAgAAAA0AIBwAAMQCACADAAAADQAgHAAAxAIAIB0AAMMCACABFQAAuQMAMAoGAACKAgAgmAEAAIgCADCZAQAACwAQmgEAAIgCADCcAQEA9QEAIZ4BQAD2AQAhqgFAAPYBACGuAQEA9QEAIbABAACJArABIscBAACHAgAgAgAAAA0AIBUAAMMCACACAAAAwQIAIBUAAMICACAImAEAAMACADCZAQAAwQIAEJoBAADAAgAwnAEBAPUBACGeAUAA9gEAIaoBQAD2AQAhrgEBAPUBACGwAQAAiQKwASIImAEAAMACADCZAQAAwQIAEJoBAADAAgAwnAEBAPUBACGeAUAA9gEAIaoBQAD2AQAhrgEBAPUBACGwAQAAiQKwASIEnAEBAJgCACGeAUAAmQIAIaoBQACZAgAhsAEAAK4CsAEiBJwBAQCYAgAhngFAAJkCACGqAUAAmQIAIbABAACuArABIgScAQEAAAABngFAAAAAAaoBQAAAAAGwAQAAALABAgMcAAC3AwAgzQEAALgDACDTAQAAAQAgBBwAALkCADDNAQAAugIAMM8BAAC8AgAg0wEAAL0CADAAAAAAAAAAAAAB0AEgAAAAAQccAACsAwAgHQAAtQMAIM0BAACtAwAgzgEAALQDACDRAQAAAwAg0gEAAAMAINMBAAABACALHAAAkQMAMB0AAJYDADDNAQAAkgMAMM4BAACTAwAwzwEAAJQDACDQAQAAlQMAMNEBAACVAwAw0gEAAJUDADDTAQAAlQMAMNQBAACXAwAw1QEAAJgDADALHAAAhQMAMB0AAIoDADDNAQAAhgMAMM4BAACHAwAwzwEAAIgDACDQAQAAiQMAMNEBAACJAwAw0gEAAIkDADDTAQAAiQMAMNQBAACLAwAw1QEAAIwDADALHAAA_AIAMB0AAIADADDNAQAA_QIAMM4BAAD-AgAwzwEAAP8CACDQAQAA9AIAMNEBAAD0AgAw0gEAAPQCADDTAQAA9AIAMNQBAACBAwAw1QEAAPcCADALHAAA8AIAMB0AAPUCADDNAQAA8QIAMM4BAADyAgAwzwEAAPMCACDQAQAA9AIAMNEBAAD0AgAw0gEAAPQCADDTAQAA9AIAMNQBAAD2AgAw1QEAAPcCADALHAAA5AIAMB0AAOkCADDNAQAA5QIAMM4BAADmAgAwzwEAAOcCACDQAQAA6AIAMNEBAADoAgAw0gEAAOgCADDTAQAA6AIAMNQBAADqAgAw1QEAAOsCADALHAAA2AIAMB0AAN0CADDNAQAA2QIAMM4BAADaAgAwzwEAANsCACDQAQAA3AIAMNEBAADcAgAw0gEAANwCADDTAQAA3AIAMNQBAADeAgAw1QEAAN8CADADnAEBAAAAAZ0BQAAAAAGeAUAAAAABAgAAABsAIBwAAOMCACADAAAAGwAgHAAA4wIAIB0AAOICACABFQAAswMAMAkFAACBAgAgmAEAAIACADCZAQAAGQAQmgEAAIACADCbAQEA9QEAIZwBAQD1AQAhnQFAAPYBACGeAUAA9gEAIcUBAAD_AQAgAgAAABsAIBUAAOICACACAAAA4AIAIBUAAOECACAHmAEAAN8CADCZAQAA4AIAEJoBAADfAgAwmwEBAPUBACGcAQEA9QEAIZ0BQAD2AQAhngFAAPYBACEHmAEAAN8CADCZAQAA4AIAEJoBAADfAgAwmwEBAPUBACGcAQEA9QEAIZ0BQAD2AQAhngFAAPYBACEDnAEBAJgCACGdAUAAmQIAIZ4BQACZAgAhA5wBAQCYAgAhnQFAAJkCACGeAUAAmQIAIQOcAQEAAAABnQFAAAAAAZ4BQAAAAAECnAEBAAAAAaoBQAAAAAECAAAAFwAgHAAA7wIAIAMAAAAXACAcAADvAgAgHQAA7gIAIAEVAACyAwAwCAUAAIECACCYAQAAgwIAMJkBAAAVABCaAQAAgwIAMJsBAQD1AQAhnAEBAPUBACGqAUAA9gEAIcUBAACCAgAgAgAAABcAIBUAAO4CACACAAAA7AIAIBUAAO0CACAGmAEAAOsCADCZAQAA7AIAEJoBAADrAgAwmwEBAPUBACGcAQEA9QEAIaoBQAD2AQAhBpgBAADrAgAwmQEAAOwCABCaAQAA6wIAMJsBAQD1AQAhnAEBAPUBACGqAUAA9gEAIQKcAQEAmAIAIaoBQACZAgAhApwBAQCYAgAhqgFAAJkCACECnAEBAAAAAaoBQAAAAAEDCgAAqQIAIKsBAQAAAAGtAQIAAAABAgAAABIAIBwAAPsCACADAAAAEgAgHAAA-wIAIB0AAPoCACABFQAAsQMAMAkKAACBAgAgCwAAgQIAIJgBAACFAgAwmQEAABAAEJoBAACFAgAwqwEBAPUBACGsAQEA9QEAIa0BAgCGAgAhxgEAAIQCACACAAAAEgAgFQAA-gIAIAIAAAD4AgAgFQAA-QIAIAaYAQAA9wIAMJkBAAD4AgAQmgEAAPcCADCrAQEA9QEAIawBAQD1AQAhrQECAIYCACEGmAEAAPcCADCZAQAA-AIAEJoBAAD3AgAwqwEBAPUBACGsAQEA9QEAIa0BAgCGAgAhAqsBAQCYAgAhrQECAKYCACEDCgAApwIAIKsBAQCYAgAhrQECAKYCACEDCgAAqQIAIKsBAQAAAAGtAQIAAAABAwsAAKoCACCsAQEAAAABrQECAAAAAQIAAAASACAcAACEAwAgAwAAABIAIBwAAIQDACAdAACDAwAgARUAALADADACAAAAEgAgFQAAgwMAIAIAAAD4AgAgFQAAggMAIAKsAQEAmAIAIa0BAgCmAgAhAwsAAKgCACCsAQEAmAIAIa0BAgCmAgAhAwsAAKoCACCsAQEAAAABrQECAAAAAQsHAADGAgAgngFAAAAAAaoBQAAAAAGxAQEAAAABsgEBAAAAAbMBAQAAAAG0AQEAAAABtQEBAAAAAbYBAQAAAAG3AUAAAAABuAEBAAAAAQIAAAAJACAcAACQAwAgAwAAAAkAIBwAAJADACAdAACPAwAgARUAAK8DADAQBQAAgQIAIAcAAIwCACCYAQAAiwIAMJkBAAAHABCaAQAAiwIAMJsBAQD1AQAhngFAAPYBACGqAUAA9gEAIbEBAQAAAAGyAQEA9QEAIbMBAQD1AQAhtAEBAPUBACG1AQEA9QEAIbYBAQD3AQAhtwFAAPgBACG4AQEA9wEAIQIAAAAJACAVAACPAwAgAgAAAI0DACAVAACOAwAgDpgBAACMAwAwmQEAAI0DABCaAQAAjAMAMJsBAQD1AQAhngFAAPYBACGqAUAA9gEAIbEBAQD1AQAhsgEBAPUBACGzAQEA9QEAIbQBAQD1AQAhtQEBAPUBACG2AQEA9wEAIbcBQAD4AQAhuAEBAPcBACEOmAEAAIwDADCZAQAAjQMAEJoBAACMAwAwmwEBAPUBACGeAUAA9gEAIaoBQAD2AQAhsQEBAPUBACGyAQEA9QEAIbMBAQD1AQAhtAEBAPUBACG1AQEA9QEAIbYBAQD3AQAhtwFAAPgBACG4AQEA9wEAIQqeAUAAmQIAIaoBQACZAgAhsQEBAJgCACGyAQEAmAIAIbMBAQCYAgAhtAEBAJgCACG1AQEAmAIAIbYBAQC1AgAhtwFAALYCACG4AQEAtQIAIQsHAAC4AgAgngFAAJkCACGqAUAAmQIAIbEBAQCYAgAhsgEBAJgCACGzAQEAmAIAIbQBAQCYAgAhtQEBAJgCACG2AQEAtQIAIbcBQAC2AgAhuAEBALUCACELBwAAxgIAIJ4BQAAAAAGqAUAAAAABsQEBAAAAAbIBAQAAAAGzAQEAAAABtAEBAAAAAbUBAQAAAAG2AQEAAAABtwFAAAAAAbgBAQAAAAEUBAAAnQMAIAkAAJ4DACAMAACfAwAgDQAAoAMAIA4AAKEDACAPAACiAwAgngFAAAAAAaoBQAAAAAGxAQEAAAABtAEBAAAAAbUBAQAAAAG3AUAAAAABuAEBAAAAAb4BAQAAAAG_AQEAAAABwAEBAAAAAcEBIAAAAAHCASAAAAABwwFAAAAAAcQBAQAAAAECAAAAAQAgHAAAnAMAIAMAAAABACAcAACcAwAgHQAAmwMAIAEVAACuAwAwGQEAAI8CACAEAACQAgAgCQAAkQIAIAwAAJICACANAACSAgAgDgAAkwIAIA8AAJQCACCYAQAAjQIAMJkBAAADABCaAQAAjQIAMJ4BQAD2AQAhqgFAAPYBACGxAQEAAAABtAEBAPUBACG1AQEA9QEAIbcBQAD4AQAhuAEBAPcBACG9AQEA9wEAIb4BAQD3AQAhvwEBAPcBACHAAQEA9QEAIcEBIACOAgAhwgEgAI4CACHDAUAA-AEAIcQBAQD3AQAhAgAAAAEAIBUAAJsDACACAAAAmQMAIBUAAJoDACASmAEAAJgDADCZAQAAmQMAEJoBAACYAwAwngFAAPYBACGqAUAA9gEAIbEBAQD1AQAhtAEBAPUBACG1AQEA9QEAIbcBQAD4AQAhuAEBAPcBACG9AQEA9wEAIb4BAQD3AQAhvwEBAPcBACHAAQEA9QEAIcEBIACOAgAhwgEgAI4CACHDAUAA-AEAIcQBAQD3AQAhEpgBAACYAwAwmQEAAJkDABCaAQAAmAMAMJ4BQAD2AQAhqgFAAPYBACGxAQEA9QEAIbQBAQD1AQAhtQEBAPUBACG3AUAA-AEAIbgBAQD3AQAhvQEBAPcBACG-AQEA9wEAIb8BAQD3AQAhwAEBAPUBACHBASAAjgIAIcIBIACOAgAhwwFAAPgBACHEAQEA9wEAIQ6eAUAAmQIAIaoBQACZAgAhsQEBAJgCACG0AQEAmAIAIbUBAQCYAgAhtwFAALYCACG4AQEAtQIAIb4BAQC1AgAhvwEBALUCACHAAQEAmAIAIcEBIADQAgAhwgEgANACACHDAUAAtgIAIcQBAQC1AgAhFAQAANICACAJAADTAgAgDAAA1AIAIA0AANUCACAOAADWAgAgDwAA1wIAIJ4BQACZAgAhqgFAAJkCACGxAQEAmAIAIbQBAQCYAgAhtQEBAJgCACG3AUAAtgIAIbgBAQC1AgAhvgEBALUCACG_AQEAtQIAIcABAQCYAgAhwQEgANACACHCASAA0AIAIcMBQAC2AgAhxAEBALUCACEUBAAAnQMAIAkAAJ4DACAMAACfAwAgDQAAoAMAIA4AAKEDACAPAACiAwAgngFAAAAAAaoBQAAAAAGxAQEAAAABtAEBAAAAAbUBAQAAAAG3AUAAAAABuAEBAAAAAb4BAQAAAAG_AQEAAAABwAEBAAAAAcEBIAAAAAHCASAAAAABwwFAAAAAAcQBAQAAAAEEHAAAkQMAMM0BAACSAwAwzwEAAJQDACDTAQAAlQMAMAQcAACFAwAwzQEAAIYDADDPAQAAiAMAINMBAACJAwAwBBwAAPwCADDNAQAA_QIAMM8BAAD_AgAg0wEAAPQCADAEHAAA8AIAMM0BAADxAgAwzwEAAPMCACDTAQAA9AIAMAQcAADkAgAwzQEAAOUCADDPAQAA5wIAINMBAADoAgAwBBwAANgCADDNAQAA2QIAMM8BAADbAgAg0wEAANwCADADHAAArAMAIM0BAACtAwAg0wEAAAEAIA4BAACkAwAgBAAApwMAIAkAAKgDACAMAACpAwAgDQAAqQMAIA4AAKoDACAPAACrAwAgtwEAALECACC4AQAAsQIAIL0BAACxAgAgvgEAALECACC_AQAAsQIAIMMBAACxAgAgxAEAALECACAFBQAApAMAIAcAAKYDACC2AQAAsQIAILcBAACxAgAguAEAALECACAAAAAAAAAVAQAAowMAIAkAAJ4DACAMAACfAwAgDQAAoAMAIA4AAKEDACAPAACiAwAgngFAAAAAAaoBQAAAAAGxAQEAAAABtAEBAAAAAbUBAQAAAAG3AUAAAAABuAEBAAAAAb0BAQAAAAG-AQEAAAABvwEBAAAAAcABAQAAAAHBASAAAAABwgEgAAAAAcMBQAAAAAHEAQEAAAABAgAAAAEAIBwAAKwDACAOngFAAAAAAaoBQAAAAAGxAQEAAAABtAEBAAAAAbUBAQAAAAG3AUAAAAABuAEBAAAAAb4BAQAAAAG_AQEAAAABwAEBAAAAAcEBIAAAAAHCASAAAAABwwFAAAAAAcQBAQAAAAEKngFAAAAAAaoBQAAAAAGxAQEAAAABsgEBAAAAAbMBAQAAAAG0AQEAAAABtQEBAAAAAbYBAQAAAAG3AUAAAAABuAEBAAAAAQKsAQEAAAABrQECAAAAAQKrAQEAAAABrQECAAAAAQKcAQEAAAABqgFAAAAAAQOcAQEAAAABnQFAAAAAAZ4BQAAAAAEDAAAAAwAgHAAArAMAIB0AALYDACAXAAAAAwAgAQAA0QIAIAkAANMCACAMAADUAgAgDQAA1QIAIA4AANYCACAPAADXAgAgFQAAtgMAIJ4BQACZAgAhqgFAAJkCACGxAQEAmAIAIbQBAQCYAgAhtQEBAJgCACG3AUAAtgIAIbgBAQC1AgAhvQEBALUCACG-AQEAtQIAIb8BAQC1AgAhwAEBAJgCACHBASAA0AIAIcIBIADQAgAhwwFAALYCACHEAQEAtQIAIRUBAADRAgAgCQAA0wIAIAwAANQCACANAADVAgAgDgAA1gIAIA8AANcCACCeAUAAmQIAIaoBQACZAgAhsQEBAJgCACG0AQEAmAIAIbUBAQCYAgAhtwFAALYCACG4AQEAtQIAIb0BAQC1AgAhvgEBALUCACG_AQEAtQIAIcABAQCYAgAhwQEgANACACHCASAA0AIAIcMBQAC2AgAhxAEBALUCACEVAQAAowMAIAQAAJ0DACAMAACfAwAgDQAAoAMAIA4AAKEDACAPAACiAwAgngFAAAAAAaoBQAAAAAGxAQEAAAABtAEBAAAAAbUBAQAAAAG3AUAAAAABuAEBAAAAAb0BAQAAAAG-AQEAAAABvwEBAAAAAcABAQAAAAHBASAAAAABwgEgAAAAAcMBQAAAAAHEAQEAAAABAgAAAAEAIBwAALcDACAEnAEBAAAAAZ4BQAAAAAGqAUAAAAABsAEAAACwAQIDAAAAAwAgHAAAtwMAIB0AALwDACAXAAAAAwAgAQAA0QIAIAQAANICACAMAADUAgAgDQAA1QIAIA4AANYCACAPAADXAgAgFQAAvAMAIJ4BQACZAgAhqgFAAJkCACGxAQEAmAIAIbQBAQCYAgAhtQEBAJgCACG3AUAAtgIAIbgBAQC1AgAhvQEBALUCACG-AQEAtQIAIb8BAQC1AgAhwAEBAJgCACHBASAA0AIAIcIBIADQAgAhwwFAALYCACHEAQEAtQIAIRUBAADRAgAgBAAA0gIAIAwAANQCACANAADVAgAgDgAA1gIAIA8AANcCACCeAUAAmQIAIaoBQACZAgAhsQEBAJgCACG0AQEAmAIAIbUBAQCYAgAhtwFAALYCACG4AQEAtQIAIb0BAQC1AgAhvgEBALUCACG_AQEAtQIAIcABAQCYAgAhwQEgANACACHCASAA0AIAIcMBQAC2AgAhxAEBALUCACEMBQAAxQIAIJsBAQAAAAGeAUAAAAABqgFAAAAAAbEBAQAAAAGyAQEAAAABswEBAAAAAbQBAQAAAAG1AQEAAAABtgEBAAAAAbcBQAAAAAG4AQEAAAABAgAAAAkAIBwAAL0DACADAAAABwAgHAAAvQMAIB0AAMEDACAOAAAABwAgBQAAtwIAIBUAAMEDACCbAQEAmAIAIZ4BQACZAgAhqgFAAJkCACGxAQEAmAIAIbIBAQCYAgAhswEBAJgCACG0AQEAmAIAIbUBAQCYAgAhtgEBALUCACG3AUAAtgIAIbgBAQC1AgAhDAUAALcCACCbAQEAmAIAIZ4BQACZAgAhqgFAAJkCACGxAQEAmAIAIbIBAQCYAgAhswEBAJgCACG0AQEAmAIAIbUBAQCYAgAhtgEBALUCACG3AUAAtgIAIbgBAQC1AgAhFQEAAKMDACAEAACdAwAgCQAAngMAIAwAAJ8DACAOAAChAwAgDwAAogMAIJ4BQAAAAAGqAUAAAAABsQEBAAAAAbQBAQAAAAG1AQEAAAABtwFAAAAAAbgBAQAAAAG9AQEAAAABvgEBAAAAAb8BAQAAAAHAAQEAAAABwQEgAAAAAcIBIAAAAAHDAUAAAAABxAEBAAAAAQIAAAABACAcAADCAwAgFQEAAKMDACAEAACdAwAgCQAAngMAIA0AAKADACAOAAChAwAgDwAAogMAIJ4BQAAAAAGqAUAAAAABsQEBAAAAAbQBAQAAAAG1AQEAAAABtwFAAAAAAbgBAQAAAAG9AQEAAAABvgEBAAAAAb8BAQAAAAHAAQEAAAABwQEgAAAAAcIBIAAAAAHDAUAAAAABxAEBAAAAAQIAAAABACAcAADEAwAgAwAAAAMAIBwAAMIDACAdAADIAwAgFwAAAAMAIAEAANECACAEAADSAgAgCQAA0wIAIAwAANQCACAOAADWAgAgDwAA1wIAIBUAAMgDACCeAUAAmQIAIaoBQACZAgAhsQEBAJgCACG0AQEAmAIAIbUBAQCYAgAhtwFAALYCACG4AQEAtQIAIb0BAQC1AgAhvgEBALUCACG_AQEAtQIAIcABAQCYAgAhwQEgANACACHCASAA0AIAIcMBQAC2AgAhxAEBALUCACEVAQAA0QIAIAQAANICACAJAADTAgAgDAAA1AIAIA4AANYCACAPAADXAgAgngFAAJkCACGqAUAAmQIAIbEBAQCYAgAhtAEBAJgCACG1AQEAmAIAIbcBQAC2AgAhuAEBALUCACG9AQEAtQIAIb4BAQC1AgAhvwEBALUCACHAAQEAmAIAIcEBIADQAgAhwgEgANACACHDAUAAtgIAIcQBAQC1AgAhAwAAAAMAIBwAAMQDACAdAADLAwAgFwAAAAMAIAEAANECACAEAADSAgAgCQAA0wIAIA0AANUCACAOAADWAgAgDwAA1wIAIBUAAMsDACCeAUAAmQIAIaoBQACZAgAhsQEBAJgCACG0AQEAmAIAIbUBAQCYAgAhtwFAALYCACG4AQEAtQIAIb0BAQC1AgAhvgEBALUCACG_AQEAtQIAIcABAQCYAgAhwQEgANACACHCASAA0AIAIcMBQAC2AgAhxAEBALUCACEVAQAA0QIAIAQAANICACAJAADTAgAgDQAA1QIAIA4AANYCACAPAADXAgAgngFAAJkCACGqAUAAmQIAIbEBAQCYAgAhtAEBAJgCACG1AQEAmAIAIbcBQAC2AgAhuAEBALUCACG9AQEAtQIAIb4BAQC1AgAhvwEBALUCACHAAQEAmAIAIcEBIADQAgAhwgEgANACACHDAUAAtgIAIcQBAQC1AgAhFQEAAKMDACAEAACdAwAgCQAAngMAIAwAAJ8DACANAACgAwAgDwAAogMAIJ4BQAAAAAGqAUAAAAABsQEBAAAAAbQBAQAAAAG1AQEAAAABtwFAAAAAAbgBAQAAAAG9AQEAAAABvgEBAAAAAb8BAQAAAAHAAQEAAAABwQEgAAAAAcIBIAAAAAHDAUAAAAABxAEBAAAAAQIAAAABACAcAADMAwAgAwAAAAMAIBwAAMwDACAdAADQAwAgFwAAAAMAIAEAANECACAEAADSAgAgCQAA0wIAIAwAANQCACANAADVAgAgDwAA1wIAIBUAANADACCeAUAAmQIAIaoBQACZAgAhsQEBAJgCACG0AQEAmAIAIbUBAQCYAgAhtwFAALYCACG4AQEAtQIAIb0BAQC1AgAhvgEBALUCACG_AQEAtQIAIcABAQCYAgAhwQEgANACACHCASAA0AIAIcMBQAC2AgAhxAEBALUCACEVAQAA0QIAIAQAANICACAJAADTAgAgDAAA1AIAIA0AANUCACAPAADXAgAgngFAAJkCACGqAUAAmQIAIbEBAQCYAgAhtAEBAJgCACG1AQEAmAIAIbcBQAC2AgAhuAEBALUCACG9AQEAtQIAIb4BAQC1AgAhvwEBALUCACHAAQEAmAIAIcEBIADQAgAhwgEgANACACHDAUAAtgIAIcQBAQC1AgAhFQEAAKMDACAEAACdAwAgCQAAngMAIAwAAJ8DACANAACgAwAgDgAAoQMAIJ4BQAAAAAGqAUAAAAABsQEBAAAAAbQBAQAAAAG1AQEAAAABtwFAAAAAAbgBAQAAAAG9AQEAAAABvgEBAAAAAb8BAQAAAAHAAQEAAAABwQEgAAAAAcIBIAAAAAHDAUAAAAABxAEBAAAAAQIAAAABACAcAADRAwAgAwAAAAMAIBwAANEDACAdAADVAwAgFwAAAAMAIAEAANECACAEAADSAgAgCQAA0wIAIAwAANQCACANAADVAgAgDgAA1gIAIBUAANUDACCeAUAAmQIAIaoBQACZAgAhsQEBAJgCACG0AQEAmAIAIbUBAQCYAgAhtwFAALYCACG4AQEAtQIAIb0BAQC1AgAhvgEBALUCACG_AQEAtQIAIcABAQCYAgAhwQEgANACACHCASAA0AIAIcMBQAC2AgAhxAEBALUCACEVAQAA0QIAIAQAANICACAJAADTAgAgDAAA1AIAIA0AANUCACAOAADWAgAgngFAAJkCACGqAUAAmQIAIbEBAQCYAgAhtAEBAJgCACG1AQEAmAIAIbcBQAC2AgAhuAEBALUCACG9AQEAtQIAIb4BAQC1AgAhvwEBALUCACHAAQEAmAIAIcEBIADQAgAhwgEgANACACHDAUAAtgIAIcQBAQC1AgAhCAEEAQQGAQgACAkKAgwTBQ0UBQ4YBg8cBwMFAAEHDgMIAAQBBgACAQcPAAIKAAELAAEBBQABAQUAAQYEHQAJHgAMHwANIAAOIQAPIgAAAQEsAQEBMgEDCAANIgAOIwAPAAAAAwgADSIADiMADwAAAAMIABUiABYjABcAAAADCAAVIgAWIwAXAAAAAwgAHSIAHiMAHwAAAAMIAB0iAB4jAB8BBQABAQUAAQMIACQiACUjACYAAAADCAAkIgAlIwAmAQYAAgEGAAIDCAArIgAsIwAtAAAAAwgAKyIALCMALQIKAAELAAECCgABCwABBQgAMiIANSMANnQAM3UANAAAAAAABQgAMiIANSMANnQAM3UANAEFAAEBBQABAwgAOyIAPCMAPQAAAAMIADsiADwjAD0BBQABAQUAAQMIAEIiAEMjAEQAAAADCABCIgBDIwBEEAIBESMBEiQBEyUBFCYBFigBFyoJGCsKGS4BGjAJGzELHjMBHzQBIDUJJDgMJTkQJjsRJzwRKD8RKUARKkERK0MRLEUJLUYSLkgRL0oJMEsTMUwRMk0RM04JNFEUNVIYNlQZN1UZOFgZOVkZOloZO1wZPF4JPV8aPmEZP2MJQGQbQWUZQmYZQ2cJRGocRWsgRmwCR20CSG4CSW8CSnACS3ICTHQJTXUhTncCT3kJUHoiUXsCUnwCU30JVIABI1WBASdWggEDV4MBA1iEAQNZhQEDWoYBA1uIAQNcigEJXYsBKF6NAQNfjwEJYJABKWGRAQNikgEDY5MBCWSWASpllwEuZpgBBWeZAQVomgEFaZsBBWqcAQVrngEFbKABCW2hAS9uowEFb6UBCXCmATBxpwEFcqgBBXOpAQl2rAExd60BN3iuAQZ5rwEGerABBnuxAQZ8sgEGfbQBBn62AQl_twE4gAG5AQaBAbsBCYIBvAE5gwG9AQaEAb4BBoUBvwEJhgHCATqHAcMBPogBxAEHiQHFAQeKAcYBB4sBxwEHjAHIAQeNAcoBB44BzAEJjwHNAT-QAc8BB5EB0QEJkgHSAUCTAdMBB5QB1AEHlQHVAQmWAdgBQZcB2QFF" + strings: JSON.parse("[\"where\",\"parentTopic\",\"orderBy\",\"cursor\",\"childTopics\",\"topic\",\"post\",\"reactions\",\"_count\",\"posts\",\"ancestorTopic\",\"descendantTopic\",\"ancestorClosures\",\"descendantClosures\",\"watches\",\"readStates\",\"Topic.findUnique\",\"Topic.findUniqueOrThrow\",\"Topic.findFirst\",\"Topic.findFirstOrThrow\",\"Topic.findMany\",\"data\",\"Topic.createOne\",\"Topic.createMany\",\"Topic.createManyAndReturn\",\"Topic.updateOne\",\"Topic.updateMany\",\"Topic.updateManyAndReturn\",\"create\",\"update\",\"Topic.upsertOne\",\"Topic.deleteOne\",\"Topic.deleteMany\",\"having\",\"_min\",\"_max\",\"Topic.groupBy\",\"Topic.aggregate\",\"MemberBan.findUnique\",\"MemberBan.findUniqueOrThrow\",\"MemberBan.findFirst\",\"MemberBan.findFirstOrThrow\",\"MemberBan.findMany\",\"MemberBan.createOne\",\"MemberBan.createMany\",\"MemberBan.createManyAndReturn\",\"MemberBan.updateOne\",\"MemberBan.updateMany\",\"MemberBan.updateManyAndReturn\",\"MemberBan.upsertOne\",\"MemberBan.deleteOne\",\"MemberBan.deleteMany\",\"MemberBan.groupBy\",\"MemberBan.aggregate\",\"IpBan.findUnique\",\"IpBan.findUniqueOrThrow\",\"IpBan.findFirst\",\"IpBan.findFirstOrThrow\",\"IpBan.findMany\",\"IpBan.createOne\",\"IpBan.createMany\",\"IpBan.createManyAndReturn\",\"IpBan.updateOne\",\"IpBan.updateMany\",\"IpBan.updateManyAndReturn\",\"IpBan.upsertOne\",\"IpBan.deleteOne\",\"IpBan.deleteMany\",\"IpBan.groupBy\",\"IpBan.aggregate\",\"Post.findUnique\",\"Post.findUniqueOrThrow\",\"Post.findFirst\",\"Post.findFirstOrThrow\",\"Post.findMany\",\"Post.createOne\",\"Post.createMany\",\"Post.createManyAndReturn\",\"Post.updateOne\",\"Post.updateMany\",\"Post.updateManyAndReturn\",\"Post.upsertOne\",\"Post.deleteOne\",\"Post.deleteMany\",\"Post.groupBy\",\"Post.aggregate\",\"PostReaction.findUnique\",\"PostReaction.findUniqueOrThrow\",\"PostReaction.findFirst\",\"PostReaction.findFirstOrThrow\",\"PostReaction.findMany\",\"PostReaction.createOne\",\"PostReaction.createMany\",\"PostReaction.createManyAndReturn\",\"PostReaction.updateOne\",\"PostReaction.updateMany\",\"PostReaction.updateManyAndReturn\",\"PostReaction.upsertOne\",\"PostReaction.deleteOne\",\"PostReaction.deleteMany\",\"PostReaction.groupBy\",\"PostReaction.aggregate\",\"TopicClosure.findUnique\",\"TopicClosure.findUniqueOrThrow\",\"TopicClosure.findFirst\",\"TopicClosure.findFirstOrThrow\",\"TopicClosure.findMany\",\"TopicClosure.createOne\",\"TopicClosure.createMany\",\"TopicClosure.createManyAndReturn\",\"TopicClosure.updateOne\",\"TopicClosure.updateMany\",\"TopicClosure.updateManyAndReturn\",\"TopicClosure.upsertOne\",\"TopicClosure.deleteOne\",\"TopicClosure.deleteMany\",\"_avg\",\"_sum\",\"TopicClosure.groupBy\",\"TopicClosure.aggregate\",\"TopicWatch.findUnique\",\"TopicWatch.findUniqueOrThrow\",\"TopicWatch.findFirst\",\"TopicWatch.findFirstOrThrow\",\"TopicWatch.findMany\",\"TopicWatch.createOne\",\"TopicWatch.createMany\",\"TopicWatch.createManyAndReturn\",\"TopicWatch.updateOne\",\"TopicWatch.updateMany\",\"TopicWatch.updateManyAndReturn\",\"TopicWatch.upsertOne\",\"TopicWatch.deleteOne\",\"TopicWatch.deleteMany\",\"TopicWatch.groupBy\",\"TopicWatch.aggregate\",\"TopicReadState.findUnique\",\"TopicReadState.findUniqueOrThrow\",\"TopicReadState.findFirst\",\"TopicReadState.findFirstOrThrow\",\"TopicReadState.findMany\",\"TopicReadState.createOne\",\"TopicReadState.createMany\",\"TopicReadState.createManyAndReturn\",\"TopicReadState.updateOne\",\"TopicReadState.updateMany\",\"TopicReadState.updateManyAndReturn\",\"TopicReadState.upsertOne\",\"TopicReadState.deleteOne\",\"TopicReadState.deleteMany\",\"TopicReadState.groupBy\",\"TopicReadState.aggregate\",\"PublicForumCategory.findUnique\",\"PublicForumCategory.findUniqueOrThrow\",\"PublicForumCategory.findFirst\",\"PublicForumCategory.findFirstOrThrow\",\"PublicForumCategory.findMany\",\"PublicForumCategory.createOne\",\"PublicForumCategory.createMany\",\"PublicForumCategory.createManyAndReturn\",\"PublicForumCategory.updateOne\",\"PublicForumCategory.updateMany\",\"PublicForumCategory.updateManyAndReturn\",\"PublicForumCategory.upsertOne\",\"PublicForumCategory.deleteOne\",\"PublicForumCategory.deleteMany\",\"PublicForumCategory.groupBy\",\"PublicForumCategory.aggregate\",\"PublicForumRoleMember.findUnique\",\"PublicForumRoleMember.findUniqueOrThrow\",\"PublicForumRoleMember.findFirst\",\"PublicForumRoleMember.findFirstOrThrow\",\"PublicForumRoleMember.findMany\",\"PublicForumRoleMember.createOne\",\"PublicForumRoleMember.createMany\",\"PublicForumRoleMember.createManyAndReturn\",\"PublicForumRoleMember.updateOne\",\"PublicForumRoleMember.updateMany\",\"PublicForumRoleMember.updateManyAndReturn\",\"PublicForumRoleMember.upsertOne\",\"PublicForumRoleMember.deleteOne\",\"PublicForumRoleMember.deleteMany\",\"PublicForumRoleMember.groupBy\",\"PublicForumRoleMember.aggregate\",\"PublicForumImportRecord.findUnique\",\"PublicForumImportRecord.findUniqueOrThrow\",\"PublicForumImportRecord.findFirst\",\"PublicForumImportRecord.findFirstOrThrow\",\"PublicForumImportRecord.findMany\",\"PublicForumImportRecord.createOne\",\"PublicForumImportRecord.createMany\",\"PublicForumImportRecord.createManyAndReturn\",\"PublicForumImportRecord.updateOne\",\"PublicForumImportRecord.updateMany\",\"PublicForumImportRecord.updateManyAndReturn\",\"PublicForumImportRecord.upsertOne\",\"PublicForumImportRecord.deleteOne\",\"PublicForumImportRecord.deleteMany\",\"PublicForumImportRecord.groupBy\",\"PublicForumImportRecord.aggregate\",\"AND\",\"OR\",\"NOT\",\"entityType\",\"sourceId\",\"targetId\",\"sourceHash\",\"targetHash\",\"createdAt\",\"equals\",\"in\",\"notIn\",\"lt\",\"lte\",\"gt\",\"gte\",\"not\",\"contains\",\"startsWith\",\"endsWith\",\"entityType_targetId\",\"entityType_sourceId\",\"roleName\",\"memberId\",\"roleName_memberId\",\"topicId\",\"description\",\"displayAs\",\"sortOrder\",\"readRoles\",\"createRoles\",\"replyRoles\",\"legacyId\",\"legacySlug\",\"source\",\"string_contains\",\"string_starts_with\",\"string_ends_with\",\"array_starts_with\",\"array_ends_with\",\"array_contains\",\"has\",\"hasEvery\",\"hasSome\",\"lastReadAt\",\"updatedAt\",\"ancestorTopicId\",\"descendantTopicId\",\"depth\",\"postId\",\"PostReactionType\",\"reaction\",\"id\",\"parentType\",\"parentId\",\"authorMemberId\",\"authorHandle\",\"content\",\"deletedAt\",\"deletedByMemberId\",\"ipAddress\",\"createdByMemberId\",\"removedAt\",\"removedByMemberId\",\"parentTopicId\",\"challengeId\",\"title\",\"isAnnouncement\",\"locked\",\"lockedAt\",\"lockedByMemberId\",\"topicId_memberId\",\"ancestorTopicId_descendantTopicId\",\"postId_memberId\",\"every\",\"some\",\"none\",\"is\",\"isNot\",\"connectOrCreate\",\"upsert\",\"createMany\",\"set\",\"disconnect\",\"delete\",\"connect\",\"updateMany\",\"deleteMany\",\"push\",\"increment\",\"decrement\",\"multiply\",\"divide\"]"), + graph: "vgRfsAEZAQAA5wIAIAQAAOgCACAJAADpAgAgDAAA6gIAIA0AAOoCACAOAADrAgAgDwAA7AIAIMgBAADlAgAwyQEAAAMAEMoBAADlAgAw0AFAAK8CACHeAQEA0AIAIfUBQACvAgAh_AEBAAAAAf8BAQCuAgAhgAIBAK4CACGCAkAA0QIAIYMCAQDQAgAhiAIBANACACGJAgEA0AIAIYoCAQCuAgAhiwIgAOYCACGMAiAA5gIAIY0CQADRAgAhjgIBANACACEBAAAAAQAgGQEAAOcCACAEAADoAgAgCQAA6QIAIAwAAOoCACANAADqAgAgDgAA6wIAIA8AAOwCACDIAQAA5QIAMMkBAAADABDKAQAA5QIAMNABQACvAgAh3gEBANACACH1AUAArwIAIfwBAQCuAgAh_wEBAK4CACGAAgEArgIAIYICQADRAgAhgwIBANACACGIAgEA0AIAIYkCAQDQAgAhigIBAK4CACGLAiAA5gIAIYwCIADmAgAhjQJAANECACGOAgEA0AIAIQEAAAADACAOAQAAjQQAIAQAAJAEACAJAACRBAAgDAAAkgQAIA0AAJIEACAOAACTBAAgDwAAlAQAIN4BAACaAwAgggIAAJoDACCDAgAAmgMAIIgCAACaAwAgiQIAAJoDACCNAgAAmgMAII4CAACaAwAgAwAAAAMAIAIAAAUAMAMAAAEAIBAFAADaAgAgBwAA5AIAIMgBAADjAgAwyQEAAAcAEMoBAADjAgAw0AFAAK8CACHhAQEArgIAIfUBQACvAgAh_AEBAK4CACH9AQEArgIAIf4BAQCuAgAh_wEBAK4CACGAAgEArgIAIYECAQDQAgAhggJAANECACGDAgEA0AIAIQUFAACNBAAgBwAAjwQAIIECAACaAwAgggIAAJoDACCDAgAAmgMAIBAFAADaAgAgBwAA5AIAIMgBAADjAgAwyQEAAAcAEMoBAADjAgAw0AFAAK8CACHhAQEArgIAIfUBQACvAgAh_AEBAAAAAf0BAQCuAgAh_gEBAK4CACH_AQEArgIAIYACAQCuAgAhgQIBANACACGCAkAA0QIAIYMCAQDQAgAhAwAAAAcAIAIAAAgAMAMAAAkAIAkGAADiAgAgyAEAAOACADDJAQAACwAQygEAAOACADDQAUAArwIAId8BAQCuAgAh9QFAAK8CACH5AQEArgIAIfsBAADhAvsBIgEGAACOBAAgCgYAAOICACDIAQAA4AIAMMkBAAALABDKAQAA4AIAMNABQACvAgAh3wEBAK4CACH1AUAArwIAIfkBAQCuAgAh-wEAAOEC-wEikQIAAN8CACADAAAACwAgAgAADAAwAwAADQAgAQAAAAsAIAgKAADaAgAgCwAA2gIAIMgBAADeAgAwyQEAABAAEMoBAADeAgAw9gEBAK4CACH3AQEArgIAIfgBAgC9AgAhAgoAAI0EACALAACNBAAgCQoAANoCACALAADaAgAgyAEAAN4CADDJAQAAEAAQygEAAN4CADD2AQEArgIAIfcBAQCuAgAh-AECAL0CACGQAgAA3QIAIAMAAAAQACACAAARADADAAASACADAAAAEAAgAgAAEQAwAwAAEgAgBwUAANoCACDIAQAA3AIAMMkBAAAVABDKAQAA3AIAMNABQACvAgAh3wEBAK4CACHhAQEArgIAIQEFAACNBAAgCAUAANoCACDIAQAA3AIAMMkBAAAVABDKAQAA3AIAMNABQACvAgAh3wEBAK4CACHhAQEArgIAIY8CAADbAgAgAwAAABUAIAIAABYAMAMAABcAIAgFAADaAgAgyAEAANkCADDJAQAAGQAQygEAANkCADDfAQEArgIAIeEBAQCuAgAh9AFAAK8CACH1AUAArwIAIQEFAACNBAAgCQUAANoCACDIAQAA2QIAMMkBAAAZABDKAQAA2QIAMN8BAQCuAgAh4QEBAK4CACH0AUAArwIAIfUBQACvAgAhjwIAANgCACADAAAAGQAgAgAAGgAwAwAAGwAgAQAAAAMAIAEAAAAHACABAAAAEAAgAQAAABAAIAEAAAAVACABAAAAGQAgAQAAAAEAIAMAAAADACACAAAFADADAAABACADAAAAAwAgAgAABQAwAwAAAQAgAwAAAAMAIAIAAAUAMAMAAAEAIBYBAACMBAAgBAAAhgQAIAkAAIcEACAMAACIBAAgDQAAiQQAIA4AAIoEACAPAACLBAAg0AFAAAAAAd4BAQAAAAH1AUAAAAAB_AEBAAAAAf8BAQAAAAGAAgEAAAABggJAAAAAAYMCAQAAAAGIAgEAAAABiQIBAAAAAYoCAQAAAAGLAiAAAAABjAIgAAAAAY0CQAAAAAGOAgEAAAABARUAACcAIA_QAUAAAAAB3gEBAAAAAfUBQAAAAAH8AQEAAAAB_wEBAAAAAYACAQAAAAGCAkAAAAABgwIBAAAAAYgCAQAAAAGJAgEAAAABigIBAAAAAYsCIAAAAAGMAiAAAAABjQJAAAAAAY4CAQAAAAEBFQAAKQAwARUAACkAMAEAAAADACAWAQAAugMAIAQAALsDACAJAAC8AwAgDAAAvQMAIA0AAL4DACAOAAC_AwAgDwAAwAMAINABQADxAgAh3gEBAJ4DACH1AUAA8QIAIfwBAQDwAgAh_wEBAPACACGAAgEA8AIAIYICQACfAwAhgwIBAJ4DACGIAgEAngMAIYkCAQCeAwAhigIBAPACACGLAiAAuQMAIYwCIAC5AwAhjQJAAJ8DACGOAgEAngMAIQIAAAABACAVAAAtACAP0AFAAPECACHeAQEAngMAIfUBQADxAgAh_AEBAPACACH_AQEA8AIAIYACAQDwAgAhggJAAJ8DACGDAgEAngMAIYgCAQCeAwAhiQIBAJ4DACGKAgEA8AIAIYsCIAC5AwAhjAIgALkDACGNAkAAnwMAIY4CAQCeAwAhAgAAAAMAIBUAAC8AIAIAAAADACAVAAAvACABAAAAAwAgAwAAAAEAIBwAACcAIB0AAC0AIAEAAAABACABAAAAAwAgCggAALYDACAiAAC4AwAgIwAAtwMAIN4BAACaAwAgggIAAJoDACCDAgAAmgMAIIgCAACaAwAgiQIAAJoDACCNAgAAmgMAII4CAACaAwAgEsgBAADUAgAwyQEAADcAEMoBAADUAgAw0AFAAKcCACHeAQEAxwIAIfUBQACnAgAh_AEBAKYCACH_AQEApgIAIYACAQCmAgAhggJAAMgCACGDAgEAxwIAIYgCAQDHAgAhiQIBAMcCACGKAgEApgIAIYsCIADVAgAhjAIgANUCACGNAkAAyAIAIY4CAQDHAgAhAwAAAAMAIAIAADYAMCEAADcAIAMAAAADACACAAAFADADAAABACAJyAEAANMCADDJAQAAPQAQygEAANMCADDQAUAArwIAId8BAQCuAgAh_AEBAAAAAYUCAQDQAgAhhgJAANECACGHAgEA0AIAIQEAAAA6ACABAAAAOgAgCcgBAADTAgAwyQEAAD0AEMoBAADTAgAw0AFAAK8CACHfAQEArgIAIfwBAQCuAgAhhQIBANACACGGAkAA0QIAIYcCAQDQAgAhA4UCAACaAwAghgIAAJoDACCHAgAAmgMAIAMAAAA9ACACAAA-ADADAAA6ACADAAAAPQAgAgAAPgAwAwAAOgAgAwAAAD0AIAIAAD4AMAMAADoAIAbQAUAAAAAB3wEBAAAAAfwBAQAAAAGFAgEAAAABhgJAAAAAAYcCAQAAAAEBFQAAQgAgBtABQAAAAAHfAQEAAAAB_AEBAAAAAYUCAQAAAAGGAkAAAAABhwIBAAAAAQEVAABEADABFQAARAAwBtABQADxAgAh3wEBAPACACH8AQEA8AIAIYUCAQCeAwAhhgJAAJ8DACGHAgEAngMAIQIAAAA6ACAVAABHACAG0AFAAPECACHfAQEA8AIAIfwBAQDwAgAhhQIBAJ4DACGGAkAAnwMAIYcCAQCeAwAhAgAAAD0AIBUAAEkAIAIAAAA9ACAVAABJACADAAAAOgAgHAAAQgAgHQAARwAgAQAAADoAIAEAAAA9ACAGCAAAswMAICIAALUDACAjAAC0AwAghQIAAJoDACCGAgAAmgMAIIcCAACaAwAgCcgBAADSAgAwyQEAAFAAEMoBAADSAgAw0AFAAKcCACHfAQEApgIAIfwBAQCmAgAhhQIBAMcCACGGAkAAyAIAIYcCAQDHAgAhAwAAAD0AIAIAAE8AMCEAAFAAIAMAAAA9ACACAAA-ADADAAA6ACAJyAEAAM8CADDJAQAAVgAQygEAAM8CADDQAUAArwIAIfwBAQAAAAGEAgEArgIAIYUCAQDQAgAhhgJAANECACGHAgEA0AIAIQEAAABTACABAAAAUwAgCcgBAADPAgAwyQEAAFYAEMoBAADPAgAw0AFAAK8CACH8AQEArgIAIYQCAQCuAgAhhQIBANACACGGAkAA0QIAIYcCAQDQAgAhA4UCAACaAwAghgIAAJoDACCHAgAAmgMAIAMAAABWACACAABXADADAABTACADAAAAVgAgAgAAVwAwAwAAUwAgAwAAAFYAIAIAAFcAMAMAAFMAIAbQAUAAAAAB_AEBAAAAAYQCAQAAAAGFAgEAAAABhgJAAAAAAYcCAQAAAAEBFQAAWwAgBtABQAAAAAH8AQEAAAABhAIBAAAAAYUCAQAAAAGGAkAAAAABhwIBAAAAAQEVAABdADABFQAAXQAwBtABQADxAgAh_AEBAPACACGEAgEA8AIAIYUCAQCeAwAhhgJAAJ8DACGHAgEAngMAIQIAAABTACAVAABgACAG0AFAAPECACH8AQEA8AIAIYQCAQDwAgAhhQIBAJ4DACGGAkAAnwMAIYcCAQCeAwAhAgAAAFYAIBUAAGIAIAIAAABWACAVAABiACADAAAAUwAgHAAAWwAgHQAAYAAgAQAAAFMAIAEAAABWACAGCAAAsAMAICIAALIDACAjAACxAwAghQIAAJoDACCGAgAAmgMAIIcCAACaAwAgCcgBAADOAgAwyQEAAGkAEMoBAADOAgAw0AFAAKcCACH8AQEApgIAIYQCAQCmAgAhhQIBAMcCACGGAkAAyAIAIYcCAQDHAgAhAwAAAFYAIAIAAGgAMCEAAGkAIAMAAABWACACAABXADADAABTACABAAAACQAgAQAAAAkAIAMAAAAHACACAAAIADADAAAJACADAAAABwAgAgAACAAwAwAACQAgAwAAAAcAIAIAAAgAMAMAAAkAIA0FAACuAwAgBwAArwMAINABQAAAAAHhAQEAAAAB9QFAAAAAAfwBAQAAAAH9AQEAAAAB_gEBAAAAAf8BAQAAAAGAAgEAAAABgQIBAAAAAYICQAAAAAGDAgEAAAABARUAAHEAIAvQAUAAAAAB4QEBAAAAAfUBQAAAAAH8AQEAAAAB_QEBAAAAAf4BAQAAAAH_AQEAAAABgAIBAAAAAYECAQAAAAGCAkAAAAABgwIBAAAAAQEVAABzADABFQAAcwAwDQUAAKADACAHAAChAwAg0AFAAPECACHhAQEA8AIAIfUBQADxAgAh_AEBAPACACH9AQEA8AIAIf4BAQDwAgAh_wEBAPACACGAAgEA8AIAIYECAQCeAwAhggJAAJ8DACGDAgEAngMAIQIAAAAJACAVAAB2ACAL0AFAAPECACHhAQEA8AIAIfUBQADxAgAh_AEBAPACACH9AQEA8AIAIf4BAQDwAgAh_wEBAPACACGAAgEA8AIAIYECAQCeAwAhggJAAJ8DACGDAgEAngMAIQIAAAAHACAVAAB4ACACAAAABwAgFQAAeAAgAwAAAAkAIBwAAHEAIB0AAHYAIAEAAAAJACABAAAABwAgBggAAJsDACAiAACdAwAgIwAAnAMAIIECAACaAwAgggIAAJoDACCDAgAAmgMAIA7IAQAAxgIAMMkBAAB_ABDKAQAAxgIAMNABQACnAgAh4QEBAKYCACH1AUAApwIAIfwBAQCmAgAh_QEBAKYCACH-AQEApgIAIf8BAQCmAgAhgAIBAKYCACGBAgEAxwIAIYICQADIAgAhgwIBAMcCACEDAAAABwAgAgAAfgAwIQAAfwAgAwAAAAcAIAIAAAgAMAMAAAkAIAEAAAANACABAAAADQAgAwAAAAsAIAIAAAwAMAMAAA0AIAMAAAALACACAAAMADADAAANACADAAAACwAgAgAADAAwAwAADQAgBgYAAJkDACDQAUAAAAAB3wEBAAAAAfUBQAAAAAH5AQEAAAAB-wEAAAD7AQIBFQAAhwEAIAXQAUAAAAAB3wEBAAAAAfUBQAAAAAH5AQEAAAAB-wEAAAD7AQIBFQAAiQEAMAEVAACJAQAwBgYAAJgDACDQAUAA8QIAId8BAQDwAgAh9QFAAPECACH5AQEA8AIAIfsBAACXA_sBIgIAAAANACAVAACMAQAgBdABQADxAgAh3wEBAPACACH1AUAA8QIAIfkBAQDwAgAh-wEAAJcD-wEiAgAAAAsAIBUAAI4BACACAAAACwAgFQAAjgEAIAMAAAANACAcAACHAQAgHQAAjAEAIAEAAAANACABAAAACwAgAwgAAJQDACAiAACWAwAgIwAAlQMAIAjIAQAAwgIAMMkBAACVAQAQygEAAMICADDQAUAApwIAId8BAQCmAgAh9QFAAKcCACH5AQEApgIAIfsBAADDAvsBIgMAAAALACACAACUAQAwIQAAlQEAIAMAAAALACACAAAMADADAAANACABAAAAEgAgAQAAABIAIAMAAAAQACACAAARADADAAASACADAAAAEAAgAgAAEQAwAwAAEgAgAwAAABAAIAIAABEAMAMAABIAIAUKAACSAwAgCwAAkwMAIPYBAQAAAAH3AQEAAAAB-AECAAAAAQEVAACdAQAgA_YBAQAAAAH3AQEAAAAB-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_gIAIOYBAAD_AgAg5wEAAIADACDoAQIAAAAB6QEBAAAAAeoBgAAAAAEBFQAA5AEAMAEVAADkAQAwCuEBAQDwAgAh4gEBAPACACHjAQEA8AIAIeQBAgD6AgAh5QEAAPsCACDmAQAA_AIAIOcBAAD9AgAg6AECAPoCACHpAQEA8AIAIeoBgAAAAAECAAAA2gEAIBUAAOcBACAK4QEBAPACACHiAQEA8AIAIeMBAQDwAgAh5AECAPoCACHlAQAA-wIAIOYBAAD8AgAg5wEAAP0CACDoAQIA-gIAIekBAQDwAgAh6gGAAAAAAQIAAADdAQAgFQAA6QEAIAIAAADdAQAgFQAA6QEAIAMAAADaAQAgHAAA4gEAIB0AAOcBACABAAAA2gEAIAEAAADdAQAgBQgAAPUCACAiAAD4AgAgIwAA9wIAIHQAAPYCACB1AAD5AgAgDcgBAAC1AgAwyQEAAPABABDKAQAAtQIAMOEBAQCmAgAh4gEBAKYCACHjAQEApgIAIeQBAgC2AgAh5QEAALcCACDmAQAAtwIAIOcBAAC3AgAg6AECALYCACHpAQEApgIAIeoBAAC4AgAgAwAAAN0BACACAADvAQAwIQAA8AEAIAMAAADdAQAgAgAA3gEAMAMAANoBACAGyAEAALMCADDJAQAA9gEAEMoBAACzAgAw3gEBAK4CACHfAQEArgIAIeABAAC0AgAgAQAAAPMBACABAAAA8wEAIAXIAQAAswIAMMkBAAD2AQAQygEAALMCADDeAQEArgIAId8BAQCuAgAhAAMAAAD2AQAgAgAA9wEAMAMAAPMBACADAAAA9gEAIAIAAPcBADADAADzAQAgAwAAAPYBACACAAD3AQAwAwAA8wEAIALeAQEAAAAB3wEBAAAAAQEVAAD7AQAgAt4BAQAAAAHfAQEAAAABARUAAP0BADABFQAA_QEAMALeAQEA8AIAId8BAQDwAgAhAgAAAPMBACAVAACAAgAgAt4BAQDwAgAh3wEBAPACACECAAAA9gEAIBUAAIICACACAAAA9gEAIBUAAIICACADAAAA8wEAIBwAAPsBACAdAACAAgAgAQAAAPMBACABAAAA9gEAIAMIAADyAgAgIgAA9AIAICMAAPMCACAFyAEAALICADDJAQAAiQIAEMoBAACyAgAw3gEBAKYCACHfAQEApgIAIQMAAAD2AQAgAgAAiAIAMCEAAIkCACADAAAA9gEAIAIAAPcBADADAADzAQAgC8gBAACtAgAwyQEAAI8CABDKAQAArQIAMMsBAQCuAgAhzAEBAK4CACHNAQEArgIAIc4BAQCuAgAhzwEBAK4CACHQAUAArwIAIdwBAACwAgAg3QEAALECACABAAAAjAIAIAEAAACMAgAgCcgBAACtAgAwyQEAAI8CABDKAQAArQIAMMsBAQCuAgAhzAEBAK4CACHNAQEArgIAIc4BAQCuAgAhzwEBAK4CACHQAUAArwIAIQADAAAAjwIAIAIAAJACADADAACMAgAgAwAAAI8CACACAACQAgAwAwAAjAIAIAMAAACPAgAgAgAAkAIAMAMAAIwCACAGywEBAAAAAcwBAQAAAAHNAQEAAAABzgEBAAAAAc8BAQAAAAHQAUAAAAABARUAAJQCACAGywEBAAAAAcwBAQAAAAHNAQEAAAABzgEBAAAAAc8BAQAAAAHQAUAAAAABARUAAJYCADABFQAAlgIAMAbLAQEA8AIAIcwBAQDwAgAhzQEBAPACACHOAQEA8AIAIc8BAQDwAgAh0AFAAPECACECAAAAjAIAIBUAAJkCACAGywEBAPACACHMAQEA8AIAIc0BAQDwAgAhzgEBAPACACHPAQEA8AIAIdABQADxAgAhAgAAAI8CACAVAACbAgAgAgAAAI8CACAVAACbAgAgAwAAAIwCACAcAACUAgAgHQAAmQIAIAEAAACMAgAgAQAAAI8CACADCAAA7QIAICIAAO8CACAjAADuAgAgCcgBAAClAgAwyQEAAKICABDKAQAApQIAMMsBAQCmAgAhzAEBAKYCACHNAQEApgIAIc4BAQCmAgAhzwEBAKYCACHQAUAApwIAIQMAAACPAgAgAgAAoQIAMCEAAKICACADAAAAjwIAIAIAAJACADADAACMA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-wEAAMMC-wEiBwgAAKkCACAiAADFAgAgIwAAxQIAINEBAAAA-wEC0gEAAAD7AQjTAQAAAPsBCNgBAADEAvsBIgcIAACpAgAgIgAAxQIAICMAAMUCACDRAQAAAPsBAtIBAAAA-wEI0wEAAAD7AQjYAQAAxAL7ASIE0QEAAAD7AQLSAQAAAPsBCNMBAAAA-wEI2AEAAMUC-wEiDsgBAADGAgAwyQEAAH8AEMoBAADGAgAw0AFAAKcCACHhAQEApgIAIfUBQACnAgAh_AEBAKYCACH9AQEApgIAIf4BAQCmAgAh_wEBAKYCACGAAgEApgIAIYECAQDHAgAhggJAAMgCACGDAgEAxwIAIQ4IAADKAgAgIgAAzQIAICMAAM0CACDRAQEAAAAB0gEBAAAABdMBAQAAAAXUAQEAAAAB1QEBAAAAAdYBAQAAAAHXAQEAAAAB2AEBAMwCACHZAQEAAAAB2gEBAAAAAdsBAQAAAAELCAAAygIAICIAAMsCACAjAADLAgAg0QFAAAAAAdIBQAAAAAXTAUAAAAAF1AFAAAAAAdUBQAAAAAHWAUAAAAAB1wFAAAAAAdgBQADJAgAhCwgAAMoCACAiAADLAgAgIwAAywIAINEBQAAAAAHSAUAAAAAF0wFAAAAABdQBQAAAAAHVAUAAAAAB1gFAAAAAAdcBQAAAAAHYAUAAyQIAIQjRAQIAAAAB0gECAAAABdMBAgAAAAXUAQIAAAAB1QECAAAAAdYBAgAAAAHXAQIAAAAB2AECAMoCACEI0QFAAAAAAdIBQAAAAAXTAUAAAAAF1AFAAAAAAdUBQAAAAAHWAUAAAAAB1wFAAAAAAdgBQADLAgAhDggAAMoCACAiAADNAgAgIwAAzQIAINEBAQAAAAHSAQEAAAAF0wEBAAAABdQBAQAAAAHVAQEAAAAB1gEBAAAAAdcBAQAAAAHYAQEAzAIAIdkBAQAAAAHaAQEAAAAB2wEBAAAAAQvRAQEAAAAB0gEBAAAABdMBAQAAAAXUAQEAAAAB1QEBAAAAAdYBAQAAAAHXAQEAAAAB2AEBAM0CACHZAQEAAAAB2gEBAAAAAdsBAQAAAAEJyAEAAM4CADDJAQAAaQAQygEAAM4CADDQAUAApwIAIfwBAQCmAgAhhAIBAKYCACGFAgEAxwIAIYYCQADIAgAhhwIBAMcCACEJyAEAAM8CADDJAQAAVgAQygEAAM8CADDQAUAArwIAIfwBAQCuAgAhhAIBAK4CACGFAgEA0AIAIYYCQADRAgAhhwIBANACACEL0QEBAAAAAdIBAQAAAAXTAQEAAAAF1AEBAAAAAdUBAQAAAAHWAQEAAAAB1wEBAAAAAdgBAQDNAgAh2QEBAAAAAdoBAQAAAAHbAQEAAAABCNEBQAAAAAHSAUAAAAAF0wFAAAAABdQBQAAAAAHVAUAAAAAB1gFAAAAAAdcBQAAAAAHYAUAAywIAIQnIAQAA0gIAMMkBAABQABDKAQAA0gIAMNABQACnAgAh3wEBAKYCACH8AQEApgIAIYUCAQDHAgAhhgJAAMgCACGHAgEAxwIAIQnIAQAA0wIAMMkBAAA9ABDKAQAA0wIAMNABQACvAgAh3wEBAK4CACH8AQEArgIAIYUCAQDQAgAhhgJAANECACGHAgEA0AIAIRLIAQAA1AIAMMkBAAA3ABDKAQAA1AIAMNABQACnAgAh3gEBAMcCACH1AUAApwIAIfwBAQCmAgAh_wEBAKYCACGAAgEApgIAIYICQADIAgAhgwIBAMcCACGIAgEAxwIAIYkCAQDHAgAhigIBAKYCACGLAiAA1QIAIYwCIADVAgAhjQJAAMgCACGOAgEAxwIAIQUIAACpAgAgIgAA1wIAICMAANcCACDRASAAAAAB2AEgANYCACEFCAAAqQIAICIAANcCACAjAADXAgAg0QEgAAAAAdgBIADWAgAhAtEBIAAAAAHYASAA1wIAIQLfAQEAAAAB4QEBAAAAAQgFAADaAgAgyAEAANkCADDJAQAAGQAQygEAANkCADDfAQEArgIAIeEBAQCuAgAh9AFAAK8CACH1AUAArwIAIRsBAADnAgAgBAAA6AIAIAkAAOkCACAMAADqAgAgDQAA6gIAIA4AAOsCACAPAADsAgAgyAEAAOUCADDJAQAAAwAQygEAAOUCADDQAUAArwIAId4BAQDQAgAh9QFAAK8CACH8AQEArgIAIf8BAQCuAgAhgAIBAK4CACGCAkAA0QIAIYMCAQDQAgAhiAIBANACACGJAgEA0AIAIYoCAQCuAgAhiwIgAOYCACGMAiAA5gIAIY0CQADRAgAhjgIBANACACGVAgAAAwAglgIAAAMAIALfAQEAAAAB4QEBAAAAAQcFAADaAgAgyAEAANwCADDJAQAAFQAQygEAANwCADDQAUAArwIAId8BAQCuAgAh4QEBAK4CACEC9gEBAAAAAfcBAQAAAAEICgAA2gIAIAsAANoCACDIAQAA3gIAMMkBAAAQABDKAQAA3gIAMPYBAQCuAgAh9wEBAK4CACH4AQIAvQIAIQLfAQEAAAAB-QEBAAAAAQkGAADiAgAgyAEAAOACADDJAQAACwAQygEAAOACADDQAUAArwIAId8BAQCuAgAh9QFAAK8CACH5AQEArgIAIfsBAADhAvsBIgTRAQAAAPsBAtIBAAAA-wEI0wEAAAD7AQjYAQAAxQL7ASISBQAA2gIAIAcAAOQCACDIAQAA4wIAMMkBAAAHABDKAQAA4wIAMNABQACvAgAh4QEBAK4CACH1AUAArwIAIfwBAQCuAgAh_QEBAK4CACH-AQEArgIAIf8BAQCuAgAhgAIBAK4CACGBAgEA0AIAIYICQADRAgAhgwIBANACACGVAgAABwAglgIAAAcAIBAFAADaAgAgBwAA5AIAIMgBAADjAgAwyQEAAAcAEMoBAADjAgAw0AFAAK8CACHhAQEArgIAIfUBQACvAgAh_AEBAK4CACH9AQEArgIAIf4BAQCuAgAh_wEBAK4CACGAAgEArgIAIYECAQDQAgAhggJAANECACGDAgEA0AIAIQOSAgAACwAgkwIAAAsAIJQCAAALACAZAQAA5wIAIAQAAOgCACAJAADpAgAgDAAA6gIAIA0AAOoCACAOAADrAgAgDwAA7AIAIMgBAADlAgAwyQEAAAMAEMoBAADlAgAw0AFAAK8CACHeAQEA0AIAIfUBQACvAgAh_AEBAK4CACH_AQEArgIAIYACAQCuAgAhggJAANECACGDAgEA0AIAIYgCAQDQAgAhiQIBANACACGKAgEArgIAIYsCIADmAgAhjAIgAOYCACGNAkAA0QIAIY4CAQDQAgAhAtEBIAAAAAHYASAA1wIAIRsBAADnAgAgBAAA6AIAIAkAAOkCACAMAADqAgAgDQAA6gIAIA4AAOsCACAPAADsAgAgyAEAAOUCADDJAQAAAwAQygEAAOUCADDQAUAArwIAId4BAQDQAgAh9QFAAK8CACH8AQEArgIAIf8BAQCuAgAhgAIBAK4CACGCAkAA0QIAIYMCAQDQAgAhiAIBANACACGJAgEA0AIAIYoCAQCuAgAhiwIgAOYCACGMAiAA5gIAIY0CQADRAgAhjgIBANACACGVAgAAAwAglgIAAAMAIAOSAgAAAwAgkwIAAAMAIJQCAAADACADkgIAAAcAIJMCAAAHACCUAgAABwAgA5ICAAAQACCTAgAAEAAglAIAABAAIAOSAgAAFQAgkwIAABUAIJQCAAAVACADkgIAABkAIJMCAAAZACCUAgAAGQAgAAAAAZoCAQAAAAEBmgJAAAAAAQAAAAAAAAAABZoCAgAAAAGhAgIAAAABogICAAAAAaMCAgAAAAGkAgIAAAABApoCAQAAAASgAgEAAAAFApoCAQAAAASgAgEAAAAFApoCAQAAAASgAgEAAAAFAZoCAQAAAAQBmgIBAAAABAGaAgEAAAAEAAAABRwAALoEACAdAAC9BAAglwIAALsEACCYAgAAvAQAIJ0CAAABACADHAAAugQAIJcCAAC7BAAgnQIAAAEAIAAAAAUcAAC1BAAgHQAAuAQAIJcCAAC2BAAgmAIAALcEACCdAgAAAQAgAxwAALUEACCXAgAAtgQAIJ0CAAABACAAAAAAAAUcAACtBAAgHQAAswQAIJcCAACuBAAgmAIAALIEACCdAgAAAQAgBRwAAKsEACAdAACwBAAglwIAAKwEACCYAgAArwQAIJ0CAAABACADHAAArQQAIJcCAACuBAAgnQIAAAEAIAMcAACrBAAglwIAAKwEACCdAgAAAQAgAAAAAZoCAAAA-wECBRwAAKYEACAdAACpBAAglwIAAKcEACCYAgAAqAQAIJ0CAAAJACADHAAApgQAIJcCAACnBAAgnQIAAAkAIAAAAAABmgIBAAAAAQGaAkAAAAABBRwAAKAEACAdAACkBAAglwIAAKEEACCYAgAAowQAIJ0CAAABACALHAAAogMAMB0AAKcDADCXAgAAowMAMJgCAACkAwAwmQIAAKUDACCaAgAApgMAMJsCAACmAwAwnAIAAKYDADCdAgAApgMAMJ4CAACoAwAwnwIAAKkDADAE0AFAAAAAAd8BAQAAAAH1AUAAAAAB-wEAAAD7AQICAAAADQAgHAAArQMAIAMAAAANACAcAACtAwAgHQAArAMAIAEVAACiBAAwCgYAAOICACDIAQAA4AIAMMkBAAALABDKAQAA4AIAMNABQACvAgAh3wEBAK4CACH1AUAArwIAIfkBAQCuAgAh-wEAAOEC-wEikQIAAN8CACACAAAADQAgFQAArAMAIAIAAACqAwAgFQAAqwMAIAjIAQAAqQMAMMkBAACqAwAQygEAAKkDADDQAUAArwIAId8BAQCuAgAh9QFAAK8CACH5AQEArgIAIfsBAADhAvsBIgjIAQAAqQMAMMkBAACqAwAQygEAAKkDADDQAUAArwIAId8BAQCuAgAh9QFAAK8CACH5AQEArgIAIfsBAADhAvsBIgTQAUAA8QIAId8BAQDwAgAh9QFAAPECACH7AQAAlwP7ASIE0AFAAPECACHfAQEA8AIAIfUBQADxAgAh-wEAAJcD-wEiBNABQAAAAAHfAQEAAAAB9QFAAAAAAfsBAAAA-wECAxwAAKAEACCXAgAAoQQAIJ0CAAABACAEHAAAogMAMJcCAACjAwAwmQIAAKUDACCdAgAApgMAMAAAAAAAAAAAAAGaAiAAAAABBxwAAJUEACAdAACeBAAglwIAAJYEACCYAgAAnQQAIJsCAAADACCcAgAAAwAgnQIAAAEAIAscAAD6AwAwHQAA_wMAMJcCAAD7AwAwmAIAAPwDADCZAgAA_QMAIJoCAAD-AwAwmwIAAP4DADCcAgAA_gMAMJ0CAAD-AwAwngIAAIAEADCfAgAAgQQAMAscAADuAwAwHQAA8wMAMJcCAADvAwAwmAIAAPADADCZAgAA8QMAIJoCAADyAwAwmwIAAPIDADCcAgAA8gMAMJ0CAADyAwAwngIAAPQDADCfAgAA9QMAMAscAADlAwAwHQAA6QMAMJcCAADmAwAwmAIAAOcDADCZAgAA6AMAIJoCAADdAwAwmwIAAN0DADCcAgAA3QMAMJ0CAADdAwAwngIAAOoDADCfAgAA4AMAMAscAADZAwAwHQAA3gMAMJcCAADaAwAwmAIAANsDADCZAgAA3AMAIJoCAADdAwAwmwIAAN0DADCcAgAA3QMAMJ0CAADdAwAwngIAAN8DADCfAgAA4AMAMAscAADNAwAwHQAA0gMAMJcCAADOAwAwmAIAAM8DADCZAgAA0AMAIJoCAADRAwAwmwIAANEDADCcAgAA0QMAMJ0CAADRAwAwngIAANMDADCfAgAA1AMAMAscAADBAwAwHQAAxgMAMJcCAADCAwAwmAIAAMMDADCZAgAAxAMAIJoCAADFAwAwmwIAAMUDADCcAgAAxQMAMJ0CAADFAwAwngIAAMcDADCfAgAAyAMAMAPfAQEAAAAB9AFAAAAAAfUBQAAAAAECAAAAGwAgHAAAzAMAIAMAAAAbACAcAADMAwAgHQAAywMAIAEVAACcBAAwCQUAANoCACDIAQAA2QIAMMkBAAAZABDKAQAA2QIAMN8BAQCuAgAh4QEBAK4CACH0AUAArwIAIfUBQACvAgAhjwIAANgCACACAAAAGwAgFQAAywMAIAIAAADJAwAgFQAAygMAIAfIAQAAyAMAMMkBAADJAwAQygEAAMgDADDfAQEArgIAIeEBAQCuAgAh9AFAAK8CACH1AUAArwIAIQfIAQAAyAMAMMkBAADJAwAQygEAAMgDADDfAQEArgIAIeEBAQCuAgAh9AFAAK8CACH1AUAArwIAIQPfAQEA8AIAIfQBQADxAgAh9QFAAPECACED3wEBAPACACH0AUAA8QIAIfUBQADxAgAhA98BAQAAAAH0AUAAAAAB9QFAAAAAAQLQAUAAAAAB3wEBAAAAAQIAAAAXACAcAADYAwAgAwAAABcAIBwAANgDACAdAADXAwAgARUAAJsEADAIBQAA2gIAIMgBAADcAgAwyQEAABUAEMoBAADcAgAw0AFAAK8CACHfAQEArgIAIeEBAQCuAgAhjwIAANsCACACAAAAFwAgFQAA1wMAIAIAAADVAwAgFQAA1gMAIAbIAQAA1AMAMMkBAADVAwAQygEAANQDADDQAUAArwIAId8BAQCuAgAh4QEBAK4CACEGyAEAANQDADDJAQAA1QMAEMoBAADUAwAw0AFAAK8CACHfAQEArgIAIeEBAQCuAgAhAtABQADxAgAh3wEBAPACACEC0AFAAPECACHfAQEA8AIAIQLQAUAAAAAB3wEBAAAAAQMKAACSAwAg9gEBAAAAAfgBAgAAAAECAAAAEgAgHAAA5AMAIAMAAAASACAcAADkAwAgHQAA4wMAIAEVAACaBAAwCQoAANoCACALAADaAgAgyAEAAN4CADDJAQAAEAAQygEAAN4CADD2AQEArgIAIfcBAQCuAgAh-AECAL0CACGQAgAA3QIAIAIAAAASACAVAADjAwAgAgAAAOEDACAVAADiAwAgBsgBAADgAwAwyQEAAOEDABDKAQAA4AMAMPYBAQCuAgAh9wEBAK4CACH4AQIAvQIAIQbIAQAA4AMAMMkBAADhAwAQygEAAOADADD2AQEArgIAIfcBAQCuAgAh-AECAL0CACEC9gEBAPACACH4AQIA-gIAIQMKAACQAwAg9gEBAPACACH4AQIA-gIAIQMKAACSAwAg9gEBAAAAAfgBAgAAAAEDCwAAkwMAIPcBAQAAAAH4AQIAAAABAgAAABIAIBwAAO0DACADAAAAEgAgHAAA7QMAIB0AAOwDACABFQAAmQQAMAIAAAASACAVAADsAwAgAgAAAOEDACAVAADrAwAgAvcBAQDwAgAh-AECAPoCACEDCwAAkQMAIPcBAQDwAgAh-AECAPoCACEDCwAAkwMAIPcBAQAAAAH4AQIAAAABCwcAAK8DACDQAUAAAAAB9QFAAAAAAfwBAQAAAAH9AQEAAAAB_gEBAAAAAf8BAQAAAAGAAgEAAAABgQIBAAAAAYICQAAAAAGDAgEAAAABAgAAAAkAIBwAAPkDACADAAAACQAgHAAA-QMAIB0AAPgDACABFQAAmAQAMBAFAADaAgAgBwAA5AIAIMgBAADjAgAwyQEAAAcAEMoBAADjAgAw0AFAAK8CACHhAQEArgIAIfUBQACvAgAh_AEBAAAAAf0BAQCuAgAh_gEBAK4CACH_AQEArgIAIYACAQCuAgAhgQIBANACACGCAkAA0QIAIYMCAQDQAgAhAgAAAAkAIBUAAPgDACACAAAA9gMAIBUAAPcDACAOyAEAAPUDADDJAQAA9gMAEMoBAAD1AwAw0AFAAK8CACHhAQEArgIAIfUBQACvAgAh_AEBAK4CACH9AQEArgIAIf4BAQCuAgAh_wEBAK4CACGAAgEArgIAIYECAQDQAgAhggJAANECACGDAgEA0AIAIQ7IAQAA9QMAMMkBAAD2AwAQygEAAPUDADDQAUAArwIAIeEBAQCuAgAh9QFAAK8CACH8AQEArgIAIf0BAQCuAgAh_gEBAK4CACH_AQEArgIAIYACAQCuAgAhgQIBANACACGCAkAA0QIAIYMCAQDQAgAhCtABQADxAgAh9QFAAPECACH8AQEA8AIAIf0BAQDwAgAh_gEBAPACACH_AQEA8AIAIYACAQDwAgAhgQIBAJ4DACGCAkAAnwMAIYMCAQCeAwAhCwcAAKEDACDQAUAA8QIAIfUBQADxAgAh_AEBAPACACH9AQEA8AIAIf4BAQDwAgAh_wEBAPACACGAAgEA8AIAIYECAQCeAwAhggJAAJ8DACGDAgEAngMAIQsHAACvAwAg0AFAAAAAAfUBQAAAAAH8AQEAAAAB_QEBAAAAAf4BAQAAAAH_AQEAAAABgAIBAAAAAYECAQAAAAGCAkAAAAABgwIBAAAAARQEAACGBAAgCQAAhwQAIAwAAIgEACANAACJBAAgDgAAigQAIA8AAIsEACDQAUAAAAAB3gEBAAAAAfUBQAAAAAH8AQEAAAAB_wEBAAAAAYACAQAAAAGCAkAAAAABgwIBAAAAAYkCAQAAAAGKAgEAAAABiwIgAAAAAYwCIAAAAAGNAkAAAAABjgIBAAAAAQIAAAABACAcAACFBAAgAwAAAAEAIBwAAIUEACAdAACEBAAgARUAAJcEADAZAQAA5wIAIAQAAOgCACAJAADpAgAgDAAA6gIAIA0AAOoCACAOAADrAgAgDwAA7AIAIMgBAADlAgAwyQEAAAMAEMoBAADlAgAw0AFAAK8CACHeAQEA0AIAIfUBQACvAgAh_AEBAAAAAf8BAQCuAgAhgAIBAK4CACGCAkAA0QIAIYMCAQDQAgAhiAIBANACACGJAgEA0AIAIYoCAQCuAgAhiwIgAOYCACGMAiAA5gIAIY0CQADRAgAhjgIBANACACECAAAAAQAgFQAAhAQAIAIAAACCBAAgFQAAgwQAIBLIAQAAgQQAMMkBAACCBAAQygEAAIEEADDQAUAArwIAId4BAQDQAgAh9QFAAK8CACH8AQEArgIAIf8BAQCuAgAhgAIBAK4CACGCAkAA0QIAIYMCAQDQAgAhiAIBANACACGJAgEA0AIAIYoCAQCuAgAhiwIgAOYCACGMAiAA5gIAIY0CQADRAgAhjgIBANACACESyAEAAIEEADDJAQAAggQAEMoBAACBBAAw0AFAAK8CACHeAQEA0AIAIfUBQACvAgAh_AEBAK4CACH_AQEArgIAIYACAQCuAgAhggJAANECACGDAgEA0AIAIYgCAQDQAgAhiQIBANACACGKAgEArgIAIYsCIADmAgAhjAIgAOYCACGNAkAA0QIAIY4CAQDQAgAhDtABQADxAgAh3gEBAJ4DACH1AUAA8QIAIfwBAQDwAgAh_wEBAPACACGAAgEA8AIAIYICQACfAwAhgwIBAJ4DACGJAgEAngMAIYoCAQDwAgAhiwIgALkDACGMAiAAuQMAIY0CQACfAwAhjgIBAJ4DACEUBAAAuwMAIAkAALwDACAMAAC9AwAgDQAAvgMAIA4AAL8DACAPAADAAwAg0AFAAPECACHeAQEAngMAIfUBQADxAgAh_AEBAPACACH_AQEA8AIAIYACAQDwAgAhggJAAJ8DACGDAgEAngMAIYkCAQCeAwAhigIBAPACACGLAiAAuQMAIYwCIAC5AwAhjQJAAJ8DACGOAgEAngMAIRQEAACGBAAgCQAAhwQAIAwAAIgEACANAACJBAAgDgAAigQAIA8AAIsEACDQAUAAAAAB3gEBAAAAAfUBQAAAAAH8AQEAAAAB_wEBAAAAAYACAQAAAAGCAkAAAAABgwIBAAAAAYkCAQAAAAGKAgEAAAABiwIgAAAAAYwCIAAAAAGNAkAAAAABjgIBAAAAAQQcAAD6AwAwlwIAAPsDADCZAgAA_QMAIJ0CAAD-AwAwBBwAAO4DADCXAgAA7wMAMJkCAADxAwAgnQIAAPIDADAEHAAA5QMAMJcCAADmAwAwmQIAAOgDACCdAgAA3QMAMAQcAADZAwAwlwIAANoDADCZAgAA3AMAIJ0CAADdAwAwBBwAAM0DADCXAgAAzgMAMJkCAADQAwAgnQIAANEDADAEHAAAwQMAMJcCAADCAwAwmQIAAMQDACCdAgAAxQMAMAMcAACVBAAglwIAAJYEACCdAgAAAQAgDgEAAI0EACAEAACQBAAgCQAAkQQAIAwAAJIEACANAACSBAAgDgAAkwQAIA8AAJQEACDeAQAAmgMAIIICAACaAwAggwIAAJoDACCIAgAAmgMAIIkCAACaAwAgjQIAAJoDACCOAgAAmgMAIAUFAACNBAAgBwAAjwQAIIECAACaAwAgggIAAJoDACCDAgAAmgMAIAAAAAAAABUBAACMBAAgCQAAhwQAIAwAAIgEACANAACJBAAgDgAAigQAIA8AAIsEACDQAUAAAAAB3gEBAAAAAfUBQAAAAAH8AQEAAAAB_wEBAAAAAYACAQAAAAGCAkAAAAABgwIBAAAAAYgCAQAAAAGJAgEAAAABigIBAAAAAYsCIAAAAAGMAiAAAAABjQJAAAAAAY4CAQAAAAECAAAAAQAgHAAAlQQAIA7QAUAAAAAB3gEBAAAAAfUBQAAAAAH8AQEAAAAB_wEBAAAAAYACAQAAAAGCAkAAAAABgwIBAAAAAYkCAQAAAAGKAgEAAAABiwIgAAAAAYwCIAAAAAGNAkAAAAABjgIBAAAAAQrQAUAAAAAB9QFAAAAAAfwBAQAAAAH9AQEAAAAB_gEBAAAAAf8BAQAAAAGAAgEAAAABgQIBAAAAAYICQAAAAAGDAgEAAAABAvcBAQAAAAH4AQIAAAABAvYBAQAAAAH4AQIAAAABAtABQAAAAAHfAQEAAAABA98BAQAAAAH0AUAAAAAB9QFAAAAAAQMAAAADACAcAACVBAAgHQAAnwQAIBcAAAADACABAAC6AwAgCQAAvAMAIAwAAL0DACANAAC-AwAgDgAAvwMAIA8AAMADACAVAACfBAAg0AFAAPECACHeAQEAngMAIfUBQADxAgAh_AEBAPACACH_AQEA8AIAIYACAQDwAgAhggJAAJ8DACGDAgEAngMAIYgCAQCeAwAhiQIBAJ4DACGKAgEA8AIAIYsCIAC5AwAhjAIgALkDACGNAkAAnwMAIY4CAQCeAwAhFQEAALoDACAJAAC8AwAgDAAAvQMAIA0AAL4DACAOAAC_AwAgDwAAwAMAINABQADxAgAh3gEBAJ4DACH1AUAA8QIAIfwBAQDwAgAh_wEBAPACACGAAgEA8AIAIYICQACfAwAhgwIBAJ4DACGIAgEAngMAIYkCAQCeAwAhigIBAPACACGLAiAAuQMAIYwCIAC5AwAhjQJAAJ8DACGOAgEAngMAIRUBAACMBAAgBAAAhgQAIAwAAIgEACANAACJBAAgDgAAigQAIA8AAIsEACDQAUAAAAAB3gEBAAAAAfUBQAAAAAH8AQEAAAAB_wEBAAAAAYACAQAAAAGCAkAAAAABgwIBAAAAAYgCAQAAAAGJAgEAAAABigIBAAAAAYsCIAAAAAGMAiAAAAABjQJAAAAAAY4CAQAAAAECAAAAAQAgHAAAoAQAIATQAUAAAAAB3wEBAAAAAfUBQAAAAAH7AQAAAPsBAgMAAAADACAcAACgBAAgHQAApQQAIBcAAAADACABAAC6AwAgBAAAuwMAIAwAAL0DACANAAC-AwAgDgAAvwMAIA8AAMADACAVAAClBAAg0AFAAPECACHeAQEAngMAIfUBQADxAgAh_AEBAPACACH_AQEA8AIAIYACAQDwAgAhggJAAJ8DACGDAgEAngMAIYgCAQCeAwAhiQIBAJ4DACGKAgEA8AIAIYsCIAC5AwAhjAIgALkDACGNAkAAnwMAIY4CAQCeAwAhFQEAALoDACAEAAC7AwAgDAAAvQMAIA0AAL4DACAOAAC_AwAgDwAAwAMAINABQADxAgAh3gEBAJ4DACH1AUAA8QIAIfwBAQDwAgAh_wEBAPACACGAAgEA8AIAIYICQACfAwAhgwIBAJ4DACGIAgEAngMAIYkCAQCeAwAhigIBAPACACGLAiAAuQMAIYwCIAC5AwAhjQJAAJ8DACGOAgEAngMAIQwFAACuAwAg0AFAAAAAAeEBAQAAAAH1AUAAAAAB_AEBAAAAAf0BAQAAAAH-AQEAAAAB_wEBAAAAAYACAQAAAAGBAgEAAAABggJAAAAAAYMCAQAAAAECAAAACQAgHAAApgQAIAMAAAAHACAcAACmBAAgHQAAqgQAIA4AAAAHACAFAACgAwAgFQAAqgQAINABQADxAgAh4QEBAPACACH1AUAA8QIAIfwBAQDwAgAh_QEBAPACACH-AQEA8AIAIf8BAQDwAgAhgAIBAPACACGBAgEAngMAIYICQACfAwAhgwIBAJ4DACEMBQAAoAMAINABQADxAgAh4QEBAPACACH1AUAA8QIAIfwBAQDwAgAh_QEBAPACACH-AQEA8AIAIf8BAQDwAgAhgAIBAPACACGBAgEAngMAIYICQACfAwAhgwIBAJ4DACEVAQAAjAQAIAQAAIYEACAJAACHBAAgDAAAiAQAIA4AAIoEACAPAACLBAAg0AFAAAAAAd4BAQAAAAH1AUAAAAAB_AEBAAAAAf8BAQAAAAGAAgEAAAABggJAAAAAAYMCAQAAAAGIAgEAAAABiQIBAAAAAYoCAQAAAAGLAiAAAAABjAIgAAAAAY0CQAAAAAGOAgEAAAABAgAAAAEAIBwAAKsEACAVAQAAjAQAIAQAAIYEACAJAACHBAAgDQAAiQQAIA4AAIoEACAPAACLBAAg0AFAAAAAAd4BAQAAAAH1AUAAAAAB_AEBAAAAAf8BAQAAAAGAAgEAAAABggJAAAAAAYMCAQAAAAGIAgEAAAABiQIBAAAAAYoCAQAAAAGLAiAAAAABjAIgAAAAAY0CQAAAAAGOAgEAAAABAgAAAAEAIBwAAK0EACADAAAAAwAgHAAAqwQAIB0AALEEACAXAAAAAwAgAQAAugMAIAQAALsDACAJAAC8AwAgDAAAvQMAIA4AAL8DACAPAADAAwAgFQAAsQQAINABQADxAgAh3gEBAJ4DACH1AUAA8QIAIfwBAQDwAgAh_wEBAPACACGAAgEA8AIAIYICQACfAwAhgwIBAJ4DACGIAgEAngMAIYkCAQCeAwAhigIBAPACACGLAiAAuQMAIYwCIAC5AwAhjQJAAJ8DACGOAgEAngMAIRUBAAC6AwAgBAAAuwMAIAkAALwDACAMAAC9AwAgDgAAvwMAIA8AAMADACDQAUAA8QIAId4BAQCeAwAh9QFAAPECACH8AQEA8AIAIf8BAQDwAgAhgAIBAPACACGCAkAAnwMAIYMCAQCeAwAhiAIBAJ4DACGJAgEAngMAIYoCAQDwAgAhiwIgALkDACGMAiAAuQMAIY0CQACfAwAhjgIBAJ4DACEDAAAAAwAgHAAArQQAIB0AALQEACAXAAAAAwAgAQAAugMAIAQAALsDACAJAAC8AwAgDQAAvgMAIA4AAL8DACAPAADAAwAgFQAAtAQAINABQADxAgAh3gEBAJ4DACH1AUAA8QIAIfwBAQDwAgAh_wEBAPACACGAAgEA8AIAIYICQACfAwAhgwIBAJ4DACGIAgEAngMAIYkCAQCeAwAhigIBAPACACGLAiAAuQMAIYwCIAC5AwAhjQJAAJ8DACGOAgEAngMAIRUBAAC6AwAgBAAAuwMAIAkAALwDACANAAC-AwAgDgAAvwMAIA8AAMADACDQAUAA8QIAId4BAQCeAwAh9QFAAPECACH8AQEA8AIAIf8BAQDwAgAhgAIBAPACACGCAkAAnwMAIYMCAQCeAwAhiAIBAJ4DACGJAgEAngMAIYoCAQDwAgAhiwIgALkDACGMAiAAuQMAIY0CQACfAwAhjgIBAJ4DACEVAQAAjAQAIAQAAIYEACAJAACHBAAgDAAAiAQAIA0AAIkEACAPAACLBAAg0AFAAAAAAd4BAQAAAAH1AUAAAAAB_AEBAAAAAf8BAQAAAAGAAgEAAAABggJAAAAAAYMCAQAAAAGIAgEAAAABiQIBAAAAAYoCAQAAAAGLAiAAAAABjAIgAAAAAY0CQAAAAAGOAgEAAAABAgAAAAEAIBwAALUEACADAAAAAwAgHAAAtQQAIB0AALkEACAXAAAAAwAgAQAAugMAIAQAALsDACAJAAC8AwAgDAAAvQMAIA0AAL4DACAPAADAAwAgFQAAuQQAINABQADxAgAh3gEBAJ4DACH1AUAA8QIAIfwBAQDwAgAh_wEBAPACACGAAgEA8AIAIYICQACfAwAhgwIBAJ4DACGIAgEAngMAIYkCAQCeAwAhigIBAPACACGLAiAAuQMAIYwCIAC5AwAhjQJAAJ8DACGOAgEAngMAIRUBAAC6AwAgBAAAuwMAIAkAALwDACAMAAC9AwAgDQAAvgMAIA8AAMADACDQAUAA8QIAId4BAQCeAwAh9QFAAPECACH8AQEA8AIAIf8BAQDwAgAhgAIBAPACACGCAkAAnwMAIYMCAQCeAwAhiAIBAJ4DACGJAgEAngMAIYoCAQDwAgAhiwIgALkDACGMAiAAuQMAIY0CQACfAwAhjgIBAJ4DACEVAQAAjAQAIAQAAIYEACAJAACHBAAgDAAAiAQAIA0AAIkEACAOAACKBAAg0AFAAAAAAd4BAQAAAAH1AUAAAAAB_AEBAAAAAf8BAQAAAAGAAgEAAAABggJAAAAAAYMCAQAAAAGIAgEAAAABiQIBAAAAAYoCAQAAAAGLAiAAAAABjAIgAAAAAY0CQAAAAAGOAgEAAAABAgAAAAEAIBwAALoEACADAAAAAwAgHAAAugQAIB0AAL4EACAXAAAAAwAgAQAAugMAIAQAALsDACAJAAC8AwAgDAAAvQMAIA0AAL4DACAOAAC_AwAgFQAAvgQAINABQADxAgAh3gEBAJ4DACH1AUAA8QIAIfwBAQDwAgAh_wEBAPACACGAAgEA8AIAIYICQACfAwAhgwIBAJ4DACGIAgEAngMAIYkCAQCeAwAhigIBAPACACGLAiAAuQMAIYwCIAC5AwAhjQJAAJ8DACGOAgEAngMAIRUBAAC6AwAgBAAAuwMAIAkAALwDACAMAAC9AwAgDQAAvgMAIA4AAL8DACDQAUAA8QIAId4BAQCeAwAh9QFAAPECACH8AQEA8AIAIf8BAQDwAgAhgAIBAPACACGCAkAAnwMAIYMCAQCeAwAhiAIBAJ4DACGJAgEAngMAIYoCAQDwAgAhiwIgALkDACGMAiAAuQMAIY0CQACfAwAhjgIBAJ4DACEIAQQBBAYBCAAICQoCDBMFDRQFDhgGDxwHAwUAAQcOAwgABAEGAAIBBw8AAgoAAQsAAQEFAAEBBQABBgQdAAkeAAwfAA0gAA4hAA8iAAABASwBAQEyAQMIAA0iAA4jAA8AAAADCAANIgAOIwAPAAAAAwgAFSIAFiMAFwAAAAMIABUiABYjABcAAAADCAAdIgAeIwAfAAAAAwgAHSIAHiMAHwEFAAEBBQABAwgAJCIAJSMAJgAAAAMIACQiACUjACYBBgACAQYAAgMIACsiACwjAC0AAAADCAArIgAsIwAtAgoAAQsAAQIKAAELAAEFCAAyIgA1IwA2dAAzdQA0AAAAAAAFCAAyIgA1IwA2dAAzdQA0AQUAAQEFAAEDCAA7IgA8IwA9AAAAAwgAOyIAPCMAPQEFAAEBBQABAwgAQiIAQyMARAAAAAMIAEIiAEMjAEQAAAAFCABKIgBNIwBOdABLdQBMAAAAAAAFCABKIgBNIwBOdABLdQBMAAAAAwgAVCIAVSMAVgAAAAMIAFQiAFUjAFYAAAADCABcIgBdIwBeAAAAAwgAXCIAXSMAXhACAREjARIkARMlARQmARYoARcqCRgrChkuARowCRsxCx4zAR80ASA1CSQ4DCU5ECY7ESc8ESg_ESlAESpBEStDESxFCS1GEi5IES9KCTBLEzFMETJNETNOCTRRFDVSGDZUGTdVGThYGTlZGTpaGTtcGTxeCT1fGj5hGT9jCUBkG0FlGUJmGUNnCURqHEVrIEZsAkdtAkhuAklvAkpwAktyAkx0CU11IU53Ak95CVB6IlF7AlJ8AlN9CVSAASNVgQEnVoIBA1eDAQNYhAEDWYUBA1qGAQNbiAEDXIoBCV2LAShejQEDX48BCWCQASlhkQEDYpIBA2OTAQlklgEqZZcBLmaYAQVnmQEFaJoBBWmbAQVqnAEFa54BBWygAQltoQEvbqMBBW-lAQlwpgEwcacBBXKoAQVzqQEJdqwBMXetATd4rgEGea8BBnqwAQZ7sQEGfLIBBn20AQZ-tgEJf7cBOIABuQEGgQG7AQmCAbwBOYMBvQEGhAG-AQaFAb8BCYYBwgE6hwHDAT6IAcQBB4kBxQEHigHGAQeLAccBB4wByAEHjQHKAQeOAcwBCY8BzQE_kAHPAQeRAdEBCZIB0gFAkwHTAQeUAdQBB5UB1QEJlgHYAUGXAdkBRZgB2wFGmQHcAUaaAd8BRpsB4AFGnAHhAUadAeMBRp4B5QEJnwHmAUegAegBRqEB6gEJogHrAUijAewBRqQB7QFGpQHuAQmmAfEBSacB8gFPqAH0AVCpAfUBUKoB-AFQqwH5AVCsAfoBUK0B_AFQrgH-AQmvAf8BUbABgQJQsQGDAgmyAYQCUrMBhQJQtAGGAlC1AYcCCbYBigJTtwGLAle4AY0CWLkBjgJYugGRAli7AZICWLwBkwJYvQGVAli-AZcCCb8BmAJZwAGaAljBAZwCCcIBnQJawwGeAljEAZ8CWMUBoAIJxgGjAlvHAaQCXw" } config.compilerWasm = { getRuntime: async () => require('./query_compiler_fast_bg.js'), diff --git a/packages/forums-prisma-client/index-browser.js b/packages/forums-prisma-client/index-browser.js index 1995440..3338bd5 100644 --- a/packages/forums-prisma-client/index-browser.js +++ b/packages/forums-prisma-client/index-browser.js @@ -197,11 +197,42 @@ exports.Prisma.TopicReadStateScalarFieldEnum = { updatedAt: 'updatedAt' }; +exports.Prisma.PublicForumCategoryScalarFieldEnum = { + topicId: 'topicId', + description: 'description', + displayAs: 'displayAs', + sortOrder: 'sortOrder', + readRoles: 'readRoles', + createRoles: 'createRoles', + replyRoles: 'replyRoles', + legacyId: 'legacyId', + legacySlug: 'legacySlug', + source: 'source' +}; + +exports.Prisma.PublicForumRoleMemberScalarFieldEnum = { + roleName: 'roleName', + memberId: 'memberId' +}; + +exports.Prisma.PublicForumImportRecordScalarFieldEnum = { + entityType: 'entityType', + sourceId: 'sourceId', + targetId: 'targetId', + sourceHash: 'sourceHash', + targetHash: 'targetHash', + createdAt: 'createdAt' +}; + exports.Prisma.SortOrder = { asc: 'asc', desc: 'desc' }; +exports.Prisma.JsonNullValueInput = { + JsonNull: Prisma.JsonNull +}; + exports.Prisma.QueryMode = { default: 'default', insensitive: 'insensitive' @@ -211,6 +242,12 @@ exports.Prisma.NullsOrder = { first: 'first', last: 'last' }; + +exports.Prisma.JsonNullValueFilter = { + DbNull: Prisma.DbNull, + JsonNull: Prisma.JsonNull, + AnyNull: Prisma.AnyNull +}; exports.PostReactionType = exports.$Enums.PostReactionType = { THUMBS_UP: 'THUMBS_UP', THUMBS_DOWN: 'THUMBS_DOWN' @@ -224,7 +261,10 @@ exports.Prisma.ModelName = { PostReaction: 'PostReaction', TopicClosure: 'TopicClosure', TopicWatch: 'TopicWatch', - TopicReadState: 'TopicReadState' + TopicReadState: 'TopicReadState', + PublicForumCategory: 'PublicForumCategory', + PublicForumRoleMember: 'PublicForumRoleMember', + PublicForumImportRecord: 'PublicForumImportRecord' }; /** diff --git a/packages/forums-prisma-client/index.d.ts b/packages/forums-prisma-client/index.d.ts index 7f5014b..6bbdd0e 100644 --- a/packages/forums-prisma-client/index.d.ts +++ b/packages/forums-prisma-client/index.d.ts @@ -35,7 +35,7 @@ export type IpBan = $Result.DefaultSelection export type Post = $Result.DefaultSelection /** * Model PostReaction - * + * */ export type PostReaction = $Result.DefaultSelection /** @@ -53,6 +53,21 @@ export type TopicWatch = $Result.DefaultSelection * */ export type TopicReadState = $Result.DefaultSelection +/** + * Model PublicForumCategory + * + */ +export type PublicForumCategory = $Result.DefaultSelection +/** + * Model PublicForumRoleMember + * + */ +export type PublicForumRoleMember = $Result.DefaultSelection +/** + * Model PublicForumImportRecord + * + */ +export type PublicForumImportRecord = $Result.DefaultSelection /** * Enums @@ -271,6 +286,36 @@ export class PrismaClient< * ``` */ get topicReadState(): Prisma.TopicReadStateDelegate; + + /** + * `prisma.publicForumCategory`: Exposes CRUD operations for the **PublicForumCategory** model. + * Example usage: + * ```ts + * // Fetch zero or more PublicForumCategories + * const publicForumCategories = await prisma.publicForumCategory.findMany() + * ``` + */ + get publicForumCategory(): Prisma.PublicForumCategoryDelegate; + + /** + * `prisma.publicForumRoleMember`: Exposes CRUD operations for the **PublicForumRoleMember** model. + * Example usage: + * ```ts + * // Fetch zero or more PublicForumRoleMembers + * const publicForumRoleMembers = await prisma.publicForumRoleMember.findMany() + * ``` + */ + get publicForumRoleMember(): Prisma.PublicForumRoleMemberDelegate; + + /** + * `prisma.publicForumImportRecord`: Exposes CRUD operations for the **PublicForumImportRecord** model. + * Example usage: + * ```ts + * // Fetch zero or more PublicForumImportRecords + * const publicForumImportRecords = await prisma.publicForumImportRecord.findMany() + * ``` + */ + get publicForumImportRecord(): Prisma.PublicForumImportRecordDelegate; } export namespace Prisma { @@ -725,7 +770,10 @@ export namespace Prisma { PostReaction: 'PostReaction', TopicClosure: 'TopicClosure', TopicWatch: 'TopicWatch', - TopicReadState: 'TopicReadState' + TopicReadState: 'TopicReadState', + PublicForumCategory: 'PublicForumCategory', + PublicForumRoleMember: 'PublicForumRoleMember', + PublicForumImportRecord: 'PublicForumImportRecord' }; export type ModelName = (typeof ModelName)[keyof typeof ModelName] @@ -741,7 +789,7 @@ export namespace Prisma { omit: GlobalOmitOptions } meta: { - modelProps: "topic" | "memberBan" | "ipBan" | "post" | "postReaction" | "topicClosure" | "topicWatch" | "topicReadState" + modelProps: "topic" | "memberBan" | "ipBan" | "post" | "postReaction" | "topicClosure" | "topicWatch" | "topicReadState" | "publicForumCategory" | "publicForumRoleMember" | "publicForumImportRecord" txIsolationLevel: Prisma.TransactionIsolationLevel } model: { @@ -1337,6 +1385,228 @@ export namespace Prisma { } } } + PublicForumCategory: { + payload: Prisma.$PublicForumCategoryPayload + fields: Prisma.PublicForumCategoryFieldRefs + operations: { + findUnique: { + args: Prisma.PublicForumCategoryFindUniqueArgs + result: $Utils.PayloadToResult | null + } + findUniqueOrThrow: { + args: Prisma.PublicForumCategoryFindUniqueOrThrowArgs + result: $Utils.PayloadToResult + } + findFirst: { + args: Prisma.PublicForumCategoryFindFirstArgs + result: $Utils.PayloadToResult | null + } + findFirstOrThrow: { + args: Prisma.PublicForumCategoryFindFirstOrThrowArgs + result: $Utils.PayloadToResult + } + findMany: { + args: Prisma.PublicForumCategoryFindManyArgs + result: $Utils.PayloadToResult[] + } + create: { + args: Prisma.PublicForumCategoryCreateArgs + result: $Utils.PayloadToResult + } + createMany: { + args: Prisma.PublicForumCategoryCreateManyArgs + result: BatchPayload + } + createManyAndReturn: { + args: Prisma.PublicForumCategoryCreateManyAndReturnArgs + result: $Utils.PayloadToResult[] + } + delete: { + args: Prisma.PublicForumCategoryDeleteArgs + result: $Utils.PayloadToResult + } + update: { + args: Prisma.PublicForumCategoryUpdateArgs + result: $Utils.PayloadToResult + } + deleteMany: { + args: Prisma.PublicForumCategoryDeleteManyArgs + result: BatchPayload + } + updateMany: { + args: Prisma.PublicForumCategoryUpdateManyArgs + result: BatchPayload + } + updateManyAndReturn: { + args: Prisma.PublicForumCategoryUpdateManyAndReturnArgs + result: $Utils.PayloadToResult[] + } + upsert: { + args: Prisma.PublicForumCategoryUpsertArgs + result: $Utils.PayloadToResult + } + aggregate: { + args: Prisma.PublicForumCategoryAggregateArgs + result: $Utils.Optional + } + groupBy: { + args: Prisma.PublicForumCategoryGroupByArgs + result: $Utils.Optional[] + } + count: { + args: Prisma.PublicForumCategoryCountArgs + result: $Utils.Optional | number + } + } + } + PublicForumRoleMember: { + payload: Prisma.$PublicForumRoleMemberPayload + fields: Prisma.PublicForumRoleMemberFieldRefs + operations: { + findUnique: { + args: Prisma.PublicForumRoleMemberFindUniqueArgs + result: $Utils.PayloadToResult | null + } + findUniqueOrThrow: { + args: Prisma.PublicForumRoleMemberFindUniqueOrThrowArgs + result: $Utils.PayloadToResult + } + findFirst: { + args: Prisma.PublicForumRoleMemberFindFirstArgs + result: $Utils.PayloadToResult | null + } + findFirstOrThrow: { + args: Prisma.PublicForumRoleMemberFindFirstOrThrowArgs + result: $Utils.PayloadToResult + } + findMany: { + args: Prisma.PublicForumRoleMemberFindManyArgs + result: $Utils.PayloadToResult[] + } + create: { + args: Prisma.PublicForumRoleMemberCreateArgs + result: $Utils.PayloadToResult + } + createMany: { + args: Prisma.PublicForumRoleMemberCreateManyArgs + result: BatchPayload + } + createManyAndReturn: { + args: Prisma.PublicForumRoleMemberCreateManyAndReturnArgs + result: $Utils.PayloadToResult[] + } + delete: { + args: Prisma.PublicForumRoleMemberDeleteArgs + result: $Utils.PayloadToResult + } + update: { + args: Prisma.PublicForumRoleMemberUpdateArgs + result: $Utils.PayloadToResult + } + deleteMany: { + args: Prisma.PublicForumRoleMemberDeleteManyArgs + result: BatchPayload + } + updateMany: { + args: Prisma.PublicForumRoleMemberUpdateManyArgs + result: BatchPayload + } + updateManyAndReturn: { + args: Prisma.PublicForumRoleMemberUpdateManyAndReturnArgs + result: $Utils.PayloadToResult[] + } + upsert: { + args: Prisma.PublicForumRoleMemberUpsertArgs + result: $Utils.PayloadToResult + } + aggregate: { + args: Prisma.PublicForumRoleMemberAggregateArgs + result: $Utils.Optional + } + groupBy: { + args: Prisma.PublicForumRoleMemberGroupByArgs + result: $Utils.Optional[] + } + count: { + args: Prisma.PublicForumRoleMemberCountArgs + result: $Utils.Optional | number + } + } + } + PublicForumImportRecord: { + payload: Prisma.$PublicForumImportRecordPayload + fields: Prisma.PublicForumImportRecordFieldRefs + operations: { + findUnique: { + args: Prisma.PublicForumImportRecordFindUniqueArgs + result: $Utils.PayloadToResult | null + } + findUniqueOrThrow: { + args: Prisma.PublicForumImportRecordFindUniqueOrThrowArgs + result: $Utils.PayloadToResult + } + findFirst: { + args: Prisma.PublicForumImportRecordFindFirstArgs + result: $Utils.PayloadToResult | null + } + findFirstOrThrow: { + args: Prisma.PublicForumImportRecordFindFirstOrThrowArgs + result: $Utils.PayloadToResult + } + findMany: { + args: Prisma.PublicForumImportRecordFindManyArgs + result: $Utils.PayloadToResult[] + } + create: { + args: Prisma.PublicForumImportRecordCreateArgs + result: $Utils.PayloadToResult + } + createMany: { + args: Prisma.PublicForumImportRecordCreateManyArgs + result: BatchPayload + } + createManyAndReturn: { + args: Prisma.PublicForumImportRecordCreateManyAndReturnArgs + result: $Utils.PayloadToResult[] + } + delete: { + args: Prisma.PublicForumImportRecordDeleteArgs + result: $Utils.PayloadToResult + } + update: { + args: Prisma.PublicForumImportRecordUpdateArgs + result: $Utils.PayloadToResult + } + deleteMany: { + args: Prisma.PublicForumImportRecordDeleteManyArgs + result: BatchPayload + } + updateMany: { + args: Prisma.PublicForumImportRecordUpdateManyArgs + result: BatchPayload + } + updateManyAndReturn: { + args: Prisma.PublicForumImportRecordUpdateManyAndReturnArgs + result: $Utils.PayloadToResult[] + } + upsert: { + args: Prisma.PublicForumImportRecordUpsertArgs + result: $Utils.PayloadToResult + } + aggregate: { + args: Prisma.PublicForumImportRecordAggregateArgs + result: $Utils.Optional + } + groupBy: { + args: Prisma.PublicForumImportRecordGroupByArgs + result: $Utils.Optional[] + } + count: { + args: Prisma.PublicForumImportRecordCountArgs + result: $Utils.Optional | number + } + } + } } } & { other: { @@ -1468,6 +1738,9 @@ export namespace Prisma { topicClosure?: TopicClosureOmit topicWatch?: TopicWatchOmit topicReadState?: TopicReadStateOmit + publicForumCategory?: PublicForumCategoryOmit + publicForumRoleMember?: PublicForumRoleMemberOmit + publicForumImportRecord?: PublicForumImportRecordOmit } /* Types for Logging */ @@ -6301,43 +6574,43 @@ export namespace Prisma { where?: PostReactionWhereInput /** * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} - * + * * Determine the order of PostReactions to fetch. */ orderBy?: PostReactionOrderByWithRelationInput | PostReactionOrderByWithRelationInput[] /** * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} - * + * * Sets the start position */ cursor?: PostReactionWhereUniqueInput /** * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} - * + * * Take `±n` PostReactions from the position of the cursor. */ take?: number /** * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} - * + * * Skip the first `n` PostReactions. */ skip?: number /** * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} - * + * * Count returned PostReactions **/ _count?: true | PostReactionCountAggregateInputType /** * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} - * + * * Select which fields to find the minimum value **/ _min?: PostReactionMinAggregateInputType /** * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} - * + * * Select which fields to find the maximum value **/ _max?: PostReactionMaxAggregateInputType @@ -6527,13 +6800,13 @@ export namespace Prisma { * @example * // Get all PostReactions * const postReactions = await prisma.postReaction.findMany() - * + * * // Get first 10 PostReactions * const postReactions = await prisma.postReaction.findMany({ take: 10 }) - * + * * // Only select the `postId` * const postReactionWithPostIdOnly = await prisma.postReaction.findMany({ select: { postId: true } }) - * + * */ findMany(args?: SelectSubset>): Prisma.PrismaPromise<$Result.GetResult, T, "findMany", GlobalOmitOptions>> @@ -6547,7 +6820,7 @@ export namespace Prisma { * // ... data to create a PostReaction * } * }) - * + * */ create(args: SelectSubset>): Prisma__PostReactionClient<$Result.GetResult, T, "create", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> @@ -6561,7 +6834,7 @@ export namespace Prisma { * // ... provide data here * ] * }) - * + * */ createMany(args?: SelectSubset>): Prisma.PrismaPromise @@ -6575,7 +6848,7 @@ export namespace Prisma { * // ... provide data here * ] * }) - * + * * // Create many PostReactions and only return the `postId` * const postReactionWithPostIdOnly = await prisma.postReaction.createManyAndReturn({ * select: { postId: true }, @@ -6585,7 +6858,7 @@ export namespace Prisma { * }) * Note, that providing `undefined` is treated as the value not being there. * Read more here: https://pris.ly/d/null-undefined - * + * */ createManyAndReturn(args?: SelectSubset>): Prisma.PrismaPromise<$Result.GetResult, T, "createManyAndReturn", GlobalOmitOptions>> @@ -6599,7 +6872,7 @@ export namespace Prisma { * // ... filter to delete one PostReaction * } * }) - * + * */ delete(args: SelectSubset>): Prisma__PostReactionClient<$Result.GetResult, T, "delete", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> @@ -6616,7 +6889,7 @@ export namespace Prisma { * // ... provide data here * } * }) - * + * */ update(args: SelectSubset>): Prisma__PostReactionClient<$Result.GetResult, T, "update", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> @@ -6630,7 +6903,7 @@ export namespace Prisma { * // ... provide filter here * } * }) - * + * */ deleteMany(args?: SelectSubset>): Prisma.PrismaPromise @@ -6649,7 +6922,7 @@ export namespace Prisma { * // ... provide data here * } * }) - * + * */ updateMany(args: SelectSubset>): Prisma.PrismaPromise @@ -6666,7 +6939,7 @@ export namespace Prisma { * // ... provide data here * ] * }) - * + * * // Update zero or more PostReactions and only return the `postId` * const postReactionWithPostIdOnly = await prisma.postReaction.updateManyAndReturn({ * select: { postId: true }, @@ -6679,7 +6952,7 @@ export namespace Prisma { * }) * Note, that providing `undefined` is treated as the value not being there. * Read more here: https://pris.ly/d/null-undefined - * + * */ updateManyAndReturn(args: SelectSubset>): Prisma.PrismaPromise<$Result.GetResult, T, "updateManyAndReturn", GlobalOmitOptions>> @@ -6768,7 +7041,7 @@ export namespace Prisma { * _all: true * }, * }) - * + * **/ groupBy< T extends PostReactionGroupByArgs, @@ -6878,7 +7151,7 @@ export namespace Prisma { readonly createdAt: FieldRef<"PostReaction", 'DateTime'> readonly updatedAt: FieldRef<"PostReaction", 'DateTime'> } - + // Custom InputTypes /** @@ -6947,31 +7220,31 @@ export namespace Prisma { where?: PostReactionWhereInput /** * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} - * + * * Determine the order of PostReactions to fetch. */ orderBy?: PostReactionOrderByWithRelationInput | PostReactionOrderByWithRelationInput[] /** * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} - * + * * Sets the position for searching for PostReactions. */ cursor?: PostReactionWhereUniqueInput /** * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} - * + * * Take `±n` PostReactions from the position of the cursor. */ take?: number /** * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} - * + * * Skip the first `n` PostReactions. */ skip?: number /** * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} - * + * * Filter by unique combinations of PostReactions. */ distinct?: PostReactionScalarFieldEnum | PostReactionScalarFieldEnum[] @@ -6999,31 +7272,31 @@ export namespace Prisma { where?: PostReactionWhereInput /** * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} - * + * * Determine the order of PostReactions to fetch. */ orderBy?: PostReactionOrderByWithRelationInput | PostReactionOrderByWithRelationInput[] /** * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} - * + * * Sets the position for searching for PostReactions. */ cursor?: PostReactionWhereUniqueInput /** * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} - * + * * Take `±n` PostReactions from the position of the cursor. */ take?: number /** * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} - * + * * Skip the first `n` PostReactions. */ skip?: number /** * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} - * + * * Filter by unique combinations of PostReactions. */ distinct?: PostReactionScalarFieldEnum | PostReactionScalarFieldEnum[] @@ -7051,31 +7324,31 @@ export namespace Prisma { where?: PostReactionWhereInput /** * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} - * + * * Determine the order of PostReactions to fetch. */ orderBy?: PostReactionOrderByWithRelationInput | PostReactionOrderByWithRelationInput[] /** * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} - * + * * Sets the position for listing PostReactions. */ cursor?: PostReactionWhereUniqueInput /** * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} - * + * * Take `±n` PostReactions from the position of the cursor. */ take?: number /** * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} - * + * * Skip the first `n` PostReactions. */ skip?: number /** * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} - * + * * Filter by unique combinations of PostReactions. */ distinct?: PostReactionScalarFieldEnum | PostReactionScalarFieldEnum[] @@ -10463,70 +10736,3131 @@ export namespace Prisma { /** - * Enums + * Model PublicForumCategory */ - export const TransactionIsolationLevel: { - ReadUncommitted: 'ReadUncommitted', - ReadCommitted: 'ReadCommitted', - RepeatableRead: 'RepeatableRead', - Serializable: 'Serializable' - }; + export type AggregatePublicForumCategory = { + _count: PublicForumCategoryCountAggregateOutputType | null + _avg: PublicForumCategoryAvgAggregateOutputType | null + _sum: PublicForumCategorySumAggregateOutputType | null + _min: PublicForumCategoryMinAggregateOutputType | null + _max: PublicForumCategoryMaxAggregateOutputType | null + } - export type TransactionIsolationLevel = (typeof TransactionIsolationLevel)[keyof typeof TransactionIsolationLevel] + export type PublicForumCategoryAvgAggregateOutputType = { + sortOrder: number | null + legacyId: number | null + } + export type PublicForumCategorySumAggregateOutputType = { + sortOrder: number | null + legacyId: number | null + } - export const TopicScalarFieldEnum: { - id: 'id', - parentTopicId: 'parentTopicId', - challengeId: 'challengeId', - roleName: 'roleName', - title: 'title', - isAnnouncement: 'isAnnouncement', - locked: 'locked', - lockedAt: 'lockedAt', - lockedByMemberId: 'lockedByMemberId', - authorMemberId: 'authorMemberId', - authorHandle: 'authorHandle', - createdAt: 'createdAt', - updatedAt: 'updatedAt', - deletedAt: 'deletedAt', - deletedByMemberId: 'deletedByMemberId' - }; + export type PublicForumCategoryMinAggregateOutputType = { + topicId: string | null + description: string | null + displayAs: string | null + sortOrder: number | null + legacyId: number | null + legacySlug: string | null + } - export type TopicScalarFieldEnum = (typeof TopicScalarFieldEnum)[keyof typeof TopicScalarFieldEnum] + export type PublicForumCategoryMaxAggregateOutputType = { + topicId: string | null + description: string | null + displayAs: string | null + sortOrder: number | null + legacyId: number | null + legacySlug: string | null + } + export type PublicForumCategoryCountAggregateOutputType = { + topicId: number + description: number + displayAs: number + sortOrder: number + readRoles: number + createRoles: number + replyRoles: number + legacyId: number + legacySlug: number + source: number + _all: number + } - export const MemberBanScalarFieldEnum: { - id: 'id', - memberId: 'memberId', - createdAt: 'createdAt', - createdByMemberId: 'createdByMemberId', - removedAt: 'removedAt', - removedByMemberId: 'removedByMemberId' - }; - export type MemberBanScalarFieldEnum = (typeof MemberBanScalarFieldEnum)[keyof typeof MemberBanScalarFieldEnum] + export type PublicForumCategoryAvgAggregateInputType = { + sortOrder?: true + legacyId?: true + } + export type PublicForumCategorySumAggregateInputType = { + sortOrder?: true + legacyId?: true + } - export const IpBanScalarFieldEnum: { - id: 'id', - ipAddress: 'ipAddress', - createdAt: 'createdAt', - createdByMemberId: 'createdByMemberId', - removedAt: 'removedAt', - removedByMemberId: 'removedByMemberId' - }; + export type PublicForumCategoryMinAggregateInputType = { + topicId?: true + description?: true + displayAs?: true + sortOrder?: true + legacyId?: true + legacySlug?: true + } - export type IpBanScalarFieldEnum = (typeof IpBanScalarFieldEnum)[keyof typeof IpBanScalarFieldEnum] + export type PublicForumCategoryMaxAggregateInputType = { + topicId?: true + description?: true + displayAs?: true + sortOrder?: true + legacyId?: true + legacySlug?: true + } + export type PublicForumCategoryCountAggregateInputType = { + topicId?: true + description?: true + displayAs?: true + sortOrder?: true + readRoles?: true + createRoles?: true + replyRoles?: true + legacyId?: true + legacySlug?: true + source?: true + _all?: true + } - export const PostScalarFieldEnum: { - id: 'id', - topicId: 'topicId', - parentType: 'parentType', - parentId: 'parentId', - authorMemberId: 'authorMemberId', + export type PublicForumCategoryAggregateArgs = { + /** + * Filter which PublicForumCategory to aggregate. + */ + where?: PublicForumCategoryWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of PublicForumCategories to fetch. + */ + orderBy?: PublicForumCategoryOrderByWithRelationInput | PublicForumCategoryOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the start position + */ + cursor?: PublicForumCategoryWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` PublicForumCategories from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` PublicForumCategories. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Count returned PublicForumCategories + **/ + _count?: true | PublicForumCategoryCountAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to average + **/ + _avg?: PublicForumCategoryAvgAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to sum + **/ + _sum?: PublicForumCategorySumAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to find the minimum value + **/ + _min?: PublicForumCategoryMinAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to find the maximum value + **/ + _max?: PublicForumCategoryMaxAggregateInputType + } + + export type GetPublicForumCategoryAggregateType = { + [P in keyof T & keyof AggregatePublicForumCategory]: P extends '_count' | 'count' + ? T[P] extends true + ? number + : GetScalarType + : GetScalarType + } + + + + + export type PublicForumCategoryGroupByArgs = { + where?: PublicForumCategoryWhereInput + orderBy?: PublicForumCategoryOrderByWithAggregationInput | PublicForumCategoryOrderByWithAggregationInput[] + by: PublicForumCategoryScalarFieldEnum[] | PublicForumCategoryScalarFieldEnum + having?: PublicForumCategoryScalarWhereWithAggregatesInput + take?: number + skip?: number + _count?: PublicForumCategoryCountAggregateInputType | true + _avg?: PublicForumCategoryAvgAggregateInputType + _sum?: PublicForumCategorySumAggregateInputType + _min?: PublicForumCategoryMinAggregateInputType + _max?: PublicForumCategoryMaxAggregateInputType + } + + export type PublicForumCategoryGroupByOutputType = { + topicId: string + description: string + displayAs: string + sortOrder: number + readRoles: string[] + createRoles: string[] + replyRoles: string[] + legacyId: number + legacySlug: string + source: JsonValue + _count: PublicForumCategoryCountAggregateOutputType | null + _avg: PublicForumCategoryAvgAggregateOutputType | null + _sum: PublicForumCategorySumAggregateOutputType | null + _min: PublicForumCategoryMinAggregateOutputType | null + _max: PublicForumCategoryMaxAggregateOutputType | null + } + + type GetPublicForumCategoryGroupByPayload = Prisma.PrismaPromise< + Array< + PickEnumerable & + { + [P in ((keyof T) & (keyof PublicForumCategoryGroupByOutputType))]: P extends '_count' + ? T[P] extends boolean + ? number + : GetScalarType + : GetScalarType + } + > + > + + + export type PublicForumCategorySelect = $Extensions.GetSelect<{ + topicId?: boolean + description?: boolean + displayAs?: boolean + sortOrder?: boolean + readRoles?: boolean + createRoles?: boolean + replyRoles?: boolean + legacyId?: boolean + legacySlug?: boolean + source?: boolean + }, ExtArgs["result"]["publicForumCategory"]> + + export type PublicForumCategorySelectCreateManyAndReturn = $Extensions.GetSelect<{ + topicId?: boolean + description?: boolean + displayAs?: boolean + sortOrder?: boolean + readRoles?: boolean + createRoles?: boolean + replyRoles?: boolean + legacyId?: boolean + legacySlug?: boolean + source?: boolean + }, ExtArgs["result"]["publicForumCategory"]> + + export type PublicForumCategorySelectUpdateManyAndReturn = $Extensions.GetSelect<{ + topicId?: boolean + description?: boolean + displayAs?: boolean + sortOrder?: boolean + readRoles?: boolean + createRoles?: boolean + replyRoles?: boolean + legacyId?: boolean + legacySlug?: boolean + source?: boolean + }, ExtArgs["result"]["publicForumCategory"]> + + export type PublicForumCategorySelectScalar = { + topicId?: boolean + description?: boolean + displayAs?: boolean + sortOrder?: boolean + readRoles?: boolean + createRoles?: boolean + replyRoles?: boolean + legacyId?: boolean + legacySlug?: boolean + source?: boolean + } + + export type PublicForumCategoryOmit = $Extensions.GetOmit<"topicId" | "description" | "displayAs" | "sortOrder" | "readRoles" | "createRoles" | "replyRoles" | "legacyId" | "legacySlug" | "source", ExtArgs["result"]["publicForumCategory"]> + + export type $PublicForumCategoryPayload = { + name: "PublicForumCategory" + objects: {} + scalars: $Extensions.GetPayloadResult<{ + topicId: string + description: string + displayAs: string + sortOrder: number + readRoles: string[] + createRoles: string[] + replyRoles: string[] + legacyId: number + legacySlug: string + source: Prisma.JsonValue + }, ExtArgs["result"]["publicForumCategory"]> + composites: {} + } + + type PublicForumCategoryGetPayload = $Result.GetResult + + type PublicForumCategoryCountArgs = + Omit & { + select?: PublicForumCategoryCountAggregateInputType | true + } + + export interface PublicForumCategoryDelegate { + [K: symbol]: { types: Prisma.TypeMap['model']['PublicForumCategory'], meta: { name: 'PublicForumCategory' } } + /** + * Find zero or one PublicForumCategory that matches the filter. + * @param {PublicForumCategoryFindUniqueArgs} args - Arguments to find a PublicForumCategory + * @example + * // Get one PublicForumCategory + * const publicForumCategory = await prisma.publicForumCategory.findUnique({ + * where: { + * // ... provide filter here + * } + * }) + */ + findUnique(args: SelectSubset>): Prisma__PublicForumCategoryClient<$Result.GetResult, T, "findUnique", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> + + /** + * Find one PublicForumCategory that matches the filter or throw an error with `error.code='P2025'` + * if no matches were found. + * @param {PublicForumCategoryFindUniqueOrThrowArgs} args - Arguments to find a PublicForumCategory + * @example + * // Get one PublicForumCategory + * const publicForumCategory = await prisma.publicForumCategory.findUniqueOrThrow({ + * where: { + * // ... provide filter here + * } + * }) + */ + findUniqueOrThrow(args: SelectSubset>): Prisma__PublicForumCategoryClient<$Result.GetResult, T, "findUniqueOrThrow", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Find the first PublicForumCategory that matches the filter. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {PublicForumCategoryFindFirstArgs} args - Arguments to find a PublicForumCategory + * @example + * // Get one PublicForumCategory + * const publicForumCategory = await prisma.publicForumCategory.findFirst({ + * where: { + * // ... provide filter here + * } + * }) + */ + findFirst(args?: SelectSubset>): Prisma__PublicForumCategoryClient<$Result.GetResult, T, "findFirst", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> + + /** + * Find the first PublicForumCategory that matches the filter or + * throw `PrismaKnownClientError` with `P2025` code if no matches were found. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {PublicForumCategoryFindFirstOrThrowArgs} args - Arguments to find a PublicForumCategory + * @example + * // Get one PublicForumCategory + * const publicForumCategory = await prisma.publicForumCategory.findFirstOrThrow({ + * where: { + * // ... provide filter here + * } + * }) + */ + findFirstOrThrow(args?: SelectSubset>): Prisma__PublicForumCategoryClient<$Result.GetResult, T, "findFirstOrThrow", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Find zero or more PublicForumCategories that matches the filter. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {PublicForumCategoryFindManyArgs} args - Arguments to filter and select certain fields only. + * @example + * // Get all PublicForumCategories + * const publicForumCategories = await prisma.publicForumCategory.findMany() + * + * // Get first 10 PublicForumCategories + * const publicForumCategories = await prisma.publicForumCategory.findMany({ take: 10 }) + * + * // Only select the `topicId` + * const publicForumCategoryWithTopicIdOnly = await prisma.publicForumCategory.findMany({ select: { topicId: true } }) + * + */ + findMany(args?: SelectSubset>): Prisma.PrismaPromise<$Result.GetResult, T, "findMany", GlobalOmitOptions>> + + /** + * Create a PublicForumCategory. + * @param {PublicForumCategoryCreateArgs} args - Arguments to create a PublicForumCategory. + * @example + * // Create one PublicForumCategory + * const PublicForumCategory = await prisma.publicForumCategory.create({ + * data: { + * // ... data to create a PublicForumCategory + * } + * }) + * + */ + create(args: SelectSubset>): Prisma__PublicForumCategoryClient<$Result.GetResult, T, "create", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Create many PublicForumCategories. + * @param {PublicForumCategoryCreateManyArgs} args - Arguments to create many PublicForumCategories. + * @example + * // Create many PublicForumCategories + * const publicForumCategory = await prisma.publicForumCategory.createMany({ + * data: [ + * // ... provide data here + * ] + * }) + * + */ + createMany(args?: SelectSubset>): Prisma.PrismaPromise + + /** + * Create many PublicForumCategories and returns the data saved in the database. + * @param {PublicForumCategoryCreateManyAndReturnArgs} args - Arguments to create many PublicForumCategories. + * @example + * // Create many PublicForumCategories + * const publicForumCategory = await prisma.publicForumCategory.createManyAndReturn({ + * data: [ + * // ... provide data here + * ] + * }) + * + * // Create many PublicForumCategories and only return the `topicId` + * const publicForumCategoryWithTopicIdOnly = await prisma.publicForumCategory.createManyAndReturn({ + * select: { topicId: true }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + createManyAndReturn(args?: SelectSubset>): Prisma.PrismaPromise<$Result.GetResult, T, "createManyAndReturn", GlobalOmitOptions>> + + /** + * Delete a PublicForumCategory. + * @param {PublicForumCategoryDeleteArgs} args - Arguments to delete one PublicForumCategory. + * @example + * // Delete one PublicForumCategory + * const PublicForumCategory = await prisma.publicForumCategory.delete({ + * where: { + * // ... filter to delete one PublicForumCategory + * } + * }) + * + */ + delete(args: SelectSubset>): Prisma__PublicForumCategoryClient<$Result.GetResult, T, "delete", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Update one PublicForumCategory. + * @param {PublicForumCategoryUpdateArgs} args - Arguments to update one PublicForumCategory. + * @example + * // Update one PublicForumCategory + * const publicForumCategory = await prisma.publicForumCategory.update({ + * where: { + * // ... provide filter here + * }, + * data: { + * // ... provide data here + * } + * }) + * + */ + update(args: SelectSubset>): Prisma__PublicForumCategoryClient<$Result.GetResult, T, "update", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Delete zero or more PublicForumCategories. + * @param {PublicForumCategoryDeleteManyArgs} args - Arguments to filter PublicForumCategories to delete. + * @example + * // Delete a few PublicForumCategories + * const { count } = await prisma.publicForumCategory.deleteMany({ + * where: { + * // ... provide filter here + * } + * }) + * + */ + deleteMany(args?: SelectSubset>): Prisma.PrismaPromise + + /** + * Update zero or more PublicForumCategories. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {PublicForumCategoryUpdateManyArgs} args - Arguments to update one or more rows. + * @example + * // Update many PublicForumCategories + * const publicForumCategory = await prisma.publicForumCategory.updateMany({ + * where: { + * // ... provide filter here + * }, + * data: { + * // ... provide data here + * } + * }) + * + */ + updateMany(args: SelectSubset>): Prisma.PrismaPromise + + /** + * Update zero or more PublicForumCategories and returns the data updated in the database. + * @param {PublicForumCategoryUpdateManyAndReturnArgs} args - Arguments to update many PublicForumCategories. + * @example + * // Update many PublicForumCategories + * const publicForumCategory = await prisma.publicForumCategory.updateManyAndReturn({ + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * + * // Update zero or more PublicForumCategories and only return the `topicId` + * const publicForumCategoryWithTopicIdOnly = await prisma.publicForumCategory.updateManyAndReturn({ + * select: { topicId: true }, + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + updateManyAndReturn(args: SelectSubset>): Prisma.PrismaPromise<$Result.GetResult, T, "updateManyAndReturn", GlobalOmitOptions>> + + /** + * Create or update one PublicForumCategory. + * @param {PublicForumCategoryUpsertArgs} args - Arguments to update or create a PublicForumCategory. + * @example + * // Update or create a PublicForumCategory + * const publicForumCategory = await prisma.publicForumCategory.upsert({ + * create: { + * // ... data to create a PublicForumCategory + * }, + * update: { + * // ... in case it already exists, update + * }, + * where: { + * // ... the filter for the PublicForumCategory we want to update + * } + * }) + */ + upsert(args: SelectSubset>): Prisma__PublicForumCategoryClient<$Result.GetResult, T, "upsert", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + + /** + * Count the number of PublicForumCategories. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {PublicForumCategoryCountArgs} args - Arguments to filter PublicForumCategories to count. + * @example + * // Count the number of PublicForumCategories + * const count = await prisma.publicForumCategory.count({ + * where: { + * // ... the filter for the PublicForumCategories we want to count + * } + * }) + **/ + count( + args?: Subset, + ): Prisma.PrismaPromise< + T extends $Utils.Record<'select', any> + ? T['select'] extends true + ? number + : GetScalarType + : number + > + + /** + * Allows you to perform aggregations operations on a PublicForumCategory. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {PublicForumCategoryAggregateArgs} args - Select which aggregations you would like to apply and on what fields. + * @example + * // Ordered by age ascending + * // Where email contains prisma.io + * // Limited to the 10 users + * const aggregations = await prisma.user.aggregate({ + * _avg: { + * age: true, + * }, + * where: { + * email: { + * contains: "prisma.io", + * }, + * }, + * orderBy: { + * age: "asc", + * }, + * take: 10, + * }) + **/ + aggregate(args: Subset): Prisma.PrismaPromise> + + /** + * Group by PublicForumCategory. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {PublicForumCategoryGroupByArgs} args - Group by arguments. + * @example + * // Group by city, order by createdAt, get count + * const result = await prisma.user.groupBy({ + * by: ['city', 'createdAt'], + * orderBy: { + * createdAt: true + * }, + * _count: { + * _all: true + * }, + * }) + * + **/ + groupBy< + T extends PublicForumCategoryGroupByArgs, + HasSelectOrTake extends Or< + Extends<'skip', Keys>, + Extends<'take', Keys> + >, + OrderByArg extends True extends HasSelectOrTake + ? { orderBy: PublicForumCategoryGroupByArgs['orderBy'] } + : { orderBy?: PublicForumCategoryGroupByArgs['orderBy'] }, + OrderFields extends ExcludeUnderscoreKeys>>, + ByFields extends MaybeTupleToUnion, + ByValid extends Has, + HavingFields extends GetHavingFields, + HavingValid extends Has, + ByEmpty extends T['by'] extends never[] ? True : False, + InputErrors extends ByEmpty extends True + ? `Error: "by" must not be empty.` + : HavingValid extends False + ? { + [P in HavingFields]: P extends ByFields + ? never + : P extends string + ? `Error: Field "${P}" used in "having" needs to be provided in "by".` + : [ + Error, + 'Field ', + P, + ` in "having" needs to be provided in "by"`, + ] + }[HavingFields] + : 'take' extends Keys + ? 'orderBy' extends Keys + ? ByValid extends True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` + }[OrderFields] + : 'Error: If you provide "take", you also need to provide "orderBy"' + : 'skip' extends Keys + ? 'orderBy' extends Keys + ? ByValid extends True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` + }[OrderFields] + : 'Error: If you provide "skip", you also need to provide "orderBy"' + : ByValid extends True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` + }[OrderFields] + >(args: SubsetIntersection & InputErrors): {} extends InputErrors ? GetPublicForumCategoryGroupByPayload : Prisma.PrismaPromise + /** + * Fields of the PublicForumCategory model + */ + readonly fields: PublicForumCategoryFieldRefs; + } + + /** + * The delegate class that acts as a "Promise-like" for PublicForumCategory. + * Why is this prefixed with `Prisma__`? + * Because we want to prevent naming conflicts as mentioned in + * https://github.com/prisma/prisma-client-js/issues/707 + */ + export interface Prisma__PublicForumCategoryClient extends Prisma.PrismaPromise { + readonly [Symbol.toStringTag]: "PrismaPromise" + /** + * Attaches callbacks for the resolution and/or rejection of the Promise. + * @param onfulfilled The callback to execute when the Promise is resolved. + * @param onrejected The callback to execute when the Promise is rejected. + * @returns A Promise for the completion of which ever callback is executed. + */ + then(onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null): $Utils.JsPromise + /** + * Attaches a callback for only the rejection of the Promise. + * @param onrejected The callback to execute when the Promise is rejected. + * @returns A Promise for the completion of the callback. + */ + catch(onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null): $Utils.JsPromise + /** + * Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The + * resolved value cannot be modified from the callback. + * @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected). + * @returns A Promise for the completion of the callback. + */ + finally(onfinally?: (() => void) | undefined | null): $Utils.JsPromise + } + + + + + /** + * Fields of the PublicForumCategory model + */ + interface PublicForumCategoryFieldRefs { + readonly topicId: FieldRef<"PublicForumCategory", 'String'> + readonly description: FieldRef<"PublicForumCategory", 'String'> + readonly displayAs: FieldRef<"PublicForumCategory", 'String'> + readonly sortOrder: FieldRef<"PublicForumCategory", 'Int'> + readonly readRoles: FieldRef<"PublicForumCategory", 'String[]'> + readonly createRoles: FieldRef<"PublicForumCategory", 'String[]'> + readonly replyRoles: FieldRef<"PublicForumCategory", 'String[]'> + readonly legacyId: FieldRef<"PublicForumCategory", 'Int'> + readonly legacySlug: FieldRef<"PublicForumCategory", 'String'> + readonly source: FieldRef<"PublicForumCategory", 'Json'> + } + + + // Custom InputTypes + /** + * PublicForumCategory findUnique + */ + export type PublicForumCategoryFindUniqueArgs = { + /** + * Select specific fields to fetch from the PublicForumCategory + */ + select?: PublicForumCategorySelect | null + /** + * Omit specific fields from the PublicForumCategory + */ + omit?: PublicForumCategoryOmit | null + /** + * Filter, which PublicForumCategory to fetch. + */ + where: PublicForumCategoryWhereUniqueInput + } + + /** + * PublicForumCategory findUniqueOrThrow + */ + export type PublicForumCategoryFindUniqueOrThrowArgs = { + /** + * Select specific fields to fetch from the PublicForumCategory + */ + select?: PublicForumCategorySelect | null + /** + * Omit specific fields from the PublicForumCategory + */ + omit?: PublicForumCategoryOmit | null + /** + * Filter, which PublicForumCategory to fetch. + */ + where: PublicForumCategoryWhereUniqueInput + } + + /** + * PublicForumCategory findFirst + */ + export type PublicForumCategoryFindFirstArgs = { + /** + * Select specific fields to fetch from the PublicForumCategory + */ + select?: PublicForumCategorySelect | null + /** + * Omit specific fields from the PublicForumCategory + */ + omit?: PublicForumCategoryOmit | null + /** + * Filter, which PublicForumCategory to fetch. + */ + where?: PublicForumCategoryWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of PublicForumCategories to fetch. + */ + orderBy?: PublicForumCategoryOrderByWithRelationInput | PublicForumCategoryOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for searching for PublicForumCategories. + */ + cursor?: PublicForumCategoryWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` PublicForumCategories from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` PublicForumCategories. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of PublicForumCategories. + */ + distinct?: PublicForumCategoryScalarFieldEnum | PublicForumCategoryScalarFieldEnum[] + } + + /** + * PublicForumCategory findFirstOrThrow + */ + export type PublicForumCategoryFindFirstOrThrowArgs = { + /** + * Select specific fields to fetch from the PublicForumCategory + */ + select?: PublicForumCategorySelect | null + /** + * Omit specific fields from the PublicForumCategory + */ + omit?: PublicForumCategoryOmit | null + /** + * Filter, which PublicForumCategory to fetch. + */ + where?: PublicForumCategoryWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of PublicForumCategories to fetch. + */ + orderBy?: PublicForumCategoryOrderByWithRelationInput | PublicForumCategoryOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for searching for PublicForumCategories. + */ + cursor?: PublicForumCategoryWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` PublicForumCategories from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` PublicForumCategories. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of PublicForumCategories. + */ + distinct?: PublicForumCategoryScalarFieldEnum | PublicForumCategoryScalarFieldEnum[] + } + + /** + * PublicForumCategory findMany + */ + export type PublicForumCategoryFindManyArgs = { + /** + * Select specific fields to fetch from the PublicForumCategory + */ + select?: PublicForumCategorySelect | null + /** + * Omit specific fields from the PublicForumCategory + */ + omit?: PublicForumCategoryOmit | null + /** + * Filter, which PublicForumCategories to fetch. + */ + where?: PublicForumCategoryWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of PublicForumCategories to fetch. + */ + orderBy?: PublicForumCategoryOrderByWithRelationInput | PublicForumCategoryOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for listing PublicForumCategories. + */ + cursor?: PublicForumCategoryWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` PublicForumCategories from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` PublicForumCategories. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of PublicForumCategories. + */ + distinct?: PublicForumCategoryScalarFieldEnum | PublicForumCategoryScalarFieldEnum[] + } + + /** + * PublicForumCategory create + */ + export type PublicForumCategoryCreateArgs = { + /** + * Select specific fields to fetch from the PublicForumCategory + */ + select?: PublicForumCategorySelect | null + /** + * Omit specific fields from the PublicForumCategory + */ + omit?: PublicForumCategoryOmit | null + /** + * The data needed to create a PublicForumCategory. + */ + data: XOR + } + + /** + * PublicForumCategory createMany + */ + export type PublicForumCategoryCreateManyArgs = { + /** + * The data used to create many PublicForumCategories. + */ + data: PublicForumCategoryCreateManyInput | PublicForumCategoryCreateManyInput[] + skipDuplicates?: boolean + } + + /** + * PublicForumCategory createManyAndReturn + */ + export type PublicForumCategoryCreateManyAndReturnArgs = { + /** + * Select specific fields to fetch from the PublicForumCategory + */ + select?: PublicForumCategorySelectCreateManyAndReturn | null + /** + * Omit specific fields from the PublicForumCategory + */ + omit?: PublicForumCategoryOmit | null + /** + * The data used to create many PublicForumCategories. + */ + data: PublicForumCategoryCreateManyInput | PublicForumCategoryCreateManyInput[] + skipDuplicates?: boolean + } + + /** + * PublicForumCategory update + */ + export type PublicForumCategoryUpdateArgs = { + /** + * Select specific fields to fetch from the PublicForumCategory + */ + select?: PublicForumCategorySelect | null + /** + * Omit specific fields from the PublicForumCategory + */ + omit?: PublicForumCategoryOmit | null + /** + * The data needed to update a PublicForumCategory. + */ + data: XOR + /** + * Choose, which PublicForumCategory to update. + */ + where: PublicForumCategoryWhereUniqueInput + } + + /** + * PublicForumCategory updateMany + */ + export type PublicForumCategoryUpdateManyArgs = { + /** + * The data used to update PublicForumCategories. + */ + data: XOR + /** + * Filter which PublicForumCategories to update + */ + where?: PublicForumCategoryWhereInput + /** + * Limit how many PublicForumCategories to update. + */ + limit?: number + } + + /** + * PublicForumCategory updateManyAndReturn + */ + export type PublicForumCategoryUpdateManyAndReturnArgs = { + /** + * Select specific fields to fetch from the PublicForumCategory + */ + select?: PublicForumCategorySelectUpdateManyAndReturn | null + /** + * Omit specific fields from the PublicForumCategory + */ + omit?: PublicForumCategoryOmit | null + /** + * The data used to update PublicForumCategories. + */ + data: XOR + /** + * Filter which PublicForumCategories to update + */ + where?: PublicForumCategoryWhereInput + /** + * Limit how many PublicForumCategories to update. + */ + limit?: number + } + + /** + * PublicForumCategory upsert + */ + export type PublicForumCategoryUpsertArgs = { + /** + * Select specific fields to fetch from the PublicForumCategory + */ + select?: PublicForumCategorySelect | null + /** + * Omit specific fields from the PublicForumCategory + */ + omit?: PublicForumCategoryOmit | null + /** + * The filter to search for the PublicForumCategory to update in case it exists. + */ + where: PublicForumCategoryWhereUniqueInput + /** + * In case the PublicForumCategory found by the `where` argument doesn't exist, create a new PublicForumCategory with this data. + */ + create: XOR + /** + * In case the PublicForumCategory was found with the provided `where` argument, update it with this data. + */ + update: XOR + } + + /** + * PublicForumCategory delete + */ + export type PublicForumCategoryDeleteArgs = { + /** + * Select specific fields to fetch from the PublicForumCategory + */ + select?: PublicForumCategorySelect | null + /** + * Omit specific fields from the PublicForumCategory + */ + omit?: PublicForumCategoryOmit | null + /** + * Filter which PublicForumCategory to delete. + */ + where: PublicForumCategoryWhereUniqueInput + } + + /** + * PublicForumCategory deleteMany + */ + export type PublicForumCategoryDeleteManyArgs = { + /** + * Filter which PublicForumCategories to delete + */ + where?: PublicForumCategoryWhereInput + /** + * Limit how many PublicForumCategories to delete. + */ + limit?: number + } + + /** + * PublicForumCategory without action + */ + export type PublicForumCategoryDefaultArgs = { + /** + * Select specific fields to fetch from the PublicForumCategory + */ + select?: PublicForumCategorySelect | null + /** + * Omit specific fields from the PublicForumCategory + */ + omit?: PublicForumCategoryOmit | null + } + + + /** + * Model PublicForumRoleMember + */ + + export type AggregatePublicForumRoleMember = { + _count: PublicForumRoleMemberCountAggregateOutputType | null + _min: PublicForumRoleMemberMinAggregateOutputType | null + _max: PublicForumRoleMemberMaxAggregateOutputType | null + } + + export type PublicForumRoleMemberMinAggregateOutputType = { + roleName: string | null + memberId: string | null + } + + export type PublicForumRoleMemberMaxAggregateOutputType = { + roleName: string | null + memberId: string | null + } + + export type PublicForumRoleMemberCountAggregateOutputType = { + roleName: number + memberId: number + _all: number + } + + + export type PublicForumRoleMemberMinAggregateInputType = { + roleName?: true + memberId?: true + } + + export type PublicForumRoleMemberMaxAggregateInputType = { + roleName?: true + memberId?: true + } + + export type PublicForumRoleMemberCountAggregateInputType = { + roleName?: true + memberId?: true + _all?: true + } + + export type PublicForumRoleMemberAggregateArgs = { + /** + * Filter which PublicForumRoleMember to aggregate. + */ + where?: PublicForumRoleMemberWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of PublicForumRoleMembers to fetch. + */ + orderBy?: PublicForumRoleMemberOrderByWithRelationInput | PublicForumRoleMemberOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the start position + */ + cursor?: PublicForumRoleMemberWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` PublicForumRoleMembers from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` PublicForumRoleMembers. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Count returned PublicForumRoleMembers + **/ + _count?: true | PublicForumRoleMemberCountAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to find the minimum value + **/ + _min?: PublicForumRoleMemberMinAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to find the maximum value + **/ + _max?: PublicForumRoleMemberMaxAggregateInputType + } + + export type GetPublicForumRoleMemberAggregateType = { + [P in keyof T & keyof AggregatePublicForumRoleMember]: P extends '_count' | 'count' + ? T[P] extends true + ? number + : GetScalarType + : GetScalarType + } + + + + + export type PublicForumRoleMemberGroupByArgs = { + where?: PublicForumRoleMemberWhereInput + orderBy?: PublicForumRoleMemberOrderByWithAggregationInput | PublicForumRoleMemberOrderByWithAggregationInput[] + by: PublicForumRoleMemberScalarFieldEnum[] | PublicForumRoleMemberScalarFieldEnum + having?: PublicForumRoleMemberScalarWhereWithAggregatesInput + take?: number + skip?: number + _count?: PublicForumRoleMemberCountAggregateInputType | true + _min?: PublicForumRoleMemberMinAggregateInputType + _max?: PublicForumRoleMemberMaxAggregateInputType + } + + export type PublicForumRoleMemberGroupByOutputType = { + roleName: string + memberId: string + _count: PublicForumRoleMemberCountAggregateOutputType | null + _min: PublicForumRoleMemberMinAggregateOutputType | null + _max: PublicForumRoleMemberMaxAggregateOutputType | null + } + + type GetPublicForumRoleMemberGroupByPayload = Prisma.PrismaPromise< + Array< + PickEnumerable & + { + [P in ((keyof T) & (keyof PublicForumRoleMemberGroupByOutputType))]: P extends '_count' + ? T[P] extends boolean + ? number + : GetScalarType + : GetScalarType + } + > + > + + + export type PublicForumRoleMemberSelect = $Extensions.GetSelect<{ + roleName?: boolean + memberId?: boolean + }, ExtArgs["result"]["publicForumRoleMember"]> + + export type PublicForumRoleMemberSelectCreateManyAndReturn = $Extensions.GetSelect<{ + roleName?: boolean + memberId?: boolean + }, ExtArgs["result"]["publicForumRoleMember"]> + + export type PublicForumRoleMemberSelectUpdateManyAndReturn = $Extensions.GetSelect<{ + roleName?: boolean + memberId?: boolean + }, ExtArgs["result"]["publicForumRoleMember"]> + + export type PublicForumRoleMemberSelectScalar = { + roleName?: boolean + memberId?: boolean + } + + export type PublicForumRoleMemberOmit = $Extensions.GetOmit<"roleName" | "memberId", ExtArgs["result"]["publicForumRoleMember"]> + + export type $PublicForumRoleMemberPayload = { + name: "PublicForumRoleMember" + objects: {} + scalars: $Extensions.GetPayloadResult<{ + roleName: string + memberId: string + }, ExtArgs["result"]["publicForumRoleMember"]> + composites: {} + } + + type PublicForumRoleMemberGetPayload = $Result.GetResult + + type PublicForumRoleMemberCountArgs = + Omit & { + select?: PublicForumRoleMemberCountAggregateInputType | true + } + + export interface PublicForumRoleMemberDelegate { + [K: symbol]: { types: Prisma.TypeMap['model']['PublicForumRoleMember'], meta: { name: 'PublicForumRoleMember' } } + /** + * Find zero or one PublicForumRoleMember that matches the filter. + * @param {PublicForumRoleMemberFindUniqueArgs} args - Arguments to find a PublicForumRoleMember + * @example + * // Get one PublicForumRoleMember + * const publicForumRoleMember = await prisma.publicForumRoleMember.findUnique({ + * where: { + * // ... provide filter here + * } + * }) + */ + findUnique(args: SelectSubset>): Prisma__PublicForumRoleMemberClient<$Result.GetResult, T, "findUnique", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> + + /** + * Find one PublicForumRoleMember that matches the filter or throw an error with `error.code='P2025'` + * if no matches were found. + * @param {PublicForumRoleMemberFindUniqueOrThrowArgs} args - Arguments to find a PublicForumRoleMember + * @example + * // Get one PublicForumRoleMember + * const publicForumRoleMember = await prisma.publicForumRoleMember.findUniqueOrThrow({ + * where: { + * // ... provide filter here + * } + * }) + */ + findUniqueOrThrow(args: SelectSubset>): Prisma__PublicForumRoleMemberClient<$Result.GetResult, T, "findUniqueOrThrow", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Find the first PublicForumRoleMember that matches the filter. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {PublicForumRoleMemberFindFirstArgs} args - Arguments to find a PublicForumRoleMember + * @example + * // Get one PublicForumRoleMember + * const publicForumRoleMember = await prisma.publicForumRoleMember.findFirst({ + * where: { + * // ... provide filter here + * } + * }) + */ + findFirst(args?: SelectSubset>): Prisma__PublicForumRoleMemberClient<$Result.GetResult, T, "findFirst", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> + + /** + * Find the first PublicForumRoleMember that matches the filter or + * throw `PrismaKnownClientError` with `P2025` code if no matches were found. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {PublicForumRoleMemberFindFirstOrThrowArgs} args - Arguments to find a PublicForumRoleMember + * @example + * // Get one PublicForumRoleMember + * const publicForumRoleMember = await prisma.publicForumRoleMember.findFirstOrThrow({ + * where: { + * // ... provide filter here + * } + * }) + */ + findFirstOrThrow(args?: SelectSubset>): Prisma__PublicForumRoleMemberClient<$Result.GetResult, T, "findFirstOrThrow", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Find zero or more PublicForumRoleMembers that matches the filter. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {PublicForumRoleMemberFindManyArgs} args - Arguments to filter and select certain fields only. + * @example + * // Get all PublicForumRoleMembers + * const publicForumRoleMembers = await prisma.publicForumRoleMember.findMany() + * + * // Get first 10 PublicForumRoleMembers + * const publicForumRoleMembers = await prisma.publicForumRoleMember.findMany({ take: 10 }) + * + * // Only select the `roleName` + * const publicForumRoleMemberWithRoleNameOnly = await prisma.publicForumRoleMember.findMany({ select: { roleName: true } }) + * + */ + findMany(args?: SelectSubset>): Prisma.PrismaPromise<$Result.GetResult, T, "findMany", GlobalOmitOptions>> + + /** + * Create a PublicForumRoleMember. + * @param {PublicForumRoleMemberCreateArgs} args - Arguments to create a PublicForumRoleMember. + * @example + * // Create one PublicForumRoleMember + * const PublicForumRoleMember = await prisma.publicForumRoleMember.create({ + * data: { + * // ... data to create a PublicForumRoleMember + * } + * }) + * + */ + create(args: SelectSubset>): Prisma__PublicForumRoleMemberClient<$Result.GetResult, T, "create", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Create many PublicForumRoleMembers. + * @param {PublicForumRoleMemberCreateManyArgs} args - Arguments to create many PublicForumRoleMembers. + * @example + * // Create many PublicForumRoleMembers + * const publicForumRoleMember = await prisma.publicForumRoleMember.createMany({ + * data: [ + * // ... provide data here + * ] + * }) + * + */ + createMany(args?: SelectSubset>): Prisma.PrismaPromise + + /** + * Create many PublicForumRoleMembers and returns the data saved in the database. + * @param {PublicForumRoleMemberCreateManyAndReturnArgs} args - Arguments to create many PublicForumRoleMembers. + * @example + * // Create many PublicForumRoleMembers + * const publicForumRoleMember = await prisma.publicForumRoleMember.createManyAndReturn({ + * data: [ + * // ... provide data here + * ] + * }) + * + * // Create many PublicForumRoleMembers and only return the `roleName` + * const publicForumRoleMemberWithRoleNameOnly = await prisma.publicForumRoleMember.createManyAndReturn({ + * select: { roleName: true }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + createManyAndReturn(args?: SelectSubset>): Prisma.PrismaPromise<$Result.GetResult, T, "createManyAndReturn", GlobalOmitOptions>> + + /** + * Delete a PublicForumRoleMember. + * @param {PublicForumRoleMemberDeleteArgs} args - Arguments to delete one PublicForumRoleMember. + * @example + * // Delete one PublicForumRoleMember + * const PublicForumRoleMember = await prisma.publicForumRoleMember.delete({ + * where: { + * // ... filter to delete one PublicForumRoleMember + * } + * }) + * + */ + delete(args: SelectSubset>): Prisma__PublicForumRoleMemberClient<$Result.GetResult, T, "delete", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Update one PublicForumRoleMember. + * @param {PublicForumRoleMemberUpdateArgs} args - Arguments to update one PublicForumRoleMember. + * @example + * // Update one PublicForumRoleMember + * const publicForumRoleMember = await prisma.publicForumRoleMember.update({ + * where: { + * // ... provide filter here + * }, + * data: { + * // ... provide data here + * } + * }) + * + */ + update(args: SelectSubset>): Prisma__PublicForumRoleMemberClient<$Result.GetResult, T, "update", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Delete zero or more PublicForumRoleMembers. + * @param {PublicForumRoleMemberDeleteManyArgs} args - Arguments to filter PublicForumRoleMembers to delete. + * @example + * // Delete a few PublicForumRoleMembers + * const { count } = await prisma.publicForumRoleMember.deleteMany({ + * where: { + * // ... provide filter here + * } + * }) + * + */ + deleteMany(args?: SelectSubset>): Prisma.PrismaPromise + + /** + * Update zero or more PublicForumRoleMembers. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {PublicForumRoleMemberUpdateManyArgs} args - Arguments to update one or more rows. + * @example + * // Update many PublicForumRoleMembers + * const publicForumRoleMember = await prisma.publicForumRoleMember.updateMany({ + * where: { + * // ... provide filter here + * }, + * data: { + * // ... provide data here + * } + * }) + * + */ + updateMany(args: SelectSubset>): Prisma.PrismaPromise + + /** + * Update zero or more PublicForumRoleMembers and returns the data updated in the database. + * @param {PublicForumRoleMemberUpdateManyAndReturnArgs} args - Arguments to update many PublicForumRoleMembers. + * @example + * // Update many PublicForumRoleMembers + * const publicForumRoleMember = await prisma.publicForumRoleMember.updateManyAndReturn({ + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * + * // Update zero or more PublicForumRoleMembers and only return the `roleName` + * const publicForumRoleMemberWithRoleNameOnly = await prisma.publicForumRoleMember.updateManyAndReturn({ + * select: { roleName: true }, + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + updateManyAndReturn(args: SelectSubset>): Prisma.PrismaPromise<$Result.GetResult, T, "updateManyAndReturn", GlobalOmitOptions>> + + /** + * Create or update one PublicForumRoleMember. + * @param {PublicForumRoleMemberUpsertArgs} args - Arguments to update or create a PublicForumRoleMember. + * @example + * // Update or create a PublicForumRoleMember + * const publicForumRoleMember = await prisma.publicForumRoleMember.upsert({ + * create: { + * // ... data to create a PublicForumRoleMember + * }, + * update: { + * // ... in case it already exists, update + * }, + * where: { + * // ... the filter for the PublicForumRoleMember we want to update + * } + * }) + */ + upsert(args: SelectSubset>): Prisma__PublicForumRoleMemberClient<$Result.GetResult, T, "upsert", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + + /** + * Count the number of PublicForumRoleMembers. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {PublicForumRoleMemberCountArgs} args - Arguments to filter PublicForumRoleMembers to count. + * @example + * // Count the number of PublicForumRoleMembers + * const count = await prisma.publicForumRoleMember.count({ + * where: { + * // ... the filter for the PublicForumRoleMembers we want to count + * } + * }) + **/ + count( + args?: Subset, + ): Prisma.PrismaPromise< + T extends $Utils.Record<'select', any> + ? T['select'] extends true + ? number + : GetScalarType + : number + > + + /** + * Allows you to perform aggregations operations on a PublicForumRoleMember. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {PublicForumRoleMemberAggregateArgs} args - Select which aggregations you would like to apply and on what fields. + * @example + * // Ordered by age ascending + * // Where email contains prisma.io + * // Limited to the 10 users + * const aggregations = await prisma.user.aggregate({ + * _avg: { + * age: true, + * }, + * where: { + * email: { + * contains: "prisma.io", + * }, + * }, + * orderBy: { + * age: "asc", + * }, + * take: 10, + * }) + **/ + aggregate(args: Subset): Prisma.PrismaPromise> + + /** + * Group by PublicForumRoleMember. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {PublicForumRoleMemberGroupByArgs} args - Group by arguments. + * @example + * // Group by city, order by createdAt, get count + * const result = await prisma.user.groupBy({ + * by: ['city', 'createdAt'], + * orderBy: { + * createdAt: true + * }, + * _count: { + * _all: true + * }, + * }) + * + **/ + groupBy< + T extends PublicForumRoleMemberGroupByArgs, + HasSelectOrTake extends Or< + Extends<'skip', Keys>, + Extends<'take', Keys> + >, + OrderByArg extends True extends HasSelectOrTake + ? { orderBy: PublicForumRoleMemberGroupByArgs['orderBy'] } + : { orderBy?: PublicForumRoleMemberGroupByArgs['orderBy'] }, + OrderFields extends ExcludeUnderscoreKeys>>, + ByFields extends MaybeTupleToUnion, + ByValid extends Has, + HavingFields extends GetHavingFields, + HavingValid extends Has, + ByEmpty extends T['by'] extends never[] ? True : False, + InputErrors extends ByEmpty extends True + ? `Error: "by" must not be empty.` + : HavingValid extends False + ? { + [P in HavingFields]: P extends ByFields + ? never + : P extends string + ? `Error: Field "${P}" used in "having" needs to be provided in "by".` + : [ + Error, + 'Field ', + P, + ` in "having" needs to be provided in "by"`, + ] + }[HavingFields] + : 'take' extends Keys + ? 'orderBy' extends Keys + ? ByValid extends True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` + }[OrderFields] + : 'Error: If you provide "take", you also need to provide "orderBy"' + : 'skip' extends Keys + ? 'orderBy' extends Keys + ? ByValid extends True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` + }[OrderFields] + : 'Error: If you provide "skip", you also need to provide "orderBy"' + : ByValid extends True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` + }[OrderFields] + >(args: SubsetIntersection & InputErrors): {} extends InputErrors ? GetPublicForumRoleMemberGroupByPayload : Prisma.PrismaPromise + /** + * Fields of the PublicForumRoleMember model + */ + readonly fields: PublicForumRoleMemberFieldRefs; + } + + /** + * The delegate class that acts as a "Promise-like" for PublicForumRoleMember. + * Why is this prefixed with `Prisma__`? + * Because we want to prevent naming conflicts as mentioned in + * https://github.com/prisma/prisma-client-js/issues/707 + */ + export interface Prisma__PublicForumRoleMemberClient extends Prisma.PrismaPromise { + readonly [Symbol.toStringTag]: "PrismaPromise" + /** + * Attaches callbacks for the resolution and/or rejection of the Promise. + * @param onfulfilled The callback to execute when the Promise is resolved. + * @param onrejected The callback to execute when the Promise is rejected. + * @returns A Promise for the completion of which ever callback is executed. + */ + then(onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null): $Utils.JsPromise + /** + * Attaches a callback for only the rejection of the Promise. + * @param onrejected The callback to execute when the Promise is rejected. + * @returns A Promise for the completion of the callback. + */ + catch(onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null): $Utils.JsPromise + /** + * Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The + * resolved value cannot be modified from the callback. + * @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected). + * @returns A Promise for the completion of the callback. + */ + finally(onfinally?: (() => void) | undefined | null): $Utils.JsPromise + } + + + + + /** + * Fields of the PublicForumRoleMember model + */ + interface PublicForumRoleMemberFieldRefs { + readonly roleName: FieldRef<"PublicForumRoleMember", 'String'> + readonly memberId: FieldRef<"PublicForumRoleMember", 'String'> + } + + + // Custom InputTypes + /** + * PublicForumRoleMember findUnique + */ + export type PublicForumRoleMemberFindUniqueArgs = { + /** + * Select specific fields to fetch from the PublicForumRoleMember + */ + select?: PublicForumRoleMemberSelect | null + /** + * Omit specific fields from the PublicForumRoleMember + */ + omit?: PublicForumRoleMemberOmit | null + /** + * Filter, which PublicForumRoleMember to fetch. + */ + where: PublicForumRoleMemberWhereUniqueInput + } + + /** + * PublicForumRoleMember findUniqueOrThrow + */ + export type PublicForumRoleMemberFindUniqueOrThrowArgs = { + /** + * Select specific fields to fetch from the PublicForumRoleMember + */ + select?: PublicForumRoleMemberSelect | null + /** + * Omit specific fields from the PublicForumRoleMember + */ + omit?: PublicForumRoleMemberOmit | null + /** + * Filter, which PublicForumRoleMember to fetch. + */ + where: PublicForumRoleMemberWhereUniqueInput + } + + /** + * PublicForumRoleMember findFirst + */ + export type PublicForumRoleMemberFindFirstArgs = { + /** + * Select specific fields to fetch from the PublicForumRoleMember + */ + select?: PublicForumRoleMemberSelect | null + /** + * Omit specific fields from the PublicForumRoleMember + */ + omit?: PublicForumRoleMemberOmit | null + /** + * Filter, which PublicForumRoleMember to fetch. + */ + where?: PublicForumRoleMemberWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of PublicForumRoleMembers to fetch. + */ + orderBy?: PublicForumRoleMemberOrderByWithRelationInput | PublicForumRoleMemberOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for searching for PublicForumRoleMembers. + */ + cursor?: PublicForumRoleMemberWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` PublicForumRoleMembers from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` PublicForumRoleMembers. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of PublicForumRoleMembers. + */ + distinct?: PublicForumRoleMemberScalarFieldEnum | PublicForumRoleMemberScalarFieldEnum[] + } + + /** + * PublicForumRoleMember findFirstOrThrow + */ + export type PublicForumRoleMemberFindFirstOrThrowArgs = { + /** + * Select specific fields to fetch from the PublicForumRoleMember + */ + select?: PublicForumRoleMemberSelect | null + /** + * Omit specific fields from the PublicForumRoleMember + */ + omit?: PublicForumRoleMemberOmit | null + /** + * Filter, which PublicForumRoleMember to fetch. + */ + where?: PublicForumRoleMemberWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of PublicForumRoleMembers to fetch. + */ + orderBy?: PublicForumRoleMemberOrderByWithRelationInput | PublicForumRoleMemberOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for searching for PublicForumRoleMembers. + */ + cursor?: PublicForumRoleMemberWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` PublicForumRoleMembers from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` PublicForumRoleMembers. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of PublicForumRoleMembers. + */ + distinct?: PublicForumRoleMemberScalarFieldEnum | PublicForumRoleMemberScalarFieldEnum[] + } + + /** + * PublicForumRoleMember findMany + */ + export type PublicForumRoleMemberFindManyArgs = { + /** + * Select specific fields to fetch from the PublicForumRoleMember + */ + select?: PublicForumRoleMemberSelect | null + /** + * Omit specific fields from the PublicForumRoleMember + */ + omit?: PublicForumRoleMemberOmit | null + /** + * Filter, which PublicForumRoleMembers to fetch. + */ + where?: PublicForumRoleMemberWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of PublicForumRoleMembers to fetch. + */ + orderBy?: PublicForumRoleMemberOrderByWithRelationInput | PublicForumRoleMemberOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for listing PublicForumRoleMembers. + */ + cursor?: PublicForumRoleMemberWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` PublicForumRoleMembers from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` PublicForumRoleMembers. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of PublicForumRoleMembers. + */ + distinct?: PublicForumRoleMemberScalarFieldEnum | PublicForumRoleMemberScalarFieldEnum[] + } + + /** + * PublicForumRoleMember create + */ + export type PublicForumRoleMemberCreateArgs = { + /** + * Select specific fields to fetch from the PublicForumRoleMember + */ + select?: PublicForumRoleMemberSelect | null + /** + * Omit specific fields from the PublicForumRoleMember + */ + omit?: PublicForumRoleMemberOmit | null + /** + * The data needed to create a PublicForumRoleMember. + */ + data: XOR + } + + /** + * PublicForumRoleMember createMany + */ + export type PublicForumRoleMemberCreateManyArgs = { + /** + * The data used to create many PublicForumRoleMembers. + */ + data: PublicForumRoleMemberCreateManyInput | PublicForumRoleMemberCreateManyInput[] + skipDuplicates?: boolean + } + + /** + * PublicForumRoleMember createManyAndReturn + */ + export type PublicForumRoleMemberCreateManyAndReturnArgs = { + /** + * Select specific fields to fetch from the PublicForumRoleMember + */ + select?: PublicForumRoleMemberSelectCreateManyAndReturn | null + /** + * Omit specific fields from the PublicForumRoleMember + */ + omit?: PublicForumRoleMemberOmit | null + /** + * The data used to create many PublicForumRoleMembers. + */ + data: PublicForumRoleMemberCreateManyInput | PublicForumRoleMemberCreateManyInput[] + skipDuplicates?: boolean + } + + /** + * PublicForumRoleMember update + */ + export type PublicForumRoleMemberUpdateArgs = { + /** + * Select specific fields to fetch from the PublicForumRoleMember + */ + select?: PublicForumRoleMemberSelect | null + /** + * Omit specific fields from the PublicForumRoleMember + */ + omit?: PublicForumRoleMemberOmit | null + /** + * The data needed to update a PublicForumRoleMember. + */ + data: XOR + /** + * Choose, which PublicForumRoleMember to update. + */ + where: PublicForumRoleMemberWhereUniqueInput + } + + /** + * PublicForumRoleMember updateMany + */ + export type PublicForumRoleMemberUpdateManyArgs = { + /** + * The data used to update PublicForumRoleMembers. + */ + data: XOR + /** + * Filter which PublicForumRoleMembers to update + */ + where?: PublicForumRoleMemberWhereInput + /** + * Limit how many PublicForumRoleMembers to update. + */ + limit?: number + } + + /** + * PublicForumRoleMember updateManyAndReturn + */ + export type PublicForumRoleMemberUpdateManyAndReturnArgs = { + /** + * Select specific fields to fetch from the PublicForumRoleMember + */ + select?: PublicForumRoleMemberSelectUpdateManyAndReturn | null + /** + * Omit specific fields from the PublicForumRoleMember + */ + omit?: PublicForumRoleMemberOmit | null + /** + * The data used to update PublicForumRoleMembers. + */ + data: XOR + /** + * Filter which PublicForumRoleMembers to update + */ + where?: PublicForumRoleMemberWhereInput + /** + * Limit how many PublicForumRoleMembers to update. + */ + limit?: number + } + + /** + * PublicForumRoleMember upsert + */ + export type PublicForumRoleMemberUpsertArgs = { + /** + * Select specific fields to fetch from the PublicForumRoleMember + */ + select?: PublicForumRoleMemberSelect | null + /** + * Omit specific fields from the PublicForumRoleMember + */ + omit?: PublicForumRoleMemberOmit | null + /** + * The filter to search for the PublicForumRoleMember to update in case it exists. + */ + where: PublicForumRoleMemberWhereUniqueInput + /** + * In case the PublicForumRoleMember found by the `where` argument doesn't exist, create a new PublicForumRoleMember with this data. + */ + create: XOR + /** + * In case the PublicForumRoleMember was found with the provided `where` argument, update it with this data. + */ + update: XOR + } + + /** + * PublicForumRoleMember delete + */ + export type PublicForumRoleMemberDeleteArgs = { + /** + * Select specific fields to fetch from the PublicForumRoleMember + */ + select?: PublicForumRoleMemberSelect | null + /** + * Omit specific fields from the PublicForumRoleMember + */ + omit?: PublicForumRoleMemberOmit | null + /** + * Filter which PublicForumRoleMember to delete. + */ + where: PublicForumRoleMemberWhereUniqueInput + } + + /** + * PublicForumRoleMember deleteMany + */ + export type PublicForumRoleMemberDeleteManyArgs = { + /** + * Filter which PublicForumRoleMembers to delete + */ + where?: PublicForumRoleMemberWhereInput + /** + * Limit how many PublicForumRoleMembers to delete. + */ + limit?: number + } + + /** + * PublicForumRoleMember without action + */ + export type PublicForumRoleMemberDefaultArgs = { + /** + * Select specific fields to fetch from the PublicForumRoleMember + */ + select?: PublicForumRoleMemberSelect | null + /** + * Omit specific fields from the PublicForumRoleMember + */ + omit?: PublicForumRoleMemberOmit | null + } + + + /** + * Model PublicForumImportRecord + */ + + export type AggregatePublicForumImportRecord = { + _count: PublicForumImportRecordCountAggregateOutputType | null + _min: PublicForumImportRecordMinAggregateOutputType | null + _max: PublicForumImportRecordMaxAggregateOutputType | null + } + + export type PublicForumImportRecordMinAggregateOutputType = { + entityType: string | null + sourceId: string | null + targetId: string | null + sourceHash: string | null + targetHash: string | null + createdAt: Date | null + } + + export type PublicForumImportRecordMaxAggregateOutputType = { + entityType: string | null + sourceId: string | null + targetId: string | null + sourceHash: string | null + targetHash: string | null + createdAt: Date | null + } + + export type PublicForumImportRecordCountAggregateOutputType = { + entityType: number + sourceId: number + targetId: number + sourceHash: number + targetHash: number + createdAt: number + _all: number + } + + + export type PublicForumImportRecordMinAggregateInputType = { + entityType?: true + sourceId?: true + targetId?: true + sourceHash?: true + targetHash?: true + createdAt?: true + } + + export type PublicForumImportRecordMaxAggregateInputType = { + entityType?: true + sourceId?: true + targetId?: true + sourceHash?: true + targetHash?: true + createdAt?: true + } + + export type PublicForumImportRecordCountAggregateInputType = { + entityType?: true + sourceId?: true + targetId?: true + sourceHash?: true + targetHash?: true + createdAt?: true + _all?: true + } + + export type PublicForumImportRecordAggregateArgs = { + /** + * Filter which PublicForumImportRecord to aggregate. + */ + where?: PublicForumImportRecordWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of PublicForumImportRecords to fetch. + */ + orderBy?: PublicForumImportRecordOrderByWithRelationInput | PublicForumImportRecordOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the start position + */ + cursor?: PublicForumImportRecordWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` PublicForumImportRecords from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` PublicForumImportRecords. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Count returned PublicForumImportRecords + **/ + _count?: true | PublicForumImportRecordCountAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to find the minimum value + **/ + _min?: PublicForumImportRecordMinAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to find the maximum value + **/ + _max?: PublicForumImportRecordMaxAggregateInputType + } + + export type GetPublicForumImportRecordAggregateType = { + [P in keyof T & keyof AggregatePublicForumImportRecord]: P extends '_count' | 'count' + ? T[P] extends true + ? number + : GetScalarType + : GetScalarType + } + + + + + export type PublicForumImportRecordGroupByArgs = { + where?: PublicForumImportRecordWhereInput + orderBy?: PublicForumImportRecordOrderByWithAggregationInput | PublicForumImportRecordOrderByWithAggregationInput[] + by: PublicForumImportRecordScalarFieldEnum[] | PublicForumImportRecordScalarFieldEnum + having?: PublicForumImportRecordScalarWhereWithAggregatesInput + take?: number + skip?: number + _count?: PublicForumImportRecordCountAggregateInputType | true + _min?: PublicForumImportRecordMinAggregateInputType + _max?: PublicForumImportRecordMaxAggregateInputType + } + + export type PublicForumImportRecordGroupByOutputType = { + entityType: string + sourceId: string + targetId: string + sourceHash: string + targetHash: string + createdAt: Date + _count: PublicForumImportRecordCountAggregateOutputType | null + _min: PublicForumImportRecordMinAggregateOutputType | null + _max: PublicForumImportRecordMaxAggregateOutputType | null + } + + type GetPublicForumImportRecordGroupByPayload = Prisma.PrismaPromise< + Array< + PickEnumerable & + { + [P in ((keyof T) & (keyof PublicForumImportRecordGroupByOutputType))]: P extends '_count' + ? T[P] extends boolean + ? number + : GetScalarType + : GetScalarType + } + > + > + + + export type PublicForumImportRecordSelect = $Extensions.GetSelect<{ + entityType?: boolean + sourceId?: boolean + targetId?: boolean + sourceHash?: boolean + targetHash?: boolean + createdAt?: boolean + }, ExtArgs["result"]["publicForumImportRecord"]> + + export type PublicForumImportRecordSelectCreateManyAndReturn = $Extensions.GetSelect<{ + entityType?: boolean + sourceId?: boolean + targetId?: boolean + sourceHash?: boolean + targetHash?: boolean + createdAt?: boolean + }, ExtArgs["result"]["publicForumImportRecord"]> + + export type PublicForumImportRecordSelectUpdateManyAndReturn = $Extensions.GetSelect<{ + entityType?: boolean + sourceId?: boolean + targetId?: boolean + sourceHash?: boolean + targetHash?: boolean + createdAt?: boolean + }, ExtArgs["result"]["publicForumImportRecord"]> + + export type PublicForumImportRecordSelectScalar = { + entityType?: boolean + sourceId?: boolean + targetId?: boolean + sourceHash?: boolean + targetHash?: boolean + createdAt?: boolean + } + + export type PublicForumImportRecordOmit = $Extensions.GetOmit<"entityType" | "sourceId" | "targetId" | "sourceHash" | "targetHash" | "createdAt", ExtArgs["result"]["publicForumImportRecord"]> + + export type $PublicForumImportRecordPayload = { + name: "PublicForumImportRecord" + objects: {} + scalars: $Extensions.GetPayloadResult<{ + entityType: string + sourceId: string + targetId: string + sourceHash: string + targetHash: string + createdAt: Date + }, ExtArgs["result"]["publicForumImportRecord"]> + composites: {} + } + + type PublicForumImportRecordGetPayload = $Result.GetResult + + type PublicForumImportRecordCountArgs = + Omit & { + select?: PublicForumImportRecordCountAggregateInputType | true + } + + export interface PublicForumImportRecordDelegate { + [K: symbol]: { types: Prisma.TypeMap['model']['PublicForumImportRecord'], meta: { name: 'PublicForumImportRecord' } } + /** + * Find zero or one PublicForumImportRecord that matches the filter. + * @param {PublicForumImportRecordFindUniqueArgs} args - Arguments to find a PublicForumImportRecord + * @example + * // Get one PublicForumImportRecord + * const publicForumImportRecord = await prisma.publicForumImportRecord.findUnique({ + * where: { + * // ... provide filter here + * } + * }) + */ + findUnique(args: SelectSubset>): Prisma__PublicForumImportRecordClient<$Result.GetResult, T, "findUnique", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> + + /** + * Find one PublicForumImportRecord that matches the filter or throw an error with `error.code='P2025'` + * if no matches were found. + * @param {PublicForumImportRecordFindUniqueOrThrowArgs} args - Arguments to find a PublicForumImportRecord + * @example + * // Get one PublicForumImportRecord + * const publicForumImportRecord = await prisma.publicForumImportRecord.findUniqueOrThrow({ + * where: { + * // ... provide filter here + * } + * }) + */ + findUniqueOrThrow(args: SelectSubset>): Prisma__PublicForumImportRecordClient<$Result.GetResult, T, "findUniqueOrThrow", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Find the first PublicForumImportRecord that matches the filter. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {PublicForumImportRecordFindFirstArgs} args - Arguments to find a PublicForumImportRecord + * @example + * // Get one PublicForumImportRecord + * const publicForumImportRecord = await prisma.publicForumImportRecord.findFirst({ + * where: { + * // ... provide filter here + * } + * }) + */ + findFirst(args?: SelectSubset>): Prisma__PublicForumImportRecordClient<$Result.GetResult, T, "findFirst", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> + + /** + * Find the first PublicForumImportRecord that matches the filter or + * throw `PrismaKnownClientError` with `P2025` code if no matches were found. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {PublicForumImportRecordFindFirstOrThrowArgs} args - Arguments to find a PublicForumImportRecord + * @example + * // Get one PublicForumImportRecord + * const publicForumImportRecord = await prisma.publicForumImportRecord.findFirstOrThrow({ + * where: { + * // ... provide filter here + * } + * }) + */ + findFirstOrThrow(args?: SelectSubset>): Prisma__PublicForumImportRecordClient<$Result.GetResult, T, "findFirstOrThrow", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Find zero or more PublicForumImportRecords that matches the filter. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {PublicForumImportRecordFindManyArgs} args - Arguments to filter and select certain fields only. + * @example + * // Get all PublicForumImportRecords + * const publicForumImportRecords = await prisma.publicForumImportRecord.findMany() + * + * // Get first 10 PublicForumImportRecords + * const publicForumImportRecords = await prisma.publicForumImportRecord.findMany({ take: 10 }) + * + * // Only select the `entityType` + * const publicForumImportRecordWithEntityTypeOnly = await prisma.publicForumImportRecord.findMany({ select: { entityType: true } }) + * + */ + findMany(args?: SelectSubset>): Prisma.PrismaPromise<$Result.GetResult, T, "findMany", GlobalOmitOptions>> + + /** + * Create a PublicForumImportRecord. + * @param {PublicForumImportRecordCreateArgs} args - Arguments to create a PublicForumImportRecord. + * @example + * // Create one PublicForumImportRecord + * const PublicForumImportRecord = await prisma.publicForumImportRecord.create({ + * data: { + * // ... data to create a PublicForumImportRecord + * } + * }) + * + */ + create(args: SelectSubset>): Prisma__PublicForumImportRecordClient<$Result.GetResult, T, "create", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Create many PublicForumImportRecords. + * @param {PublicForumImportRecordCreateManyArgs} args - Arguments to create many PublicForumImportRecords. + * @example + * // Create many PublicForumImportRecords + * const publicForumImportRecord = await prisma.publicForumImportRecord.createMany({ + * data: [ + * // ... provide data here + * ] + * }) + * + */ + createMany(args?: SelectSubset>): Prisma.PrismaPromise + + /** + * Create many PublicForumImportRecords and returns the data saved in the database. + * @param {PublicForumImportRecordCreateManyAndReturnArgs} args - Arguments to create many PublicForumImportRecords. + * @example + * // Create many PublicForumImportRecords + * const publicForumImportRecord = await prisma.publicForumImportRecord.createManyAndReturn({ + * data: [ + * // ... provide data here + * ] + * }) + * + * // Create many PublicForumImportRecords and only return the `entityType` + * const publicForumImportRecordWithEntityTypeOnly = await prisma.publicForumImportRecord.createManyAndReturn({ + * select: { entityType: true }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + createManyAndReturn(args?: SelectSubset>): Prisma.PrismaPromise<$Result.GetResult, T, "createManyAndReturn", GlobalOmitOptions>> + + /** + * Delete a PublicForumImportRecord. + * @param {PublicForumImportRecordDeleteArgs} args - Arguments to delete one PublicForumImportRecord. + * @example + * // Delete one PublicForumImportRecord + * const PublicForumImportRecord = await prisma.publicForumImportRecord.delete({ + * where: { + * // ... filter to delete one PublicForumImportRecord + * } + * }) + * + */ + delete(args: SelectSubset>): Prisma__PublicForumImportRecordClient<$Result.GetResult, T, "delete", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Update one PublicForumImportRecord. + * @param {PublicForumImportRecordUpdateArgs} args - Arguments to update one PublicForumImportRecord. + * @example + * // Update one PublicForumImportRecord + * const publicForumImportRecord = await prisma.publicForumImportRecord.update({ + * where: { + * // ... provide filter here + * }, + * data: { + * // ... provide data here + * } + * }) + * + */ + update(args: SelectSubset>): Prisma__PublicForumImportRecordClient<$Result.GetResult, T, "update", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Delete zero or more PublicForumImportRecords. + * @param {PublicForumImportRecordDeleteManyArgs} args - Arguments to filter PublicForumImportRecords to delete. + * @example + * // Delete a few PublicForumImportRecords + * const { count } = await prisma.publicForumImportRecord.deleteMany({ + * where: { + * // ... provide filter here + * } + * }) + * + */ + deleteMany(args?: SelectSubset>): Prisma.PrismaPromise + + /** + * Update zero or more PublicForumImportRecords. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {PublicForumImportRecordUpdateManyArgs} args - Arguments to update one or more rows. + * @example + * // Update many PublicForumImportRecords + * const publicForumImportRecord = await prisma.publicForumImportRecord.updateMany({ + * where: { + * // ... provide filter here + * }, + * data: { + * // ... provide data here + * } + * }) + * + */ + updateMany(args: SelectSubset>): Prisma.PrismaPromise + + /** + * Update zero or more PublicForumImportRecords and returns the data updated in the database. + * @param {PublicForumImportRecordUpdateManyAndReturnArgs} args - Arguments to update many PublicForumImportRecords. + * @example + * // Update many PublicForumImportRecords + * const publicForumImportRecord = await prisma.publicForumImportRecord.updateManyAndReturn({ + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * + * // Update zero or more PublicForumImportRecords and only return the `entityType` + * const publicForumImportRecordWithEntityTypeOnly = await prisma.publicForumImportRecord.updateManyAndReturn({ + * select: { entityType: true }, + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + updateManyAndReturn(args: SelectSubset>): Prisma.PrismaPromise<$Result.GetResult, T, "updateManyAndReturn", GlobalOmitOptions>> + + /** + * Create or update one PublicForumImportRecord. + * @param {PublicForumImportRecordUpsertArgs} args - Arguments to update or create a PublicForumImportRecord. + * @example + * // Update or create a PublicForumImportRecord + * const publicForumImportRecord = await prisma.publicForumImportRecord.upsert({ + * create: { + * // ... data to create a PublicForumImportRecord + * }, + * update: { + * // ... in case it already exists, update + * }, + * where: { + * // ... the filter for the PublicForumImportRecord we want to update + * } + * }) + */ + upsert(args: SelectSubset>): Prisma__PublicForumImportRecordClient<$Result.GetResult, T, "upsert", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + + /** + * Count the number of PublicForumImportRecords. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {PublicForumImportRecordCountArgs} args - Arguments to filter PublicForumImportRecords to count. + * @example + * // Count the number of PublicForumImportRecords + * const count = await prisma.publicForumImportRecord.count({ + * where: { + * // ... the filter for the PublicForumImportRecords we want to count + * } + * }) + **/ + count( + args?: Subset, + ): Prisma.PrismaPromise< + T extends $Utils.Record<'select', any> + ? T['select'] extends true + ? number + : GetScalarType + : number + > + + /** + * Allows you to perform aggregations operations on a PublicForumImportRecord. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {PublicForumImportRecordAggregateArgs} args - Select which aggregations you would like to apply and on what fields. + * @example + * // Ordered by age ascending + * // Where email contains prisma.io + * // Limited to the 10 users + * const aggregations = await prisma.user.aggregate({ + * _avg: { + * age: true, + * }, + * where: { + * email: { + * contains: "prisma.io", + * }, + * }, + * orderBy: { + * age: "asc", + * }, + * take: 10, + * }) + **/ + aggregate(args: Subset): Prisma.PrismaPromise> + + /** + * Group by PublicForumImportRecord. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {PublicForumImportRecordGroupByArgs} args - Group by arguments. + * @example + * // Group by city, order by createdAt, get count + * const result = await prisma.user.groupBy({ + * by: ['city', 'createdAt'], + * orderBy: { + * createdAt: true + * }, + * _count: { + * _all: true + * }, + * }) + * + **/ + groupBy< + T extends PublicForumImportRecordGroupByArgs, + HasSelectOrTake extends Or< + Extends<'skip', Keys>, + Extends<'take', Keys> + >, + OrderByArg extends True extends HasSelectOrTake + ? { orderBy: PublicForumImportRecordGroupByArgs['orderBy'] } + : { orderBy?: PublicForumImportRecordGroupByArgs['orderBy'] }, + OrderFields extends ExcludeUnderscoreKeys>>, + ByFields extends MaybeTupleToUnion, + ByValid extends Has, + HavingFields extends GetHavingFields, + HavingValid extends Has, + ByEmpty extends T['by'] extends never[] ? True : False, + InputErrors extends ByEmpty extends True + ? `Error: "by" must not be empty.` + : HavingValid extends False + ? { + [P in HavingFields]: P extends ByFields + ? never + : P extends string + ? `Error: Field "${P}" used in "having" needs to be provided in "by".` + : [ + Error, + 'Field ', + P, + ` in "having" needs to be provided in "by"`, + ] + }[HavingFields] + : 'take' extends Keys + ? 'orderBy' extends Keys + ? ByValid extends True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` + }[OrderFields] + : 'Error: If you provide "take", you also need to provide "orderBy"' + : 'skip' extends Keys + ? 'orderBy' extends Keys + ? ByValid extends True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` + }[OrderFields] + : 'Error: If you provide "skip", you also need to provide "orderBy"' + : ByValid extends True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` + }[OrderFields] + >(args: SubsetIntersection & InputErrors): {} extends InputErrors ? GetPublicForumImportRecordGroupByPayload : Prisma.PrismaPromise + /** + * Fields of the PublicForumImportRecord model + */ + readonly fields: PublicForumImportRecordFieldRefs; + } + + /** + * The delegate class that acts as a "Promise-like" for PublicForumImportRecord. + * Why is this prefixed with `Prisma__`? + * Because we want to prevent naming conflicts as mentioned in + * https://github.com/prisma/prisma-client-js/issues/707 + */ + export interface Prisma__PublicForumImportRecordClient extends Prisma.PrismaPromise { + readonly [Symbol.toStringTag]: "PrismaPromise" + /** + * Attaches callbacks for the resolution and/or rejection of the Promise. + * @param onfulfilled The callback to execute when the Promise is resolved. + * @param onrejected The callback to execute when the Promise is rejected. + * @returns A Promise for the completion of which ever callback is executed. + */ + then(onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null): $Utils.JsPromise + /** + * Attaches a callback for only the rejection of the Promise. + * @param onrejected The callback to execute when the Promise is rejected. + * @returns A Promise for the completion of the callback. + */ + catch(onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null): $Utils.JsPromise + /** + * Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The + * resolved value cannot be modified from the callback. + * @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected). + * @returns A Promise for the completion of the callback. + */ + finally(onfinally?: (() => void) | undefined | null): $Utils.JsPromise + } + + + + + /** + * Fields of the PublicForumImportRecord model + */ + interface PublicForumImportRecordFieldRefs { + readonly entityType: FieldRef<"PublicForumImportRecord", 'String'> + readonly sourceId: FieldRef<"PublicForumImportRecord", 'String'> + readonly targetId: FieldRef<"PublicForumImportRecord", 'String'> + readonly sourceHash: FieldRef<"PublicForumImportRecord", 'String'> + readonly targetHash: FieldRef<"PublicForumImportRecord", 'String'> + readonly createdAt: FieldRef<"PublicForumImportRecord", 'DateTime'> + } + + + // Custom InputTypes + /** + * PublicForumImportRecord findUnique + */ + export type PublicForumImportRecordFindUniqueArgs = { + /** + * Select specific fields to fetch from the PublicForumImportRecord + */ + select?: PublicForumImportRecordSelect | null + /** + * Omit specific fields from the PublicForumImportRecord + */ + omit?: PublicForumImportRecordOmit | null + /** + * Filter, which PublicForumImportRecord to fetch. + */ + where: PublicForumImportRecordWhereUniqueInput + } + + /** + * PublicForumImportRecord findUniqueOrThrow + */ + export type PublicForumImportRecordFindUniqueOrThrowArgs = { + /** + * Select specific fields to fetch from the PublicForumImportRecord + */ + select?: PublicForumImportRecordSelect | null + /** + * Omit specific fields from the PublicForumImportRecord + */ + omit?: PublicForumImportRecordOmit | null + /** + * Filter, which PublicForumImportRecord to fetch. + */ + where: PublicForumImportRecordWhereUniqueInput + } + + /** + * PublicForumImportRecord findFirst + */ + export type PublicForumImportRecordFindFirstArgs = { + /** + * Select specific fields to fetch from the PublicForumImportRecord + */ + select?: PublicForumImportRecordSelect | null + /** + * Omit specific fields from the PublicForumImportRecord + */ + omit?: PublicForumImportRecordOmit | null + /** + * Filter, which PublicForumImportRecord to fetch. + */ + where?: PublicForumImportRecordWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of PublicForumImportRecords to fetch. + */ + orderBy?: PublicForumImportRecordOrderByWithRelationInput | PublicForumImportRecordOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for searching for PublicForumImportRecords. + */ + cursor?: PublicForumImportRecordWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` PublicForumImportRecords from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` PublicForumImportRecords. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of PublicForumImportRecords. + */ + distinct?: PublicForumImportRecordScalarFieldEnum | PublicForumImportRecordScalarFieldEnum[] + } + + /** + * PublicForumImportRecord findFirstOrThrow + */ + export type PublicForumImportRecordFindFirstOrThrowArgs = { + /** + * Select specific fields to fetch from the PublicForumImportRecord + */ + select?: PublicForumImportRecordSelect | null + /** + * Omit specific fields from the PublicForumImportRecord + */ + omit?: PublicForumImportRecordOmit | null + /** + * Filter, which PublicForumImportRecord to fetch. + */ + where?: PublicForumImportRecordWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of PublicForumImportRecords to fetch. + */ + orderBy?: PublicForumImportRecordOrderByWithRelationInput | PublicForumImportRecordOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for searching for PublicForumImportRecords. + */ + cursor?: PublicForumImportRecordWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` PublicForumImportRecords from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` PublicForumImportRecords. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of PublicForumImportRecords. + */ + distinct?: PublicForumImportRecordScalarFieldEnum | PublicForumImportRecordScalarFieldEnum[] + } + + /** + * PublicForumImportRecord findMany + */ + export type PublicForumImportRecordFindManyArgs = { + /** + * Select specific fields to fetch from the PublicForumImportRecord + */ + select?: PublicForumImportRecordSelect | null + /** + * Omit specific fields from the PublicForumImportRecord + */ + omit?: PublicForumImportRecordOmit | null + /** + * Filter, which PublicForumImportRecords to fetch. + */ + where?: PublicForumImportRecordWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of PublicForumImportRecords to fetch. + */ + orderBy?: PublicForumImportRecordOrderByWithRelationInput | PublicForumImportRecordOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for listing PublicForumImportRecords. + */ + cursor?: PublicForumImportRecordWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` PublicForumImportRecords from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` PublicForumImportRecords. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of PublicForumImportRecords. + */ + distinct?: PublicForumImportRecordScalarFieldEnum | PublicForumImportRecordScalarFieldEnum[] + } + + /** + * PublicForumImportRecord create + */ + export type PublicForumImportRecordCreateArgs = { + /** + * Select specific fields to fetch from the PublicForumImportRecord + */ + select?: PublicForumImportRecordSelect | null + /** + * Omit specific fields from the PublicForumImportRecord + */ + omit?: PublicForumImportRecordOmit | null + /** + * The data needed to create a PublicForumImportRecord. + */ + data: XOR + } + + /** + * PublicForumImportRecord createMany + */ + export type PublicForumImportRecordCreateManyArgs = { + /** + * The data used to create many PublicForumImportRecords. + */ + data: PublicForumImportRecordCreateManyInput | PublicForumImportRecordCreateManyInput[] + skipDuplicates?: boolean + } + + /** + * PublicForumImportRecord createManyAndReturn + */ + export type PublicForumImportRecordCreateManyAndReturnArgs = { + /** + * Select specific fields to fetch from the PublicForumImportRecord + */ + select?: PublicForumImportRecordSelectCreateManyAndReturn | null + /** + * Omit specific fields from the PublicForumImportRecord + */ + omit?: PublicForumImportRecordOmit | null + /** + * The data used to create many PublicForumImportRecords. + */ + data: PublicForumImportRecordCreateManyInput | PublicForumImportRecordCreateManyInput[] + skipDuplicates?: boolean + } + + /** + * PublicForumImportRecord update + */ + export type PublicForumImportRecordUpdateArgs = { + /** + * Select specific fields to fetch from the PublicForumImportRecord + */ + select?: PublicForumImportRecordSelect | null + /** + * Omit specific fields from the PublicForumImportRecord + */ + omit?: PublicForumImportRecordOmit | null + /** + * The data needed to update a PublicForumImportRecord. + */ + data: XOR + /** + * Choose, which PublicForumImportRecord to update. + */ + where: PublicForumImportRecordWhereUniqueInput + } + + /** + * PublicForumImportRecord updateMany + */ + export type PublicForumImportRecordUpdateManyArgs = { + /** + * The data used to update PublicForumImportRecords. + */ + data: XOR + /** + * Filter which PublicForumImportRecords to update + */ + where?: PublicForumImportRecordWhereInput + /** + * Limit how many PublicForumImportRecords to update. + */ + limit?: number + } + + /** + * PublicForumImportRecord updateManyAndReturn + */ + export type PublicForumImportRecordUpdateManyAndReturnArgs = { + /** + * Select specific fields to fetch from the PublicForumImportRecord + */ + select?: PublicForumImportRecordSelectUpdateManyAndReturn | null + /** + * Omit specific fields from the PublicForumImportRecord + */ + omit?: PublicForumImportRecordOmit | null + /** + * The data used to update PublicForumImportRecords. + */ + data: XOR + /** + * Filter which PublicForumImportRecords to update + */ + where?: PublicForumImportRecordWhereInput + /** + * Limit how many PublicForumImportRecords to update. + */ + limit?: number + } + + /** + * PublicForumImportRecord upsert + */ + export type PublicForumImportRecordUpsertArgs = { + /** + * Select specific fields to fetch from the PublicForumImportRecord + */ + select?: PublicForumImportRecordSelect | null + /** + * Omit specific fields from the PublicForumImportRecord + */ + omit?: PublicForumImportRecordOmit | null + /** + * The filter to search for the PublicForumImportRecord to update in case it exists. + */ + where: PublicForumImportRecordWhereUniqueInput + /** + * In case the PublicForumImportRecord found by the `where` argument doesn't exist, create a new PublicForumImportRecord with this data. + */ + create: XOR + /** + * In case the PublicForumImportRecord was found with the provided `where` argument, update it with this data. + */ + update: XOR + } + + /** + * PublicForumImportRecord delete + */ + export type PublicForumImportRecordDeleteArgs = { + /** + * Select specific fields to fetch from the PublicForumImportRecord + */ + select?: PublicForumImportRecordSelect | null + /** + * Omit specific fields from the PublicForumImportRecord + */ + omit?: PublicForumImportRecordOmit | null + /** + * Filter which PublicForumImportRecord to delete. + */ + where: PublicForumImportRecordWhereUniqueInput + } + + /** + * PublicForumImportRecord deleteMany + */ + export type PublicForumImportRecordDeleteManyArgs = { + /** + * Filter which PublicForumImportRecords to delete + */ + where?: PublicForumImportRecordWhereInput + /** + * Limit how many PublicForumImportRecords to delete. + */ + limit?: number + } + + /** + * PublicForumImportRecord without action + */ + export type PublicForumImportRecordDefaultArgs = { + /** + * Select specific fields to fetch from the PublicForumImportRecord + */ + select?: PublicForumImportRecordSelect | null + /** + * Omit specific fields from the PublicForumImportRecord + */ + omit?: PublicForumImportRecordOmit | null + } + + + /** + * Enums + */ + + export const TransactionIsolationLevel: { + ReadUncommitted: 'ReadUncommitted', + ReadCommitted: 'ReadCommitted', + RepeatableRead: 'RepeatableRead', + Serializable: 'Serializable' + }; + + export type TransactionIsolationLevel = (typeof TransactionIsolationLevel)[keyof typeof TransactionIsolationLevel] + + + export const TopicScalarFieldEnum: { + id: 'id', + parentTopicId: 'parentTopicId', + challengeId: 'challengeId', + roleName: 'roleName', + title: 'title', + isAnnouncement: 'isAnnouncement', + locked: 'locked', + lockedAt: 'lockedAt', + lockedByMemberId: 'lockedByMemberId', + authorMemberId: 'authorMemberId', + authorHandle: 'authorHandle', + createdAt: 'createdAt', + updatedAt: 'updatedAt', + deletedAt: 'deletedAt', + deletedByMemberId: 'deletedByMemberId' + }; + + export type TopicScalarFieldEnum = (typeof TopicScalarFieldEnum)[keyof typeof TopicScalarFieldEnum] + + + export const MemberBanScalarFieldEnum: { + id: 'id', + memberId: 'memberId', + createdAt: 'createdAt', + createdByMemberId: 'createdByMemberId', + removedAt: 'removedAt', + removedByMemberId: 'removedByMemberId' + }; + + export type MemberBanScalarFieldEnum = (typeof MemberBanScalarFieldEnum)[keyof typeof MemberBanScalarFieldEnum] + + + export const IpBanScalarFieldEnum: { + id: 'id', + ipAddress: 'ipAddress', + createdAt: 'createdAt', + createdByMemberId: 'createdByMemberId', + removedAt: 'removedAt', + removedByMemberId: 'removedByMemberId' + }; + + export type IpBanScalarFieldEnum = (typeof IpBanScalarFieldEnum)[keyof typeof IpBanScalarFieldEnum] + + + export const PostScalarFieldEnum: { + id: 'id', + topicId: 'topicId', + parentType: 'parentType', + parentId: 'parentId', + authorMemberId: 'authorMemberId', authorHandle: 'authorHandle', content: 'content', createdAt: 'createdAt', @@ -10577,6 +13911,42 @@ export namespace Prisma { export type TopicReadStateScalarFieldEnum = (typeof TopicReadStateScalarFieldEnum)[keyof typeof TopicReadStateScalarFieldEnum] + export const PublicForumCategoryScalarFieldEnum: { + topicId: 'topicId', + description: 'description', + displayAs: 'displayAs', + sortOrder: 'sortOrder', + readRoles: 'readRoles', + createRoles: 'createRoles', + replyRoles: 'replyRoles', + legacyId: 'legacyId', + legacySlug: 'legacySlug', + source: 'source' + }; + + export type PublicForumCategoryScalarFieldEnum = (typeof PublicForumCategoryScalarFieldEnum)[keyof typeof PublicForumCategoryScalarFieldEnum] + + + export const PublicForumRoleMemberScalarFieldEnum: { + roleName: 'roleName', + memberId: 'memberId' + }; + + export type PublicForumRoleMemberScalarFieldEnum = (typeof PublicForumRoleMemberScalarFieldEnum)[keyof typeof PublicForumRoleMemberScalarFieldEnum] + + + export const PublicForumImportRecordScalarFieldEnum: { + entityType: 'entityType', + sourceId: 'sourceId', + targetId: 'targetId', + sourceHash: 'sourceHash', + targetHash: 'targetHash', + createdAt: 'createdAt' + }; + + export type PublicForumImportRecordScalarFieldEnum = (typeof PublicForumImportRecordScalarFieldEnum)[keyof typeof PublicForumImportRecordScalarFieldEnum] + + export const SortOrder: { asc: 'asc', desc: 'desc' @@ -10585,6 +13955,13 @@ export namespace Prisma { export type SortOrder = (typeof SortOrder)[keyof typeof SortOrder] + export const JsonNullValueInput: { + JsonNull: typeof JsonNull + }; + + export type JsonNullValueInput = (typeof JsonNullValueInput)[keyof typeof JsonNullValueInput] + + export const QueryMode: { default: 'default', insensitive: 'insensitive' @@ -10601,6 +13978,15 @@ export namespace Prisma { export type NullsOrder = (typeof NullsOrder)[keyof typeof NullsOrder] + export const JsonNullValueFilter: { + DbNull: typeof DbNull, + JsonNull: typeof JsonNull, + AnyNull: typeof AnyNull + }; + + export type JsonNullValueFilter = (typeof JsonNullValueFilter)[keyof typeof JsonNullValueFilter] + + /** * Field references */ @@ -10645,14 +14031,14 @@ export namespace Prisma { * Reference to a field of type 'PostReactionType' */ export type EnumPostReactionTypeFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'PostReactionType'> - + /** * Reference to a field of type 'PostReactionType[]' */ export type ListEnumPostReactionTypeFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'PostReactionType[]'> - + /** @@ -10669,6 +14055,20 @@ export namespace Prisma { + /** + * Reference to a field of type 'Json' + */ + export type JsonFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'Json'> + + + + /** + * Reference to a field of type 'QueryMode' + */ + export type EnumQueryModeFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'QueryMode'> + + + /** * Reference to a field of type 'Float' */ @@ -11215,6 +14615,182 @@ export namespace Prisma { updatedAt?: DateTimeWithAggregatesFilter<"TopicReadState"> | Date | string } + export type PublicForumCategoryWhereInput = { + AND?: PublicForumCategoryWhereInput | PublicForumCategoryWhereInput[] + OR?: PublicForumCategoryWhereInput[] + NOT?: PublicForumCategoryWhereInput | PublicForumCategoryWhereInput[] + topicId?: StringFilter<"PublicForumCategory"> | string + description?: StringFilter<"PublicForumCategory"> | string + displayAs?: StringFilter<"PublicForumCategory"> | string + sortOrder?: IntFilter<"PublicForumCategory"> | number + readRoles?: StringNullableListFilter<"PublicForumCategory"> + createRoles?: StringNullableListFilter<"PublicForumCategory"> + replyRoles?: StringNullableListFilter<"PublicForumCategory"> + legacyId?: IntFilter<"PublicForumCategory"> | number + legacySlug?: StringFilter<"PublicForumCategory"> | string + source?: JsonFilter<"PublicForumCategory"> + } + + export type PublicForumCategoryOrderByWithRelationInput = { + topicId?: SortOrder + description?: SortOrder + displayAs?: SortOrder + sortOrder?: SortOrder + readRoles?: SortOrder + createRoles?: SortOrder + replyRoles?: SortOrder + legacyId?: SortOrder + legacySlug?: SortOrder + source?: SortOrder + } + + export type PublicForumCategoryWhereUniqueInput = Prisma.AtLeast<{ + topicId?: string + legacyId?: number + AND?: PublicForumCategoryWhereInput | PublicForumCategoryWhereInput[] + OR?: PublicForumCategoryWhereInput[] + NOT?: PublicForumCategoryWhereInput | PublicForumCategoryWhereInput[] + description?: StringFilter<"PublicForumCategory"> | string + displayAs?: StringFilter<"PublicForumCategory"> | string + sortOrder?: IntFilter<"PublicForumCategory"> | number + readRoles?: StringNullableListFilter<"PublicForumCategory"> + createRoles?: StringNullableListFilter<"PublicForumCategory"> + replyRoles?: StringNullableListFilter<"PublicForumCategory"> + legacySlug?: StringFilter<"PublicForumCategory"> | string + source?: JsonFilter<"PublicForumCategory"> + }, "topicId" | "legacyId"> + + export type PublicForumCategoryOrderByWithAggregationInput = { + topicId?: SortOrder + description?: SortOrder + displayAs?: SortOrder + sortOrder?: SortOrder + readRoles?: SortOrder + createRoles?: SortOrder + replyRoles?: SortOrder + legacyId?: SortOrder + legacySlug?: SortOrder + source?: SortOrder + _count?: PublicForumCategoryCountOrderByAggregateInput + _avg?: PublicForumCategoryAvgOrderByAggregateInput + _max?: PublicForumCategoryMaxOrderByAggregateInput + _min?: PublicForumCategoryMinOrderByAggregateInput + _sum?: PublicForumCategorySumOrderByAggregateInput + } + + export type PublicForumCategoryScalarWhereWithAggregatesInput = { + AND?: PublicForumCategoryScalarWhereWithAggregatesInput | PublicForumCategoryScalarWhereWithAggregatesInput[] + OR?: PublicForumCategoryScalarWhereWithAggregatesInput[] + NOT?: PublicForumCategoryScalarWhereWithAggregatesInput | PublicForumCategoryScalarWhereWithAggregatesInput[] + topicId?: StringWithAggregatesFilter<"PublicForumCategory"> | string + description?: StringWithAggregatesFilter<"PublicForumCategory"> | string + displayAs?: StringWithAggregatesFilter<"PublicForumCategory"> | string + sortOrder?: IntWithAggregatesFilter<"PublicForumCategory"> | number + readRoles?: StringNullableListFilter<"PublicForumCategory"> + createRoles?: StringNullableListFilter<"PublicForumCategory"> + replyRoles?: StringNullableListFilter<"PublicForumCategory"> + legacyId?: IntWithAggregatesFilter<"PublicForumCategory"> | number + legacySlug?: StringWithAggregatesFilter<"PublicForumCategory"> | string + source?: JsonWithAggregatesFilter<"PublicForumCategory"> + } + + export type PublicForumRoleMemberWhereInput = { + AND?: PublicForumRoleMemberWhereInput | PublicForumRoleMemberWhereInput[] + OR?: PublicForumRoleMemberWhereInput[] + NOT?: PublicForumRoleMemberWhereInput | PublicForumRoleMemberWhereInput[] + roleName?: StringFilter<"PublicForumRoleMember"> | string + memberId?: StringFilter<"PublicForumRoleMember"> | string + } + + export type PublicForumRoleMemberOrderByWithRelationInput = { + roleName?: SortOrder + memberId?: SortOrder + } + + export type PublicForumRoleMemberWhereUniqueInput = Prisma.AtLeast<{ + roleName_memberId?: PublicForumRoleMemberRoleNameMemberIdCompoundUniqueInput + AND?: PublicForumRoleMemberWhereInput | PublicForumRoleMemberWhereInput[] + OR?: PublicForumRoleMemberWhereInput[] + NOT?: PublicForumRoleMemberWhereInput | PublicForumRoleMemberWhereInput[] + roleName?: StringFilter<"PublicForumRoleMember"> | string + memberId?: StringFilter<"PublicForumRoleMember"> | string + }, "roleName_memberId"> + + export type PublicForumRoleMemberOrderByWithAggregationInput = { + roleName?: SortOrder + memberId?: SortOrder + _count?: PublicForumRoleMemberCountOrderByAggregateInput + _max?: PublicForumRoleMemberMaxOrderByAggregateInput + _min?: PublicForumRoleMemberMinOrderByAggregateInput + } + + export type PublicForumRoleMemberScalarWhereWithAggregatesInput = { + AND?: PublicForumRoleMemberScalarWhereWithAggregatesInput | PublicForumRoleMemberScalarWhereWithAggregatesInput[] + OR?: PublicForumRoleMemberScalarWhereWithAggregatesInput[] + NOT?: PublicForumRoleMemberScalarWhereWithAggregatesInput | PublicForumRoleMemberScalarWhereWithAggregatesInput[] + roleName?: StringWithAggregatesFilter<"PublicForumRoleMember"> | string + memberId?: StringWithAggregatesFilter<"PublicForumRoleMember"> | string + } + + export type PublicForumImportRecordWhereInput = { + AND?: PublicForumImportRecordWhereInput | PublicForumImportRecordWhereInput[] + OR?: PublicForumImportRecordWhereInput[] + NOT?: PublicForumImportRecordWhereInput | PublicForumImportRecordWhereInput[] + entityType?: StringFilter<"PublicForumImportRecord"> | string + sourceId?: StringFilter<"PublicForumImportRecord"> | string + targetId?: StringFilter<"PublicForumImportRecord"> | string + sourceHash?: StringFilter<"PublicForumImportRecord"> | string + targetHash?: StringFilter<"PublicForumImportRecord"> | string + createdAt?: DateTimeFilter<"PublicForumImportRecord"> | Date | string + } + + export type PublicForumImportRecordOrderByWithRelationInput = { + entityType?: SortOrder + sourceId?: SortOrder + targetId?: SortOrder + sourceHash?: SortOrder + targetHash?: SortOrder + createdAt?: SortOrder + } + + export type PublicForumImportRecordWhereUniqueInput = Prisma.AtLeast<{ + entityType_targetId?: PublicForumImportRecordEntityTypeTargetIdCompoundUniqueInput + entityType_sourceId?: PublicForumImportRecordEntityTypeSourceIdCompoundUniqueInput + AND?: PublicForumImportRecordWhereInput | PublicForumImportRecordWhereInput[] + OR?: PublicForumImportRecordWhereInput[] + NOT?: PublicForumImportRecordWhereInput | PublicForumImportRecordWhereInput[] + entityType?: StringFilter<"PublicForumImportRecord"> | string + sourceId?: StringFilter<"PublicForumImportRecord"> | string + targetId?: StringFilter<"PublicForumImportRecord"> | string + sourceHash?: StringFilter<"PublicForumImportRecord"> | string + targetHash?: StringFilter<"PublicForumImportRecord"> | string + createdAt?: DateTimeFilter<"PublicForumImportRecord"> | Date | string + }, "entityType_sourceId" | "entityType_targetId"> + + export type PublicForumImportRecordOrderByWithAggregationInput = { + entityType?: SortOrder + sourceId?: SortOrder + targetId?: SortOrder + sourceHash?: SortOrder + targetHash?: SortOrder + createdAt?: SortOrder + _count?: PublicForumImportRecordCountOrderByAggregateInput + _max?: PublicForumImportRecordMaxOrderByAggregateInput + _min?: PublicForumImportRecordMinOrderByAggregateInput + } + + export type PublicForumImportRecordScalarWhereWithAggregatesInput = { + AND?: PublicForumImportRecordScalarWhereWithAggregatesInput | PublicForumImportRecordScalarWhereWithAggregatesInput[] + OR?: PublicForumImportRecordScalarWhereWithAggregatesInput[] + NOT?: PublicForumImportRecordScalarWhereWithAggregatesInput | PublicForumImportRecordScalarWhereWithAggregatesInput[] + entityType?: StringWithAggregatesFilter<"PublicForumImportRecord"> | string + sourceId?: StringWithAggregatesFilter<"PublicForumImportRecord"> | string + targetId?: StringWithAggregatesFilter<"PublicForumImportRecord"> | string + sourceHash?: StringWithAggregatesFilter<"PublicForumImportRecord"> | string + targetHash?: StringWithAggregatesFilter<"PublicForumImportRecord"> | string + createdAt?: DateTimeWithAggregatesFilter<"PublicForumImportRecord"> | Date | string + } + export type TopicCreateInput = { id?: string challengeId?: string | null @@ -11775,6 +15351,195 @@ export namespace Prisma { updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string } + export type PublicForumCategoryCreateInput = { + topicId: string + description?: string + displayAs: string + sortOrder: number + readRoles?: PublicForumCategoryCreatereadRolesInput | string[] + createRoles?: PublicForumCategoryCreatecreateRolesInput | string[] + replyRoles?: PublicForumCategoryCreatereplyRolesInput | string[] + legacyId: number + legacySlug: string + source: JsonNullValueInput | InputJsonValue + } + + export type PublicForumCategoryUncheckedCreateInput = { + topicId: string + description?: string + displayAs: string + sortOrder: number + readRoles?: PublicForumCategoryCreatereadRolesInput | string[] + createRoles?: PublicForumCategoryCreatecreateRolesInput | string[] + replyRoles?: PublicForumCategoryCreatereplyRolesInput | string[] + legacyId: number + legacySlug: string + source: JsonNullValueInput | InputJsonValue + } + + export type PublicForumCategoryUpdateInput = { + topicId?: StringFieldUpdateOperationsInput | string + description?: StringFieldUpdateOperationsInput | string + displayAs?: StringFieldUpdateOperationsInput | string + sortOrder?: IntFieldUpdateOperationsInput | number + readRoles?: PublicForumCategoryUpdatereadRolesInput | string[] + createRoles?: PublicForumCategoryUpdatecreateRolesInput | string[] + replyRoles?: PublicForumCategoryUpdatereplyRolesInput | string[] + legacyId?: IntFieldUpdateOperationsInput | number + legacySlug?: StringFieldUpdateOperationsInput | string + source?: JsonNullValueInput | InputJsonValue + } + + export type PublicForumCategoryUncheckedUpdateInput = { + topicId?: StringFieldUpdateOperationsInput | string + description?: StringFieldUpdateOperationsInput | string + displayAs?: StringFieldUpdateOperationsInput | string + sortOrder?: IntFieldUpdateOperationsInput | number + readRoles?: PublicForumCategoryUpdatereadRolesInput | string[] + createRoles?: PublicForumCategoryUpdatecreateRolesInput | string[] + replyRoles?: PublicForumCategoryUpdatereplyRolesInput | string[] + legacyId?: IntFieldUpdateOperationsInput | number + legacySlug?: StringFieldUpdateOperationsInput | string + source?: JsonNullValueInput | InputJsonValue + } + + export type PublicForumCategoryCreateManyInput = { + topicId: string + description?: string + displayAs: string + sortOrder: number + readRoles?: PublicForumCategoryCreatereadRolesInput | string[] + createRoles?: PublicForumCategoryCreatecreateRolesInput | string[] + replyRoles?: PublicForumCategoryCreatereplyRolesInput | string[] + legacyId: number + legacySlug: string + source: JsonNullValueInput | InputJsonValue + } + + export type PublicForumCategoryUpdateManyMutationInput = { + topicId?: StringFieldUpdateOperationsInput | string + description?: StringFieldUpdateOperationsInput | string + displayAs?: StringFieldUpdateOperationsInput | string + sortOrder?: IntFieldUpdateOperationsInput | number + readRoles?: PublicForumCategoryUpdatereadRolesInput | string[] + createRoles?: PublicForumCategoryUpdatecreateRolesInput | string[] + replyRoles?: PublicForumCategoryUpdatereplyRolesInput | string[] + legacyId?: IntFieldUpdateOperationsInput | number + legacySlug?: StringFieldUpdateOperationsInput | string + source?: JsonNullValueInput | InputJsonValue + } + + export type PublicForumCategoryUncheckedUpdateManyInput = { + topicId?: StringFieldUpdateOperationsInput | string + description?: StringFieldUpdateOperationsInput | string + displayAs?: StringFieldUpdateOperationsInput | string + sortOrder?: IntFieldUpdateOperationsInput | number + readRoles?: PublicForumCategoryUpdatereadRolesInput | string[] + createRoles?: PublicForumCategoryUpdatecreateRolesInput | string[] + replyRoles?: PublicForumCategoryUpdatereplyRolesInput | string[] + legacyId?: IntFieldUpdateOperationsInput | number + legacySlug?: StringFieldUpdateOperationsInput | string + source?: JsonNullValueInput | InputJsonValue + } + + export type PublicForumRoleMemberCreateInput = { + roleName: string + memberId: string + } + + export type PublicForumRoleMemberUncheckedCreateInput = { + roleName: string + memberId: string + } + + export type PublicForumRoleMemberUpdateInput = { + roleName?: StringFieldUpdateOperationsInput | string + memberId?: StringFieldUpdateOperationsInput | string + } + + export type PublicForumRoleMemberUncheckedUpdateInput = { + roleName?: StringFieldUpdateOperationsInput | string + memberId?: StringFieldUpdateOperationsInput | string + } + + export type PublicForumRoleMemberCreateManyInput = { + roleName: string + memberId: string + } + + export type PublicForumRoleMemberUpdateManyMutationInput = { + roleName?: StringFieldUpdateOperationsInput | string + memberId?: StringFieldUpdateOperationsInput | string + } + + export type PublicForumRoleMemberUncheckedUpdateManyInput = { + roleName?: StringFieldUpdateOperationsInput | string + memberId?: StringFieldUpdateOperationsInput | string + } + + export type PublicForumImportRecordCreateInput = { + entityType: string + sourceId: string + targetId: string + sourceHash: string + targetHash: string + createdAt?: Date | string + } + + export type PublicForumImportRecordUncheckedCreateInput = { + entityType: string + sourceId: string + targetId: string + sourceHash: string + targetHash: string + createdAt?: Date | string + } + + export type PublicForumImportRecordUpdateInput = { + entityType?: StringFieldUpdateOperationsInput | string + sourceId?: StringFieldUpdateOperationsInput | string + targetId?: StringFieldUpdateOperationsInput | string + sourceHash?: StringFieldUpdateOperationsInput | string + targetHash?: StringFieldUpdateOperationsInput | string + createdAt?: DateTimeFieldUpdateOperationsInput | Date | string + } + + export type PublicForumImportRecordUncheckedUpdateInput = { + entityType?: StringFieldUpdateOperationsInput | string + sourceId?: StringFieldUpdateOperationsInput | string + targetId?: StringFieldUpdateOperationsInput | string + sourceHash?: StringFieldUpdateOperationsInput | string + targetHash?: StringFieldUpdateOperationsInput | string + createdAt?: DateTimeFieldUpdateOperationsInput | Date | string + } + + export type PublicForumImportRecordCreateManyInput = { + entityType: string + sourceId: string + targetId: string + sourceHash: string + targetHash: string + createdAt?: Date | string + } + + export type PublicForumImportRecordUpdateManyMutationInput = { + entityType?: StringFieldUpdateOperationsInput | string + sourceId?: StringFieldUpdateOperationsInput | string + targetId?: StringFieldUpdateOperationsInput | string + sourceHash?: StringFieldUpdateOperationsInput | string + targetHash?: StringFieldUpdateOperationsInput | string + createdAt?: DateTimeFieldUpdateOperationsInput | Date | string + } + + export type PublicForumImportRecordUncheckedUpdateManyInput = { + entityType?: StringFieldUpdateOperationsInput | string + sourceId?: StringFieldUpdateOperationsInput | string + targetId?: StringFieldUpdateOperationsInput | string + sourceHash?: StringFieldUpdateOperationsInput | string + targetHash?: StringFieldUpdateOperationsInput | string + createdAt?: DateTimeFieldUpdateOperationsInput | Date | string + } + export type StringFilter<$PrismaModel = never> = { equals?: string | StringFieldRefInput<$PrismaModel> in?: string[] | ListStringFieldRefInput<$PrismaModel> @@ -12287,6 +16052,161 @@ export namespace Prisma { updatedAt?: SortOrder } + export type StringNullableListFilter<$PrismaModel = never> = { + equals?: string[] | ListStringFieldRefInput<$PrismaModel> | null + has?: string | StringFieldRefInput<$PrismaModel> | null + hasEvery?: string[] | ListStringFieldRefInput<$PrismaModel> + hasSome?: string[] | ListStringFieldRefInput<$PrismaModel> + isEmpty?: boolean + } + export type JsonFilter<$PrismaModel = never> = + | PatchUndefined< + Either>, Exclude>, 'path'>>, + Required> + > + | OptionalFlat>, 'path'>> + + export type JsonFilterBase<$PrismaModel = never> = { + equals?: InputJsonValue | JsonFieldRefInput<$PrismaModel> | JsonNullValueFilter + path?: string[] + mode?: QueryMode | EnumQueryModeFieldRefInput<$PrismaModel> + string_contains?: string | StringFieldRefInput<$PrismaModel> + string_starts_with?: string | StringFieldRefInput<$PrismaModel> + string_ends_with?: string | StringFieldRefInput<$PrismaModel> + array_starts_with?: InputJsonValue | JsonFieldRefInput<$PrismaModel> | null + array_ends_with?: InputJsonValue | JsonFieldRefInput<$PrismaModel> | null + array_contains?: InputJsonValue | JsonFieldRefInput<$PrismaModel> | null + lt?: InputJsonValue | JsonFieldRefInput<$PrismaModel> + lte?: InputJsonValue | JsonFieldRefInput<$PrismaModel> + gt?: InputJsonValue | JsonFieldRefInput<$PrismaModel> + gte?: InputJsonValue | JsonFieldRefInput<$PrismaModel> + not?: InputJsonValue | JsonFieldRefInput<$PrismaModel> | JsonNullValueFilter + } + + export type PublicForumCategoryCountOrderByAggregateInput = { + topicId?: SortOrder + description?: SortOrder + displayAs?: SortOrder + sortOrder?: SortOrder + readRoles?: SortOrder + createRoles?: SortOrder + replyRoles?: SortOrder + legacyId?: SortOrder + legacySlug?: SortOrder + source?: SortOrder + } + + export type PublicForumCategoryAvgOrderByAggregateInput = { + sortOrder?: SortOrder + legacyId?: SortOrder + } + + export type PublicForumCategoryMaxOrderByAggregateInput = { + topicId?: SortOrder + description?: SortOrder + displayAs?: SortOrder + sortOrder?: SortOrder + legacyId?: SortOrder + legacySlug?: SortOrder + } + + export type PublicForumCategoryMinOrderByAggregateInput = { + topicId?: SortOrder + description?: SortOrder + displayAs?: SortOrder + sortOrder?: SortOrder + legacyId?: SortOrder + legacySlug?: SortOrder + } + + export type PublicForumCategorySumOrderByAggregateInput = { + sortOrder?: SortOrder + legacyId?: SortOrder + } + export type JsonWithAggregatesFilter<$PrismaModel = never> = + | PatchUndefined< + Either>, Exclude>, 'path'>>, + Required> + > + | OptionalFlat>, 'path'>> + + export type JsonWithAggregatesFilterBase<$PrismaModel = never> = { + equals?: InputJsonValue | JsonFieldRefInput<$PrismaModel> | JsonNullValueFilter + path?: string[] + mode?: QueryMode | EnumQueryModeFieldRefInput<$PrismaModel> + string_contains?: string | StringFieldRefInput<$PrismaModel> + string_starts_with?: string | StringFieldRefInput<$PrismaModel> + string_ends_with?: string | StringFieldRefInput<$PrismaModel> + array_starts_with?: InputJsonValue | JsonFieldRefInput<$PrismaModel> | null + array_ends_with?: InputJsonValue | JsonFieldRefInput<$PrismaModel> | null + array_contains?: InputJsonValue | JsonFieldRefInput<$PrismaModel> | null + lt?: InputJsonValue | JsonFieldRefInput<$PrismaModel> + lte?: InputJsonValue | JsonFieldRefInput<$PrismaModel> + gt?: InputJsonValue | JsonFieldRefInput<$PrismaModel> + gte?: InputJsonValue | JsonFieldRefInput<$PrismaModel> + not?: InputJsonValue | JsonFieldRefInput<$PrismaModel> | JsonNullValueFilter + _count?: NestedIntFilter<$PrismaModel> + _min?: NestedJsonFilter<$PrismaModel> + _max?: NestedJsonFilter<$PrismaModel> + } + + export type PublicForumRoleMemberRoleNameMemberIdCompoundUniqueInput = { + roleName: string + memberId: string + } + + export type PublicForumRoleMemberCountOrderByAggregateInput = { + roleName?: SortOrder + memberId?: SortOrder + } + + export type PublicForumRoleMemberMaxOrderByAggregateInput = { + roleName?: SortOrder + memberId?: SortOrder + } + + export type PublicForumRoleMemberMinOrderByAggregateInput = { + roleName?: SortOrder + memberId?: SortOrder + } + + export type PublicForumImportRecordEntityTypeTargetIdCompoundUniqueInput = { + entityType: string + targetId: string + } + + export type PublicForumImportRecordEntityTypeSourceIdCompoundUniqueInput = { + entityType: string + sourceId: string + } + + export type PublicForumImportRecordCountOrderByAggregateInput = { + entityType?: SortOrder + sourceId?: SortOrder + targetId?: SortOrder + sourceHash?: SortOrder + targetHash?: SortOrder + createdAt?: SortOrder + } + + export type PublicForumImportRecordMaxOrderByAggregateInput = { + entityType?: SortOrder + sourceId?: SortOrder + targetId?: SortOrder + sourceHash?: SortOrder + targetHash?: SortOrder + createdAt?: SortOrder + } + + export type PublicForumImportRecordMinOrderByAggregateInput = { + entityType?: SortOrder + sourceId?: SortOrder + targetId?: SortOrder + sourceHash?: SortOrder + targetHash?: SortOrder + createdAt?: SortOrder + } + export type TopicCreateNestedOneWithoutChildTopicsInput = { create?: XOR connectOrCreate?: TopicCreateOrConnectWithoutChildTopicsInput @@ -12713,6 +16633,33 @@ export namespace Prisma { update?: XOR, TopicUncheckedUpdateWithoutReadStatesInput> } + export type PublicForumCategoryCreatereadRolesInput = { + set: string[] + } + + export type PublicForumCategoryCreatecreateRolesInput = { + set: string[] + } + + export type PublicForumCategoryCreatereplyRolesInput = { + set: string[] + } + + export type PublicForumCategoryUpdatereadRolesInput = { + set?: string[] + push?: string | string[] + } + + export type PublicForumCategoryUpdatecreateRolesInput = { + set?: string[] + push?: string | string[] + } + + export type PublicForumCategoryUpdatereplyRolesInput = { + set?: string[] + push?: string | string[] + } + export type NestedStringFilter<$PrismaModel = never> = { equals?: string | StringFieldRefInput<$PrismaModel> in?: string[] | ListStringFieldRefInput<$PrismaModel> @@ -12903,6 +16850,29 @@ export namespace Prisma { gte?: number | FloatFieldRefInput<$PrismaModel> not?: NestedFloatFilter<$PrismaModel> | number } + export type NestedJsonFilter<$PrismaModel = never> = + | PatchUndefined< + Either>, Exclude>, 'path'>>, + Required> + > + | OptionalFlat>, 'path'>> + + export type NestedJsonFilterBase<$PrismaModel = never> = { + equals?: InputJsonValue | JsonFieldRefInput<$PrismaModel> | JsonNullValueFilter + path?: string[] + mode?: QueryMode | EnumQueryModeFieldRefInput<$PrismaModel> + string_contains?: string | StringFieldRefInput<$PrismaModel> + string_starts_with?: string | StringFieldRefInput<$PrismaModel> + string_ends_with?: string | StringFieldRefInput<$PrismaModel> + array_starts_with?: InputJsonValue | JsonFieldRefInput<$PrismaModel> | null + array_ends_with?: InputJsonValue | JsonFieldRefInput<$PrismaModel> | null + array_contains?: InputJsonValue | JsonFieldRefInput<$PrismaModel> | null + lt?: InputJsonValue | JsonFieldRefInput<$PrismaModel> + lte?: InputJsonValue | JsonFieldRefInput<$PrismaModel> + gt?: InputJsonValue | JsonFieldRefInput<$PrismaModel> + gte?: InputJsonValue | JsonFieldRefInput<$PrismaModel> + not?: InputJsonValue | JsonFieldRefInput<$PrismaModel> | JsonNullValueFilter + } export type TopicCreateWithoutChildTopicsInput = { id?: string diff --git a/packages/forums-prisma-client/index.js b/packages/forums-prisma-client/index.js index 73cb996..6c4f777 100644 --- a/packages/forums-prisma-client/index.js +++ b/packages/forums-prisma-client/index.js @@ -170,11 +170,42 @@ exports.Prisma.TopicReadStateScalarFieldEnum = { updatedAt: 'updatedAt' }; +exports.Prisma.PublicForumCategoryScalarFieldEnum = { + topicId: 'topicId', + description: 'description', + displayAs: 'displayAs', + sortOrder: 'sortOrder', + readRoles: 'readRoles', + createRoles: 'createRoles', + replyRoles: 'replyRoles', + legacyId: 'legacyId', + legacySlug: 'legacySlug', + source: 'source' +}; + +exports.Prisma.PublicForumRoleMemberScalarFieldEnum = { + roleName: 'roleName', + memberId: 'memberId' +}; + +exports.Prisma.PublicForumImportRecordScalarFieldEnum = { + entityType: 'entityType', + sourceId: 'sourceId', + targetId: 'targetId', + sourceHash: 'sourceHash', + targetHash: 'targetHash', + createdAt: 'createdAt' +}; + exports.Prisma.SortOrder = { asc: 'asc', desc: 'desc' }; +exports.Prisma.JsonNullValueInput = { + JsonNull: Prisma.JsonNull +}; + exports.Prisma.QueryMode = { default: 'default', insensitive: 'insensitive' @@ -184,6 +215,12 @@ exports.Prisma.NullsOrder = { first: 'first', last: 'last' }; + +exports.Prisma.JsonNullValueFilter = { + DbNull: Prisma.DbNull, + JsonNull: Prisma.JsonNull, + AnyNull: Prisma.AnyNull +}; exports.PostReactionType = exports.$Enums.PostReactionType = { THUMBS_UP: 'THUMBS_UP', THUMBS_DOWN: 'THUMBS_DOWN' @@ -197,7 +234,10 @@ exports.Prisma.ModelName = { PostReaction: 'PostReaction', TopicClosure: 'TopicClosure', TopicWatch: 'TopicWatch', - TopicReadState: 'TopicReadState' + TopicReadState: 'TopicReadState', + PublicForumCategory: 'PublicForumCategory', + PublicForumRoleMember: 'PublicForumRoleMember', + PublicForumImportRecord: 'PublicForumImportRecord' }; /** * Create the Client @@ -207,14 +247,14 @@ const config = { "clientVersion": "7.9.0", "engineVersion": "e922089b7d7502aff4249d5da3420f6fa55fc6ad", "activeProvider": "postgresql", - "inlineSchema": "generator client {\n provider = \"prisma-client-js\"\n output = \"./generated/client\"\n}\n\ngenerator externalClient {\n provider = \"prisma-client-js\"\n output = \"../packages/forums-prisma-client\"\n binaryTargets = [\"native\", \"debian-openssl-3.0.x\"]\n}\n\ndatasource db {\n provider = \"postgresql\"\n schemas = [\"forums\"]\n}\n\nenum PostReactionType {\n THUMBS_UP\n THUMBS_DOWN\n\n @@schema(\"forums\")\n}\n\nmodel Topic {\n id String @id @default(dbgenerated(\"nanoid()\")) @db.VarChar(14)\n parentTopicId String? @db.VarChar(14)\n challengeId String? @db.VarChar(64)\n roleName String? @db.VarChar(128)\n title String @db.VarChar(255)\n isAnnouncement Boolean @default(false)\n locked Boolean @default(false)\n lockedAt DateTime?\n lockedByMemberId String? @db.VarChar(64)\n authorMemberId String @db.VarChar(64)\n authorHandle String @db.VarChar(128)\n createdAt DateTime @default(now())\n updatedAt DateTime @default(now()) @updatedAt\n deletedAt DateTime?\n deletedByMemberId String? @db.VarChar(64)\n\n parentTopic Topic? @relation(\"TopicChildren\", fields: [parentTopicId], references: [id])\n childTopics Topic[] @relation(\"TopicChildren\")\n posts Post[]\n ancestorClosures TopicClosure[] @relation(\"TopicClosureAncestor\")\n descendantClosures TopicClosure[] @relation(\"TopicClosureDescendant\")\n watches TopicWatch[]\n readStates TopicReadState[]\n\n @@index([parentTopicId])\n @@index([challengeId])\n @@index([roleName])\n @@index([deletedAt])\n @@index([isAnnouncement, createdAt])\n @@schema(\"forums\")\n}\n\nmodel MemberBan {\n id String @id @default(dbgenerated(\"nanoid()\")) @db.VarChar(14)\n memberId String @db.VarChar(64)\n createdAt DateTime @default(now())\n createdByMemberId String? @db.VarChar(64)\n removedAt DateTime?\n removedByMemberId String? @db.VarChar(64)\n\n @@index([memberId])\n @@index([removedAt])\n @@schema(\"forums\")\n}\n\nmodel IpBan {\n id String @id @default(dbgenerated(\"nanoid()\")) @db.VarChar(14)\n ipAddress String @db.VarChar(45)\n createdAt DateTime @default(now())\n createdByMemberId String? @db.VarChar(64)\n removedAt DateTime?\n removedByMemberId String? @db.VarChar(64)\n\n @@index([ipAddress])\n @@index([removedAt])\n @@schema(\"forums\")\n}\n\nmodel Post {\n id String @id @default(dbgenerated(\"nanoid()\")) @db.VarChar(14)\n topicId String @db.VarChar(14)\n parentType String @db.VarChar(16)\n parentId String @db.VarChar(14)\n authorMemberId String @db.VarChar(64)\n authorHandle String @db.VarChar(128)\n content String? @db.Text\n createdAt DateTime @default(now())\n updatedAt DateTime @default(now()) @updatedAt\n deletedAt DateTime?\n deletedByMemberId String? @db.VarChar(64)\n\n topic Topic @relation(fields: [topicId], references: [id], onDelete: Cascade)\n reactions PostReaction[]\n\n @@index([topicId, createdAt])\n @@index([topicId, parentType, parentId])\n @@index([deletedAt])\n @@schema(\"forums\")\n}\n\nmodel PostReaction {\n postId String @db.VarChar(14)\n memberId String @db.VarChar(64)\n reaction PostReactionType\n createdAt DateTime @default(now())\n updatedAt DateTime @default(now()) @updatedAt\n\n post Post @relation(fields: [postId], references: [id], onDelete: Cascade)\n\n @@id([postId, memberId])\n @@index([memberId])\n @@index([postId, reaction])\n @@schema(\"forums\")\n}\n\nmodel TopicClosure {\n ancestorTopicId String @db.VarChar(14)\n descendantTopicId String @db.VarChar(14)\n depth Int\n\n ancestorTopic Topic @relation(\"TopicClosureAncestor\", fields: [ancestorTopicId], references: [id], onDelete: Cascade)\n descendantTopic Topic @relation(\"TopicClosureDescendant\", fields: [descendantTopicId], references: [id], onDelete: Cascade)\n\n @@id([ancestorTopicId, descendantTopicId])\n @@index([ancestorTopicId, depth])\n @@index([descendantTopicId, depth])\n @@schema(\"forums\")\n}\n\nmodel TopicWatch {\n topicId String @db.VarChar(14)\n memberId String @db.VarChar(64)\n createdAt DateTime @default(now())\n\n topic Topic @relation(fields: [topicId], references: [id], onDelete: Cascade)\n\n @@id([topicId, memberId])\n @@index([memberId])\n @@schema(\"forums\")\n}\n\nmodel TopicReadState {\n topicId String @db.VarChar(14)\n memberId String @db.VarChar(64)\n lastReadAt DateTime @default(now())\n updatedAt DateTime @default(now()) @updatedAt\n\n topic Topic @relation(fields: [topicId], references: [id], onDelete: Cascade)\n\n @@id([topicId, memberId])\n @@index([memberId])\n @@index([lastReadAt])\n @@schema(\"forums\")\n}\n" + "inlineSchema": "generator client {\n provider = \"prisma-client-js\"\n output = \"./generated/client\"\n}\n\ngenerator externalClient {\n provider = \"prisma-client-js\"\n output = \"../packages/forums-prisma-client\"\n binaryTargets = [\"native\", \"debian-openssl-3.0.x\"]\n}\n\ndatasource db {\n provider = \"postgresql\"\n schemas = [\"forums\"]\n}\n\nenum PostReactionType {\n THUMBS_UP\n THUMBS_DOWN\n\n @@schema(\"forums\")\n}\n\nmodel Topic {\n id String @id @default(dbgenerated(\"nanoid()\")) @db.VarChar(14)\n parentTopicId String? @db.VarChar(14)\n challengeId String? @db.VarChar(64)\n roleName String? @db.VarChar(128)\n title String @db.VarChar(255)\n isAnnouncement Boolean @default(false)\n locked Boolean @default(false)\n lockedAt DateTime?\n lockedByMemberId String? @db.VarChar(64)\n authorMemberId String @db.VarChar(64)\n authorHandle String @db.VarChar(128)\n createdAt DateTime @default(now())\n updatedAt DateTime @default(now()) @updatedAt\n deletedAt DateTime?\n deletedByMemberId String? @db.VarChar(64)\n\n parentTopic Topic? @relation(\"TopicChildren\", fields: [parentTopicId], references: [id])\n childTopics Topic[] @relation(\"TopicChildren\")\n posts Post[]\n ancestorClosures TopicClosure[] @relation(\"TopicClosureAncestor\")\n descendantClosures TopicClosure[] @relation(\"TopicClosureDescendant\")\n watches TopicWatch[]\n readStates TopicReadState[]\n\n @@index([parentTopicId])\n @@index([challengeId])\n @@index([roleName])\n @@index([deletedAt])\n @@index([isAnnouncement, createdAt])\n @@schema(\"forums\")\n}\n\nmodel MemberBan {\n id String @id @default(dbgenerated(\"nanoid()\")) @db.VarChar(14)\n memberId String @db.VarChar(64)\n createdAt DateTime @default(now())\n createdByMemberId String? @db.VarChar(64)\n removedAt DateTime?\n removedByMemberId String? @db.VarChar(64)\n\n @@index([memberId])\n @@index([removedAt])\n @@schema(\"forums\")\n}\n\nmodel IpBan {\n id String @id @default(dbgenerated(\"nanoid()\")) @db.VarChar(14)\n ipAddress String @db.VarChar(45)\n createdAt DateTime @default(now())\n createdByMemberId String? @db.VarChar(64)\n removedAt DateTime?\n removedByMemberId String? @db.VarChar(64)\n\n @@index([ipAddress])\n @@index([removedAt])\n @@schema(\"forums\")\n}\n\nmodel Post {\n id String @id @default(dbgenerated(\"nanoid()\")) @db.VarChar(14)\n topicId String @db.VarChar(14)\n parentType String @db.VarChar(16)\n parentId String @db.VarChar(14)\n authorMemberId String @db.VarChar(64)\n authorHandle String @db.VarChar(128)\n content String? @db.Text\n createdAt DateTime @default(now())\n updatedAt DateTime @default(now()) @updatedAt\n deletedAt DateTime?\n deletedByMemberId String? @db.VarChar(64)\n\n topic Topic @relation(fields: [topicId], references: [id], onDelete: Cascade)\n reactions PostReaction[]\n\n @@index([topicId, createdAt])\n @@index([topicId, parentType, parentId])\n @@index([deletedAt])\n @@schema(\"forums\")\n}\n\nmodel PostReaction {\n postId String @db.VarChar(14)\n memberId String @db.VarChar(64)\n reaction PostReactionType\n createdAt DateTime @default(now())\n updatedAt DateTime @default(now()) @updatedAt\n\n post Post @relation(fields: [postId], references: [id], onDelete: Cascade)\n\n @@id([postId, memberId])\n @@index([memberId])\n @@index([postId, reaction])\n @@schema(\"forums\")\n}\n\nmodel TopicClosure {\n ancestorTopicId String @db.VarChar(14)\n descendantTopicId String @db.VarChar(14)\n depth Int\n\n ancestorTopic Topic @relation(\"TopicClosureAncestor\", fields: [ancestorTopicId], references: [id], onDelete: Cascade)\n descendantTopic Topic @relation(\"TopicClosureDescendant\", fields: [descendantTopicId], references: [id], onDelete: Cascade)\n\n @@id([ancestorTopicId, descendantTopicId])\n @@index([ancestorTopicId, depth])\n @@index([descendantTopicId, depth])\n @@schema(\"forums\")\n}\n\nmodel TopicWatch {\n topicId String @db.VarChar(14)\n memberId String @db.VarChar(64)\n createdAt DateTime @default(now())\n\n topic Topic @relation(fields: [topicId], references: [id], onDelete: Cascade)\n\n @@id([topicId, memberId])\n @@index([memberId])\n @@schema(\"forums\")\n}\n\nmodel TopicReadState {\n topicId String @db.VarChar(14)\n memberId String @db.VarChar(64)\n lastReadAt DateTime @default(now())\n updatedAt DateTime @default(now()) @updatedAt\n\n topic Topic @relation(fields: [topicId], references: [id], onDelete: Cascade)\n\n @@id([topicId, memberId])\n @@index([memberId])\n @@index([lastReadAt])\n @@schema(\"forums\")\n}\n\n// Presentation and access rules for imported public category topics only.\nmodel PublicForumCategory {\n topicId String @id @db.VarChar(14)\n description String @default(\"\") @db.Text\n displayAs String @db.VarChar(16)\n sortOrder Int\n readRoles String[]\n createRoles String[]\n replyRoles String[]\n legacyId Int @unique\n legacySlug String @db.Text\n source Json\n\n @@schema(\"forums\")\n}\n\n// Vanilla-only role assignments never grant identity-wide roles.\nmodel PublicForumRoleMember {\n roleName String @db.VarChar(128)\n memberId String @db.VarChar(64)\n\n @@id([roleName, memberId])\n @@index([memberId])\n @@schema(\"forums\")\n}\n\n// Immutable provenance makes imports insert-only and safely repeatable.\nmodel PublicForumImportRecord {\n entityType String @db.VarChar(32)\n sourceId String @db.VarChar(64)\n targetId String @db.VarChar(64)\n sourceHash String @db.VarChar(64)\n targetHash String @db.VarChar(64)\n createdAt DateTime @default(now())\n\n @@id([entityType, sourceId])\n @@unique([entityType, targetId])\n @@schema(\"forums\")\n}\n" } -config.runtimeDataModel = JSON.parse("{\"models\":{\"Topic\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"parentTopicId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"challengeId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"roleName\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"title\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"isAnnouncement\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"locked\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"lockedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"lockedByMemberId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"authorMemberId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"authorHandle\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"deletedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"deletedByMemberId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"parentTopic\",\"kind\":\"object\",\"type\":\"Topic\",\"relationName\":\"TopicChildren\"},{\"name\":\"childTopics\",\"kind\":\"object\",\"type\":\"Topic\",\"relationName\":\"TopicChildren\"},{\"name\":\"posts\",\"kind\":\"object\",\"type\":\"Post\",\"relationName\":\"PostToTopic\"},{\"name\":\"ancestorClosures\",\"kind\":\"object\",\"type\":\"TopicClosure\",\"relationName\":\"TopicClosureAncestor\"},{\"name\":\"descendantClosures\",\"kind\":\"object\",\"type\":\"TopicClosure\",\"relationName\":\"TopicClosureDescendant\"},{\"name\":\"watches\",\"kind\":\"object\",\"type\":\"TopicWatch\",\"relationName\":\"TopicToTopicWatch\"},{\"name\":\"readStates\",\"kind\":\"object\",\"type\":\"TopicReadState\",\"relationName\":\"TopicToTopicReadState\"}],\"dbName\":null},\"MemberBan\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"memberId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"createdByMemberId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"removedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"removedByMemberId\",\"kind\":\"scalar\",\"type\":\"String\"}],\"dbName\":null},\"IpBan\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"ipAddress\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"createdByMemberId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"removedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"removedByMemberId\",\"kind\":\"scalar\",\"type\":\"String\"}],\"dbName\":null},\"Post\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"topicId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"parentType\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"parentId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"authorMemberId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"authorHandle\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"content\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"deletedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"deletedByMemberId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"topic\",\"kind\":\"object\",\"type\":\"Topic\",\"relationName\":\"PostToTopic\"},{\"name\":\"reactions\",\"kind\":\"object\",\"type\":\"PostReaction\",\"relationName\":\"PostToPostReaction\"}],\"dbName\":null},\"PostReaction\":{\"fields\":[{\"name\":\"postId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"memberId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"reaction\",\"kind\":\"enum\",\"type\":\"PostReactionType\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"post\",\"kind\":\"object\",\"type\":\"Post\",\"relationName\":\"PostToPostReaction\"}],\"dbName\":null},\"TopicClosure\":{\"fields\":[{\"name\":\"ancestorTopicId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"descendantTopicId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"depth\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"ancestorTopic\",\"kind\":\"object\",\"type\":\"Topic\",\"relationName\":\"TopicClosureAncestor\"},{\"name\":\"descendantTopic\",\"kind\":\"object\",\"type\":\"Topic\",\"relationName\":\"TopicClosureDescendant\"}],\"dbName\":null},\"TopicWatch\":{\"fields\":[{\"name\":\"topicId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"memberId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"topic\",\"kind\":\"object\",\"type\":\"Topic\",\"relationName\":\"TopicToTopicWatch\"}],\"dbName\":null},\"TopicReadState\":{\"fields\":[{\"name\":\"topicId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"memberId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"lastReadAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"topic\",\"kind\":\"object\",\"type\":\"Topic\",\"relationName\":\"TopicToTopicReadState\"}],\"dbName\":null}},\"enums\":{},\"types\":{}}") +config.runtimeDataModel = JSON.parse("{\"models\":{\"Topic\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"parentTopicId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"challengeId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"roleName\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"title\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"isAnnouncement\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"locked\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"lockedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"lockedByMemberId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"authorMemberId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"authorHandle\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"deletedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"deletedByMemberId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"parentTopic\",\"kind\":\"object\",\"type\":\"Topic\",\"relationName\":\"TopicChildren\"},{\"name\":\"childTopics\",\"kind\":\"object\",\"type\":\"Topic\",\"relationName\":\"TopicChildren\"},{\"name\":\"posts\",\"kind\":\"object\",\"type\":\"Post\",\"relationName\":\"PostToTopic\"},{\"name\":\"ancestorClosures\",\"kind\":\"object\",\"type\":\"TopicClosure\",\"relationName\":\"TopicClosureAncestor\"},{\"name\":\"descendantClosures\",\"kind\":\"object\",\"type\":\"TopicClosure\",\"relationName\":\"TopicClosureDescendant\"},{\"name\":\"watches\",\"kind\":\"object\",\"type\":\"TopicWatch\",\"relationName\":\"TopicToTopicWatch\"},{\"name\":\"readStates\",\"kind\":\"object\",\"type\":\"TopicReadState\",\"relationName\":\"TopicToTopicReadState\"}],\"dbName\":null},\"MemberBan\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"memberId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"createdByMemberId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"removedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"removedByMemberId\",\"kind\":\"scalar\",\"type\":\"String\"}],\"dbName\":null},\"IpBan\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"ipAddress\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"createdByMemberId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"removedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"removedByMemberId\",\"kind\":\"scalar\",\"type\":\"String\"}],\"dbName\":null},\"Post\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"topicId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"parentType\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"parentId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"authorMemberId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"authorHandle\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"content\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"deletedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"deletedByMemberId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"topic\",\"kind\":\"object\",\"type\":\"Topic\",\"relationName\":\"PostToTopic\"},{\"name\":\"reactions\",\"kind\":\"object\",\"type\":\"PostReaction\",\"relationName\":\"PostToPostReaction\"}],\"dbName\":null},\"PostReaction\":{\"fields\":[{\"name\":\"postId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"memberId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"reaction\",\"kind\":\"enum\",\"type\":\"PostReactionType\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"post\",\"kind\":\"object\",\"type\":\"Post\",\"relationName\":\"PostToPostReaction\"}],\"dbName\":null},\"TopicClosure\":{\"fields\":[{\"name\":\"ancestorTopicId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"descendantTopicId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"depth\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"ancestorTopic\",\"kind\":\"object\",\"type\":\"Topic\",\"relationName\":\"TopicClosureAncestor\"},{\"name\":\"descendantTopic\",\"kind\":\"object\",\"type\":\"Topic\",\"relationName\":\"TopicClosureDescendant\"}],\"dbName\":null},\"TopicWatch\":{\"fields\":[{\"name\":\"topicId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"memberId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"topic\",\"kind\":\"object\",\"type\":\"Topic\",\"relationName\":\"TopicToTopicWatch\"}],\"dbName\":null},\"TopicReadState\":{\"fields\":[{\"name\":\"topicId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"memberId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"lastReadAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"topic\",\"kind\":\"object\",\"type\":\"Topic\",\"relationName\":\"TopicToTopicReadState\"}],\"dbName\":null},\"PublicForumCategory\":{\"fields\":[{\"name\":\"topicId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"description\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"displayAs\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"sortOrder\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"readRoles\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createRoles\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"replyRoles\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"legacyId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"legacySlug\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"source\",\"kind\":\"scalar\",\"type\":\"Json\"}],\"dbName\":null},\"PublicForumRoleMember\":{\"fields\":[{\"name\":\"roleName\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"memberId\",\"kind\":\"scalar\",\"type\":\"String\"}],\"dbName\":null},\"PublicForumImportRecord\":{\"fields\":[{\"name\":\"entityType\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"sourceId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"targetId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"sourceHash\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"targetHash\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null}},\"enums\":{},\"types\":{}}") defineDmmfProperty(exports.Prisma, config.runtimeDataModel) config.parameterizationSchema = { - strings: JSON.parse("[\"where\",\"parentTopic\",\"orderBy\",\"cursor\",\"childTopics\",\"topic\",\"post\",\"reactions\",\"_count\",\"posts\",\"ancestorTopic\",\"descendantTopic\",\"ancestorClosures\",\"descendantClosures\",\"watches\",\"readStates\",\"Topic.findUnique\",\"Topic.findUniqueOrThrow\",\"Topic.findFirst\",\"Topic.findFirstOrThrow\",\"Topic.findMany\",\"data\",\"Topic.createOne\",\"Topic.createMany\",\"Topic.createManyAndReturn\",\"Topic.updateOne\",\"Topic.updateMany\",\"Topic.updateManyAndReturn\",\"create\",\"update\",\"Topic.upsertOne\",\"Topic.deleteOne\",\"Topic.deleteMany\",\"having\",\"_min\",\"_max\",\"Topic.groupBy\",\"Topic.aggregate\",\"MemberBan.findUnique\",\"MemberBan.findUniqueOrThrow\",\"MemberBan.findFirst\",\"MemberBan.findFirstOrThrow\",\"MemberBan.findMany\",\"MemberBan.createOne\",\"MemberBan.createMany\",\"MemberBan.createManyAndReturn\",\"MemberBan.updateOne\",\"MemberBan.updateMany\",\"MemberBan.updateManyAndReturn\",\"MemberBan.upsertOne\",\"MemberBan.deleteOne\",\"MemberBan.deleteMany\",\"MemberBan.groupBy\",\"MemberBan.aggregate\",\"IpBan.findUnique\",\"IpBan.findUniqueOrThrow\",\"IpBan.findFirst\",\"IpBan.findFirstOrThrow\",\"IpBan.findMany\",\"IpBan.createOne\",\"IpBan.createMany\",\"IpBan.createManyAndReturn\",\"IpBan.updateOne\",\"IpBan.updateMany\",\"IpBan.updateManyAndReturn\",\"IpBan.upsertOne\",\"IpBan.deleteOne\",\"IpBan.deleteMany\",\"IpBan.groupBy\",\"IpBan.aggregate\",\"Post.findUnique\",\"Post.findUniqueOrThrow\",\"Post.findFirst\",\"Post.findFirstOrThrow\",\"Post.findMany\",\"Post.createOne\",\"Post.createMany\",\"Post.createManyAndReturn\",\"Post.updateOne\",\"Post.updateMany\",\"Post.updateManyAndReturn\",\"Post.upsertOne\",\"Post.deleteOne\",\"Post.deleteMany\",\"Post.groupBy\",\"Post.aggregate\",\"PostReaction.findUnique\",\"PostReaction.findUniqueOrThrow\",\"PostReaction.findFirst\",\"PostReaction.findFirstOrThrow\",\"PostReaction.findMany\",\"PostReaction.createOne\",\"PostReaction.createMany\",\"PostReaction.createManyAndReturn\",\"PostReaction.updateOne\",\"PostReaction.updateMany\",\"PostReaction.updateManyAndReturn\",\"PostReaction.upsertOne\",\"PostReaction.deleteOne\",\"PostReaction.deleteMany\",\"PostReaction.groupBy\",\"PostReaction.aggregate\",\"TopicClosure.findUnique\",\"TopicClosure.findUniqueOrThrow\",\"TopicClosure.findFirst\",\"TopicClosure.findFirstOrThrow\",\"TopicClosure.findMany\",\"TopicClosure.createOne\",\"TopicClosure.createMany\",\"TopicClosure.createManyAndReturn\",\"TopicClosure.updateOne\",\"TopicClosure.updateMany\",\"TopicClosure.updateManyAndReturn\",\"TopicClosure.upsertOne\",\"TopicClosure.deleteOne\",\"TopicClosure.deleteMany\",\"_avg\",\"_sum\",\"TopicClosure.groupBy\",\"TopicClosure.aggregate\",\"TopicWatch.findUnique\",\"TopicWatch.findUniqueOrThrow\",\"TopicWatch.findFirst\",\"TopicWatch.findFirstOrThrow\",\"TopicWatch.findMany\",\"TopicWatch.createOne\",\"TopicWatch.createMany\",\"TopicWatch.createManyAndReturn\",\"TopicWatch.updateOne\",\"TopicWatch.updateMany\",\"TopicWatch.updateManyAndReturn\",\"TopicWatch.upsertOne\",\"TopicWatch.deleteOne\",\"TopicWatch.deleteMany\",\"TopicWatch.groupBy\",\"TopicWatch.aggregate\",\"TopicReadState.findUnique\",\"TopicReadState.findUniqueOrThrow\",\"TopicReadState.findFirst\",\"TopicReadState.findFirstOrThrow\",\"TopicReadState.findMany\",\"TopicReadState.createOne\",\"TopicReadState.createMany\",\"TopicReadState.createManyAndReturn\",\"TopicReadState.updateOne\",\"TopicReadState.updateMany\",\"TopicReadState.updateManyAndReturn\",\"TopicReadState.upsertOne\",\"TopicReadState.deleteOne\",\"TopicReadState.deleteMany\",\"TopicReadState.groupBy\",\"TopicReadState.aggregate\",\"AND\",\"OR\",\"NOT\",\"topicId\",\"memberId\",\"lastReadAt\",\"updatedAt\",\"equals\",\"in\",\"notIn\",\"lt\",\"lte\",\"gt\",\"gte\",\"not\",\"contains\",\"startsWith\",\"endsWith\",\"createdAt\",\"ancestorTopicId\",\"descendantTopicId\",\"depth\",\"postId\",\"PostReactionType\",\"reaction\",\"id\",\"parentType\",\"parentId\",\"authorMemberId\",\"authorHandle\",\"content\",\"deletedAt\",\"deletedByMemberId\",\"ipAddress\",\"createdByMemberId\",\"removedAt\",\"removedByMemberId\",\"parentTopicId\",\"challengeId\",\"roleName\",\"title\",\"isAnnouncement\",\"locked\",\"lockedAt\",\"lockedByMemberId\",\"topicId_memberId\",\"ancestorTopicId_descendantTopicId\",\"postId_memberId\",\"every\",\"some\",\"none\",\"is\",\"isNot\",\"connectOrCreate\",\"upsert\",\"createMany\",\"set\",\"disconnect\",\"delete\",\"connect\",\"updateMany\",\"deleteMany\",\"increment\",\"decrement\",\"multiply\",\"divide\"]"), - graph: "1QNFgAEZAQAAjwIAIAQAAJACACAJAACRAgAgDAAAkgIAIA0AAJICACAOAACTAgAgDwAAlAIAIJgBAACNAgAwmQEAAAMAEJoBAACNAgAwngFAAPYBACGqAUAA9gEAIbEBAQAAAAG0AQEA9QEAIbUBAQD1AQAhtwFAAPgBACG4AQEA9wEAIb0BAQD3AQAhvgEBAPcBACG_AQEA9wEAIcABAQD1AQAhwQEgAI4CACHCASAAjgIAIcMBQAD4AQAhxAEBAPcBACEBAAAAAQAgGQEAAI8CACAEAACQAgAgCQAAkQIAIAwAAJICACANAACSAgAgDgAAkwIAIA8AAJQCACCYAQAAjQIAMJkBAAADABCaAQAAjQIAMJ4BQAD2AQAhqgFAAPYBACGxAQEA9QEAIbQBAQD1AQAhtQEBAPUBACG3AUAA-AEAIbgBAQD3AQAhvQEBAPcBACG-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_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_ABCaAQAA6wEAMJsBAQDbAQAhngFAANwBACGqAUAA3AEAIbEBAQDbAQAhsgEBANsBACGzAQEA2wEAIbQBAQDbAQAhtQEBANsBACG2AQEA7AEAIbcBQADtAQAhuAEBAOwBACEDAAAABwAgAgAAfgAwIQAAfwAgAwAAAAcAIAIAAAgAMAMAAAkAIAEAAAANACABAAAADQAgAwAAAAsAIAIAAAwAMAMAAA0AIAMAAAALACACAAAMADADAAANACADAAAACwAgAgAADAAwAwAADQAgBgYAALACACCcAQEAAAABngFAAAAAAaoBQAAAAAGuAQEAAAABsAEAAACwAQIBFQAAhwEAIAWcAQEAAAABngFAAAAAAaoBQAAAAAGuAQEAAAABsAEAAACwAQIBFQAAiQEAMAEVAACJAQAwBgYAAK8CACCcAQEAmAIAIZ4BQACZAgAhqgFAAJkCACGuAQEAmAIAIbABAACuArABIgIAAAANACAVAACMAQAgBZwBAQCYAgAhngFAAJkCACGqAUAAmQIAIa4BAQCYAgAhsAEAAK4CsAEiAgAAAAsAIBUAAI4BACACAAAACwAgFQAAjgEAIAMAAAANACAcAACHAQAgHQAAjAEAIAEAAAANACABAAAACwAgAwgAAKsCACAiAACtAgAgIwAArAIAIAiYAQAA5wEAMJkBAACVAQAQmgEAAOcBADCcAQEA2wEAIZ4BQADcAQAhqgFAANwBACGuAQEA2wEAIbABAADoAbABIgMAAAALACACAACUAQAwIQAAlQEAIAMAAAALACACAAAMADADAAANACABAAAAEgAgAQAAABIAIAMAAAAQACACAAARADADAAASACADAAAAEAAgAgAAEQAwAwAAEgAgAwAAABAAIAIAABEAMAMAABIAIAUKAACpAgAgCwAAqgIAIKsBAQAAAAGsAQEAAAABrQECAAAAAQEVAACdAQAgA6sBAQAAAAGsAQEAAAABrQECAAAAAQEVAACfAQAwARUAAJ8BADAFCgAApwIAIAsAAKgCACCrAQEAmAIAIawBAQCYAgAhrQECAKYCACECAAAAEgAgFQAAogEAIAOrAQEAmAIAIawBAQCYAgAhrQECAKYCACECAAAAEAAgFQAApAEAIAIAAAAQACAVAACkAQAgAwAAABIAIBwAAJ0BACAdAACiAQAgAQAAABIAIAEAAAAQ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AAABoAEBAAAABaEBAQAAAAWiAQEAAAABowEBAAAAAaQBAQAAAAGlAQEAAAABpgEBAPIBACGnAQEAAAABqAEBAAAAAakBAQAAAAEJmAEAAPMBADCZAQAAaQAQmgEAAPMBADCqAUAA3AEAIbEBAQDbAQAhuQEBANsBACG6AQEA7AEAIbsBQADtAQAhvAEBAOwBACEJmAEAAPQBADCZAQAAVgAQmgEAAPQBADCqAUAA9gEAIbEBAQD1AQAhuQEBAPUBACG6AQEA9wEAIbsBQAD4AQAhvAEBAPcBACELnwEBAAAAAaABAQAAAAShAQEAAAAEogEBAAAAAaMBAQAAAAGkAQEAAAABpQEBAAAAAaYBAQDhAQAhpwEBAAAAAagBAQAAAAGpAQEAAAABCJ8BQAAAAAGgAUAAAAAEoQFAAAAABKIBQAAAAAGjAUAAAAABpAFAAAAAAaUBQAAAAAGmAUAA3wEAIQufAQEAAAABoAEBAAAABaEBAQAAAAWiAQEAAAABowEBAAAAAaQBAQAAAAGlAQEAAAABpgEBAPIBACGnAQEAAAABqAEBAAAAAakBAQAAAAEInwFAAAAAAaABQAAAAAWhAUAAAAAFogFAAAAAAaMBQAAAAAGkAUAAAAABpQFAAAAAAaYBQADwAQAhCZgBAAD5AQAwmQEAAFAAEJoBAAD5AQAwnAEBANsBACGqAUAA3AEAIbEBAQDbAQAhugEBAOwBACG7AUAA7QEAIbwBAQDsAQAhCZgBAAD6AQAwmQEAAD0AEJoBAAD6AQAwnAEBAPUBACGqAUAA9gEAIbEBAQD1AQAhugEBAPcBACG7AUAA-AEAIbwBAQD3AQAhEpgBAAD7AQAwmQEAADcAEJoBAAD7AQAwngFAANwBACGqAUAA3AEAIbEBAQDbAQAhtAEBANsBACG1AQEA2wEAIbcBQADtAQAhuAEBAOwBACG9AQEA7AEAIb4BAQDsAQAhvwEBAOwBACHAAQEA2wEAIcEBIAD8AQAhwgEgAPwBACHDAUAA7QEAIcQBAQDsAQAhBQgAAN4BACAiAAD-AQAgIwAA_gEAIJ8BIAAAAAGmASAA_QEAIQUIAADeAQAgIgAA_gEAICMAAP4BACCfASAAAAABpgEgAP0BACECnwEgAAAAAaYBIAD-AQAhApsBAQAAAAGcAQEAAAABCAUAAIECACCYAQAAgAIAMJkBAAAZABCaAQAAgAIAMJsBAQD1AQAhnAEBAPUBACGdAUAA9gEAIZ4BQAD2AQAhGwEAAI8CACAEAACQAgAgCQAAkQIAIAwAAJICACANAACSAgAgDgAAkwIAIA8AAJQCACCYAQAAjQIAMJkBAAADABCaAQAAjQIAMJ4BQAD2AQAhqgFAAPYBACGxAQEA9QEAIbQBAQD1AQAhtQEBAPUBACG3AUAA-AEAIbgBAQD3AQAhvQEBAPcBACG-AQEA9wEAIb8BAQD3AQAhwAEBAPUBACHBASAAjgIAIcIBIACOAgAhwwFAAPgBACHEAQEA9wEAIcsBAAADACDMAQAAAwAgApsBAQAAAAGcAQEAAAABBwUAAIECACCYAQAAgwIAMJkBAAAVABCaAQAAgwIAMJsBAQD1AQAhnAEBAPUBACGqAUAA9gEAIQKrAQEAAAABrAEBAAAAAQgKAACBAgAgCwAAgQIAIJgBAACFAgAwmQEAABAAEJoBAACFAgAwqwEBAPUBACGsAQEA9QEAIa0BAgCGAgAhCJ8BAgAAAAGgAQIAAAAEoQECAAAABKIBAgAAAAGjAQIAAAABpAECAAAAAaUBAgAAAAGmAQIA3gEAIQKcAQEAAAABrgEBAAAAAQkGAACKAgAgmAEAAIgCADCZAQAACwAQmgEAAIgCADCcAQEA9QEAIZ4BQAD2AQAhqgFAAPYBACGuAQEA9QEAIbABAACJArABIgSfAQAAALABAqABAAAAsAEIoQEAAACwAQimAQAA6gGwASISBQAAgQIAIAcAAIwCACCYAQAAiwIAMJkBAAAHABCaAQAAiwIAMJsBAQD1AQAhngFAAPYBACGqAUAA9gEAIbEBAQD1AQAhsgEBAPUBACGzAQEA9QEAIbQBAQD1AQAhtQEBAPUBACG2AQEA9wEAIbcBQAD4AQAhuAEBAPcBACHLAQAABwAgzAEAAAcAIBAFAACBAgAgBwAAjAIAIJgBAACLAgAwmQEAAAcAEJoBAACLAgAwmwEBAPUBACGeAUAA9gEAIaoBQAD2AQAhsQEBAPUBACGyAQEA9QEAIbMBAQD1AQAhtAEBAPUBACG1AQEA9QEAIbYBAQD3AQAhtwFAAPgBACG4AQEA9wEAIQPIAQAACwAgyQEAAAsAIMoBAAALACAZAQAAjwIAIAQAAJACACAJAACRAgAgDAAAkgIAIA0AAJICACAOAACTAgAgDwAAlAIAIJgBAACNAgAwmQEAAAMAEJoBAACNAgAwngFAAPYBACGqAUAA9gEAIbEBAQD1AQAhtAEBAPUBACG1AQEA9QEAIbcBQAD4AQAhuAEBAPcBACG9AQEA9wEAIb4BAQD3AQAhvwEBAPcBACHAAQEA9QEAIcEBIACOAgAhwgEgAI4CACHDAUAA-AEAIcQBAQD3AQAhAp8BIAAAAAGmASAA_gEAIRsBAACPAgAgBAAAkAIAIAkAAJECACAMAACSAgAgDQAAkgIAIA4AAJMCACAPAACUAgAgmAEAAI0CADCZAQAAAwAQmgEAAI0CADCeAUAA9gEAIaoBQAD2AQAhsQEBAPUBACG0AQEA9QEAIbUBAQD1AQAhtwFAAPgBACG4AQEA9wEAIb0BAQD3AQAhvgEBAPcBACG_AQEA9wEAIcABAQD1AQAhwQEgAI4CACHCASAAjgIAIcMBQAD4AQAhxAEBAPcBACHLAQAAAwAgzAEAAAMAIAPIAQAAAwAgyQEAAAMAIMoBAAADACADyAEAAAcAIMkBAAAHACDKAQAABwAgA8gBAAAQACDJAQAAEAAgygEAABAAIAPIAQAAFQAgyQEAABUAIMoBAAAVACADyAEAABkAIMkBAAAZACDKAQAAGQAgAAAAAdABAQAAAAEB0AFAAAAAAQUcAADRAwAgHQAA1AMAIM0BAADSAwAgzgEAANMDACDTAQAAAQAgAxwAANEDACDNAQAA0gMAINMBAAABACAAAAAFHAAAzAMAIB0AAM8DACDNAQAAzQMAIM4BAADOAwAg0wEAAAEAIAMcAADMAwAgzQEAAM0DACDTAQAAAQAgAAAAAAAF0AECAAAAAdYBAgAAAAHXAQIAAAAB2AECAAAAAdkBAgAAAAEFHAAAxAMAIB0AAMoDACDNAQAAxQMAIM4BAADJAwAg0wEAAAEAIAUcAADCAwAgHQAAxwMAIM0BAADDAwAgzgEAAMYDACDTAQAAAQAgAxwAAMQDACDNAQAAxQMAINMBAAABACADHAAAwgMAIM0BAADDAwAg0wEAAAEAIAAAAAHQAQAAALABAgUcAAC9AwAgHQAAwAMAIM0BAAC-AwAgzgEAAL8DACDTAQAACQAgAxwAAL0DACDNAQAAvgMAINMBAAAJACAAAAAAAdABAQAAAAEB0AFAAAAAAQUcAAC3AwAgHQAAuwMAIM0BAAC4AwAgzgEAALoDACDTAQAAAQAgCxwAALkCADAdAAC-AgAwzQEAALoCADDOAQAAuwIAMM8BAAC8AgAg0AEAAL0CADDRAQAAvQIAMNIBAAC9AgAw0wEAAL0CADDUAQAAvwIAMNUBAADAAgAwBJwBAQAAAAGeAUAAAAABqgFAAAAAAbABAAAAsAECAgAAAA0AIBwAAMQCACADAAAADQAgHAAAxAIAIB0AAMMCACABFQAAuQMAMAoGAACKAgAgmAEAAIgCADCZAQAACwAQmgEAAIgCADCcAQEA9QEAIZ4BQAD2AQAhqgFAAPYBACGuAQEA9QEAIbABAACJArABIscBAACHAgAgAgAAAA0AIBUAAMMCACACAAAAwQIAIBUAAMICACAImAEAAMACADCZAQAAwQIAEJoBAADAAgAwnAEBAPUBACGeAUAA9gEAIaoBQAD2AQAhrgEBAPUBACGwAQAAiQKwASIImAEAAMACADCZAQAAwQIAEJoBAADAAgAwnAEBAPUBACGeAUAA9gEAIaoBQAD2AQAhrgEBAPUBACGwAQAAiQKwASIEnAEBAJgCACGeAUAAmQIAIaoBQACZAgAhsAEAAK4CsAEiBJwBAQCYAgAhngFAAJkCACGqAUAAmQIAIbABAACuArABIgScAQEAAAABngFAAAAAAaoBQAAAAAGwAQAAALABAgMcAAC3AwAgzQEAALgDACDTAQAAAQAgBBwAALkCADDNAQAAugIAMM8BAAC8AgAg0wEAAL0CADAAAAAAAAAAAAAB0AEgAAAAAQccAACsAwAgHQAAtQMAIM0BAACtAwAgzgEAALQDACDRAQAAAwAg0gEAAAMAINMBAAABACALHAAAkQMAMB0AAJYDADDNAQAAkgMAMM4BAACTAwAwzwEAAJQDACDQAQAAlQMAMNEBAACVAwAw0gEAAJUDADDTAQAAlQMAMNQBAACXAwAw1QEAAJgDADALHAAAhQMAMB0AAIoDADDNAQAAhgMAMM4BAACHAwAwzwEAAIgDACDQAQAAiQMAMNEBAACJAwAw0gEAAIkDADDTAQAAiQMAMNQBAACLAwAw1QEAAIwDADALHAAA_AIAMB0AAIADADDNAQAA_QIAMM4BAAD-AgAwzwEAAP8CACDQAQAA9AIAMNEBAAD0AgAw0gEAAPQCADDTAQAA9AIAMNQBAACBAwAw1QEAAPcCADALHAAA8AIAMB0AAPUCADDNAQAA8QIAMM4BAADyAgAwzwEAAPMCACDQAQAA9AIAMNEBAAD0AgAw0gEAAPQCADDTAQAA9AIAMNQBAAD2AgAw1QEAAPcCADALHAAA5AIAMB0AAOkCADDNAQAA5QIAMM4BAADmAgAwzwEAAOcCACDQAQAA6AIAMNEBAADoAgAw0gEAAOgCADDTAQAA6AIAMNQBAADqAgAw1QEAAOsCADALHAAA2AIAMB0AAN0CADDNAQAA2QIAMM4BAADaAgAwzwEAANsCACDQAQAA3AIAMNEBAADcAgAw0gEAANwCADDTAQAA3AIAMNQBAADeAgAw1QEAAN8CADADnAEBAAAAAZ0BQAAAAAGeAUAAAAABAgAAABsAIBwAAOMCACADAAAAGwAgHAAA4wIAIB0AAOICACABFQAAswMAMAkFAACBAgAgmAEAAIACADCZAQAAGQAQmgEAAIACADCbAQEA9QEAIZwBAQD1AQAhnQFAAPYBACGeAUAA9gEAIcUBAAD_AQAgAgAAABsAIBUAAOICACACAAAA4AIAIBUAAOECACAHmAEAAN8CADCZAQAA4AIAEJoBAADfAgAwmwEBAPUBACGcAQEA9QEAIZ0BQAD2AQAhngFAAPYBACEHmAEAAN8CADCZAQAA4AIAEJoBAADfAgAwmwEBAPUBACGcAQEA9QEAIZ0BQAD2AQAhngFAAPYBACEDnAEBAJgCACGdAUAAmQIAIZ4BQACZAgAhA5wBAQCYAgAhnQFAAJkCACGeAUAAmQIAIQOcAQEAAAABnQFAAAAAAZ4BQAAAAAECnAEBAAAAAaoBQAAAAAECAAAAFwAgHAAA7wIAIAMAAAAXACAcAADvAgAgHQAA7gIAIAEVAACyAwAwCAUAAIECACCYAQAAgwIAMJkBAAAVABCaAQAAgwIAMJsBAQD1AQAhnAEBAPUBACGqAUAA9gEAIcUBAACCAgAgAgAAABcAIBUAAO4CACACAAAA7AIAIBUAAO0CACAGmAEAAOsCADCZAQAA7AIAEJoBAADrAgAwmwEBAPUBACGcAQEA9QEAIaoBQAD2AQAhBpgBAADrAgAwmQEAAOwCABCaAQAA6wIAMJsBAQD1AQAhnAEBAPUBACGqAUAA9gEAIQKcAQEAmAIAIaoBQACZAgAhApwBAQCYAgAhqgFAAJkCACECnAEBAAAAAaoBQAAAAAEDCgAAqQIAIKsBAQAAAAGtAQIAAAABAgAAABIAIBwAAPsCACADAAAAEgAgHAAA-wIAIB0AAPoCACABFQAAsQMAMAkKAACBAgAgCwAAgQIAIJgBAACFAgAwmQEAABAAEJoBAACFAgAwqwEBAPUBACGsAQEA9QEAIa0BAgCGAgAhxgEAAIQCACACAAAAEgAgFQAA-gIAIAIAAAD4AgAgFQAA-QIAIAaYAQAA9wIAMJkBAAD4AgAQmgEAAPcCADCrAQEA9QEAIawBAQD1AQAhrQECAIYCACEGmAEAAPcCADCZAQAA-AIAEJoBAAD3AgAwqwEBAPUBACGsAQEA9QEAIa0BAgCGAgAhAqsBAQCYAgAhrQECAKYCACEDCgAApwIAIKsBAQCYAgAhrQECAKYCACEDCgAAqQIAIKsBAQAAAAGtAQIAAAABAwsAAKoCACCsAQEAAAABrQECAAAAAQIAAAASACAcAACEAwAgAwAAABIAIBwAAIQDACAdAACDAwAgARUAALADADACAAAAEgAgFQAAgwMAIAIAAAD4AgAgFQAAggMAIAKsAQEAmAIAIa0BAgCmAgAhAwsAAKgCACCsAQEAmAIAIa0BAgCmAgAhAwsAAKoCACCsAQEAAAABrQECAAAAAQsHAADGAgAgngFAAAAAAaoBQAAAAAGxAQEAAAABsgEBAAAAAbMBAQAAAAG0AQEAAAABtQEBAAAAAbYBAQAAAAG3AUAAAAABuAEBAAAAAQIAAAAJACAcAACQAwAgAwAAAAkAIBwAAJADACAdAACPAwAgARUAAK8DADAQBQAAgQIAIAcAAIwCACCYAQAAiwIAMJkBAAAHABCaAQAAiwIAMJsBAQD1AQAhngFAAPYBACGqAUAA9gEAIbEBAQAAAAGyAQEA9QEAIbMBAQD1AQAhtAEBAPUBACG1AQEA9QEAIbYBAQD3AQAhtwFAAPgBACG4AQEA9wEAIQIAAAAJACAVAACPAwAgAgAAAI0DACAVAACOAwAgDpgBAACMAwAwmQEAAI0DABCaAQAAjAMAMJsBAQD1AQAhngFAAPYBACGqAUAA9gEAIbEBAQD1AQAhsgEBAPUBACGzAQEA9QEAIbQBAQD1AQAhtQEBAPUBACG2AQEA9wEAIbcBQAD4AQAhuAEBAPcBACEOmAEAAIwDADCZAQAAjQMAEJoBAACMAwAwmwEBAPUBACGeAUAA9gEAIaoBQAD2AQAhsQEBAPUBACGyAQEA9QEAIbMBAQD1AQAhtAEBAPUBACG1AQEA9QEAIbYBAQD3AQAhtwFAAPgBACG4AQEA9wEAIQqeAUAAmQIAIaoBQACZAgAhsQEBAJgCACGyAQEAmAIAIbMBAQCYAgAhtAEBAJgCACG1AQEAmAIAIbYBAQC1AgAhtwFAALYCACG4AQEAtQIAIQsHAAC4AgAgngFAAJkCACGqAUAAmQIAIbEBAQCYAgAhsgEBAJgCACGzAQEAmAIAIbQBAQCYAgAhtQEBAJgCACG2AQEAtQIAIbcBQAC2AgAhuAEBALUCACELBwAAxgIAIJ4BQAAAAAGqAUAAAAABsQEBAAAAAbIBAQAAAAGzAQEAAAABtAEBAAAAAbUBAQAAAAG2AQEAAAABtwFAAAAAAbgBAQAAAAEUBAAAnQMAIAkAAJ4DACAMAACfAwAgDQAAoAMAIA4AAKEDACAPAACiAwAgngFAAAAAAaoBQAAAAAGxAQEAAAABtAEBAAAAAbUBAQAAAAG3AUAAAAABuAEBAAAAAb4BAQAAAAG_AQEAAAABwAEBAAAAAcEBIAAAAAHCASAAAAABwwFAAAAAAcQBAQAAAAECAAAAAQAgHAAAnAMAIAMAAAABACAcAACcAwAgHQAAmwMAIAEVAACuAwAwGQEAAI8CACAEAACQAgAgCQAAkQIAIAwAAJICACANAACSAgAgDgAAkwIAIA8AAJQCACCYAQAAjQIAMJkBAAADABCaAQAAjQIAMJ4BQAD2AQAhqgFAAPYBACGxAQEAAAABtAEBAPUBACG1AQEA9QEAIbcBQAD4AQAhuAEBAPcBACG9AQEA9wEAIb4BAQD3AQAhvwEBAPcBACHAAQEA9QEAIcEBIACOAgAhwgEgAI4CACHDAUAA-AEAIcQBAQD3AQAhAgAAAAEAIBUAAJsDACACAAAAmQMAIBUAAJoDACASmAEAAJgDADCZAQAAmQMAEJoBAACYAwAwngFAAPYBACGqAUAA9gEAIbEBAQD1AQAhtAEBAPUBACG1AQEA9QEAIbcBQAD4AQAhuAEBAPcBACG9AQEA9wEAIb4BAQD3AQAhvwEBAPcBACHAAQEA9QEAIcEBIACOAgAhwgEgAI4CACHDAUAA-AEAIcQBAQD3AQAhEpgBAACYAwAwmQEAAJkDABCaAQAAmAMAMJ4BQAD2AQAhqgFAAPYBACGxAQEA9QEAIbQBAQD1AQAhtQEBAPUBACG3AUAA-AEAIbgBAQD3AQAhvQEBAPcBACG-AQEA9wEAIb8BAQD3AQAhwAEBAPUBACHBASAAjgIAIcIBIACOAgAhwwFAAPgBACHEAQEA9wEAIQ6eAUAAmQIAIaoBQACZAgAhsQEBAJgCACG0AQEAmAIAIbUBAQCYAgAhtwFAALYCACG4AQEAtQIAIb4BAQC1AgAhvwEBALUCACHAAQEAmAIAIcEBIADQAgAhwgEgANACACHDAUAAtgIAIcQBAQC1AgAhFAQAANICACAJAADTAgAgDAAA1AIAIA0AANUCACAOAADWAgAgDwAA1wIAIJ4BQACZAgAhqgFAAJkCACGxAQEAmAIAIbQBAQCYAgAhtQEBAJgCACG3AUAAtgIAIbgBAQC1AgAhvgEBALUCACG_AQEAtQIAIcABAQCYAgAhwQEgANACACHCASAA0AIAIcMBQAC2AgAhxAEBALUCACEUBAAAnQMAIAkAAJ4DACAMAACfAwAgDQAAoAMAIA4AAKEDACAPAACiAwAgngFAAAAAAaoBQAAAAAGxAQEAAAABtAEBAAAAAbUBAQAAAAG3AUAAAAABuAEBAAAAAb4BAQAAAAG_AQEAAAABwAEBAAAAAcEBIAAAAAHCASAAAAABwwFAAAAAAcQBAQAAAAEEHAAAkQMAMM0BAACSAwAwzwEAAJQDACDTAQAAlQMAMAQcAACFAwAwzQEAAIYDADDPAQAAiAMAINMBAACJAwAwBBwAAPwCADDNAQAA_QIAMM8BAAD_AgAg0wEAAPQCADAEHAAA8AIAMM0BAADxAgAwzwEAAPMCACDTAQAA9AIAMAQcAADkAgAwzQEAAOUCADDPAQAA5wIAINMBAADoAgAwBBwAANgCADDNAQAA2QIAMM8BAADbAgAg0wEAANwCADADHAAArAMAIM0BAACtAwAg0wEAAAEAIA4BAACkAwAgBAAApwMAIAkAAKgDACAMAACpAwAgDQAAqQMAIA4AAKoDACAPAACrAwAgtwEAALECACC4AQAAsQIAIL0BAACxAgAgvgEAALECACC_AQAAsQIAIMMBAACxAgAgxAEAALECACAFBQAApAMAIAcAAKYDACC2AQAAsQIAILcBAACxAgAguAEAALECACAAAAAAAAAVAQAAowMAIAkAAJ4DACAMAACfAwAgDQAAoAMAIA4AAKEDACAPAACiAwAgngFAAAAAAaoBQAAAAAGxAQEAAAABtAEBAAAAAbUBAQAAAAG3AUAAAAABuAEBAAAAAb0BAQAAAAG-AQEAAAABvwEBAAAAAcABAQAAAAHBASAAAAABwgEgAAAAAcMBQAAAAAHEAQEAAAABAgAAAAEAIBwAAKwDACAOngFAAAAAAaoBQAAAAAGxAQEAAAABtAEBAAAAAbUBAQAAAAG3AUAAAAABuAEBAAAAAb4BAQAAAAG_AQEAAAABwAEBAAAAAcEBIAAAAAHCASAAAAABwwFAAAAAAcQBAQAAAAEKngFAAAAAAaoBQAAAAAGxAQEAAAABsgEBAAAAAbMBAQAAAAG0AQEAAAABtQEBAAAAAbYBAQAAAAG3AUAAAAABuAEBAAAAAQKsAQEAAAABrQECAAAAAQKrAQEAAAABrQECAAAAAQKcAQEAAAABqgFAAAAAAQOcAQEAAAABnQFAAAAAAZ4BQAAAAAEDAAAAAwAgHAAArAMAIB0AALYDACAXAAAAAwAgAQAA0QIAIAkAANMCACAMAADUAgAgDQAA1QIAIA4AANYCACAPAADXAgAgFQAAtgMAIJ4BQACZAgAhqgFAAJkCACGxAQEAmAIAIbQBAQCYAgAhtQEBAJgCACG3AUAAtgIAIbgBAQC1AgAhvQEBALUCACG-AQEAtQIAIb8BAQC1AgAhwAEBAJgCACHBASAA0AIAIcIBIADQAgAhwwFAALYCACHEAQEAtQIAIRUBAADRAgAgCQAA0wIAIAwAANQCACANAADVAgAgDgAA1gIAIA8AANcCACCeAUAAmQIAIaoBQACZAgAhsQEBAJgCACG0AQEAmAIAIbUBAQCYAgAhtwFAALYCACG4AQEAtQIAIb0BAQC1AgAhvgEBALUCACG_AQEAtQIAIcABAQCYAgAhwQEgANACACHCASAA0AIAIcMBQAC2AgAhxAEBALUCACEVAQAAowMAIAQAAJ0DACAMAACfAwAgDQAAoAMAIA4AAKEDACAPAACiAwAgngFAAAAAAaoBQAAAAAGxAQEAAAABtAEBAAAAAbUBAQAAAAG3AUAAAAABuAEBAAAAAb0BAQAAAAG-AQEAAAABvwEBAAAAAcABAQAAAAHBASAAAAABwgEgAAAAAcMBQAAAAAHEAQEAAAABAgAAAAEAIBwAALcDACAEnAEBAAAAAZ4BQAAAAAGqAUAAAAABsAEAAACwAQIDAAAAAwAgHAAAtwMAIB0AALwDACAXAAAAAwAgAQAA0QIAIAQAANICACAMAADUAgAgDQAA1QIAIA4AANYCACAPAADXAgAgFQAAvAMAIJ4BQACZAgAhqgFAAJkCACGxAQEAmAIAIbQBAQCYAgAhtQEBAJgCACG3AUAAtgIAIbgBAQC1AgAhvQEBALUCACG-AQEAtQIAIb8BAQC1AgAhwAEBAJgCACHBASAA0AIAIcIBIADQAgAhwwFAALYCACHEAQEAtQIAIRUBAADRAgAgBAAA0gIAIAwAANQCACANAADVAgAgDgAA1gIAIA8AANcCACCeAUAAmQIAIaoBQACZAgAhsQEBAJgCACG0AQEAmAIAIbUBAQCYAgAhtwFAALYCACG4AQEAtQIAIb0BAQC1AgAhvgEBALUCACG_AQEAtQIAIcABAQCYAgAhwQEgANACACHCASAA0AIAIcMBQAC2AgAhxAEBALUCACEMBQAAxQIAIJsBAQAAAAGeAUAAAAABqgFAAAAAAbEBAQAAAAGyAQEAAAABswEBAAAAAbQBAQAAAAG1AQEAAAABtgEBAAAAAbcBQAAAAAG4AQEAAAABAgAAAAkAIBwAAL0DACADAAAABwAgHAAAvQMAIB0AAMEDACAOAAAABwAgBQAAtwIAIBUAAMEDACCbAQEAmAIAIZ4BQACZAgAhqgFAAJkCACGxAQEAmAIAIbIBAQCYAgAhswEBAJgCACG0AQEAmAIAIbUBAQCYAgAhtgEBALUCACG3AUAAtgIAIbgBAQC1AgAhDAUAALcCACCbAQEAmAIAIZ4BQACZAgAhqgFAAJkCACGxAQEAmAIAIbIBAQCYAgAhswEBAJgCACG0AQEAmAIAIbUBAQCYAgAhtgEBALUCACG3AUAAtgIAIbgBAQC1AgAhFQEAAKMDACAEAACdAwAgCQAAngMAIAwAAJ8DACAOAAChAwAgDwAAogMAIJ4BQAAAAAGqAUAAAAABsQEBAAAAAbQBAQAAAAG1AQEAAAABtwFAAAAAAbgBAQAAAAG9AQEAAAABvgEBAAAAAb8BAQAAAAHAAQEAAAABwQEgAAAAAcIBIAAAAAHDAUAAAAABxAEBAAAAAQIAAAABACAcAADCAwAgFQEAAKMDACAEAACdAwAgCQAAngMAIA0AAKADACAOAAChAwAgDwAAogMAIJ4BQAAAAAGqAUAAAAABsQEBAAAAAbQBAQAAAAG1AQEAAAABtwFAAAAAAbgBAQAAAAG9AQEAAAABvgEBAAAAAb8BAQAAAAHAAQEAAAABwQEgAAAAAcIBIAAAAAHDAUAAAAABxAEBAAAAAQIAAAABACAcAADEAwAgAwAAAAMAIBwAAMIDACAdAADIAwAgFwAAAAMAIAEAANECACAEAADSAgAgCQAA0wIAIAwAANQCACAOAADWAgAgDwAA1wIAIBUAAMgDACCeAUAAmQIAIaoBQACZAgAhsQEBAJgCACG0AQEAmAIAIbUBAQCYAgAhtwFAALYCACG4AQEAtQIAIb0BAQC1AgAhvgEBALUCACG_AQEAtQIAIcABAQCYAgAhwQEgANACACHCASAA0AIAIcMBQAC2AgAhxAEBALUCACEVAQAA0QIAIAQAANICACAJAADTAgAgDAAA1AIAIA4AANYCACAPAADXAgAgngFAAJkCACGqAUAAmQIAIbEBAQCYAgAhtAEBAJgCACG1AQEAmAIAIbcBQAC2AgAhuAEBALUCACG9AQEAtQIAIb4BAQC1AgAhvwEBALUCACHAAQEAmAIAIcEBIADQAgAhwgEgANACACHDAUAAtgIAIcQBAQC1AgAhAwAAAAMAIBwAAMQDACAdAADLAwAgFwAAAAMAIAEAANECACAEAADSAgAgCQAA0wIAIA0AANUCACAOAADWAgAgDwAA1wIAIBUAAMsDACCeAUAAmQIAIaoBQACZAgAhsQEBAJgCACG0AQEAmAIAIbUBAQCYAgAhtwFAALYCACG4AQEAtQIAIb0BAQC1AgAhvgEBALUCACG_AQEAtQIAIcABAQCYAgAhwQEgANACACHCASAA0AIAIcMBQAC2AgAhxAEBALUCACEVAQAA0QIAIAQAANICACAJAADTAgAgDQAA1QIAIA4AANYCACAPAADXAgAgngFAAJkCACGqAUAAmQIAIbEBAQCYAgAhtAEBAJgCACG1AQEAmAIAIbcBQAC2AgAhuAEBALUCACG9AQEAtQIAIb4BAQC1AgAhvwEBALUCACHAAQEAmAIAIcEBIADQAgAhwgEgANACACHDAUAAtgIAIcQBAQC1AgAhFQEAAKMDACAEAACdAwAgCQAAngMAIAwAAJ8DACANAACgAwAgDwAAogMAIJ4BQAAAAAGqAUAAAAABsQEBAAAAAbQBAQAAAAG1AQEAAAABtwFAAAAAAbgBAQAAAAG9AQEAAAABvgEBAAAAAb8BAQAAAAHAAQEAAAABwQEgAAAAAcIBIAAAAAHDAUAAAAABxAEBAAAAAQIAAAABACAcAADMAwAgAwAAAAMAIBwAAMwDACAdAADQAwAgFwAAAAMAIAEAANECACAEAADSAgAgCQAA0wIAIAwAANQCACANAADVAgAgDwAA1wIAIBUAANADACCeAUAAmQIAIaoBQACZAgAhsQEBAJgCACG0AQEAmAIAIbUBAQCYAgAhtwFAALYCACG4AQEAtQIAIb0BAQC1AgAhvgEBALUCACG_AQEAtQIAIcABAQCYAgAhwQEgANACACHCASAA0AIAIcMBQAC2AgAhxAEBALUCACEVAQAA0QIAIAQAANICACAJAADTAgAgDAAA1AIAIA0AANUCACAPAADXAgAgngFAAJkCACGqAUAAmQIAIbEBAQCYAgAhtAEBAJgCACG1AQEAmAIAIbcBQAC2AgAhuAEBALUCACG9AQEAtQIAIb4BAQC1AgAhvwEBALUCACHAAQEAmAIAIcEBIADQAgAhwgEgANACACHDAUAAtgIAIcQBAQC1AgAhFQEAAKMDACAEAACdAwAgCQAAngMAIAwAAJ8DACANAACgAwAgDgAAoQMAIJ4BQAAAAAGqAUAAAAABsQEBAAAAAbQBAQAAAAG1AQEAAAABtwFAAAAAAbgBAQAAAAG9AQEAAAABvgEBAAAAAb8BAQAAAAHAAQEAAAABwQEgAAAAAcIBIAAAAAHDAUAAAAABxAEBAAAAAQIAAAABACAcAADRAwAgAwAAAAMAIBwAANEDACAdAADVAwAgFwAAAAMAIAEAANECACAEAADSAgAgCQAA0wIAIAwAANQCACANAADVAgAgDgAA1gIAIBUAANUDACCeAUAAmQIAIaoBQACZAgAhsQEBAJgCACG0AQEAmAIAIbUBAQCYAgAhtwFAALYCACG4AQEAtQIAIb0BAQC1AgAhvgEBALUCACG_AQEAtQIAIcABAQCYAgAhwQEgANACACHCASAA0AIAIcMBQAC2AgAhxAEBALUCACEVAQAA0QIAIAQAANICACAJAADTAgAgDAAA1AIAIA0AANUCACAOAADWAgAgngFAAJkCACGqAUAAmQIAIbEBAQCYAgAhtAEBAJgCACG1AQEAmAIAIbcBQAC2AgAhuAEBALUCACG9AQEAtQIAIb4BAQC1AgAhvwEBALUCACHAAQEAmAIAIcEBIADQAgAhwgEgANACACHDAUAAtgIAIcQBAQC1AgAhCAEEAQQGAQgACAkKAgwTBQ0UBQ4YBg8cBwMFAAEHDgMIAAQBBgACAQcPAAIKAAELAAEBBQABAQUAAQYEHQAJHgAMHwANIAAOIQAPIgAAAQEsAQEBMgEDCAANIgAOIwAPAAAAAwgADSIADiMADwAAAAMIABUiABYjABcAAAADCAAVIgAWIwAXAAAAAwgAHSIAHiMAHwAAAAMIAB0iAB4jAB8BBQABAQUAAQMIACQiACUjACYAAAADCAAkIgAlIwAmAQYAAgEGAAIDCAArIgAsIwAtAAAAAwgAKyIALCMALQIKAAELAAECCgABCwABBQgAMiIANSMANnQAM3UANAAAAAAABQgAMiIANSMANnQAM3UANAEFAAEBBQABAwgAOyIAPCMAPQAAAAMIADsiADwjAD0BBQABAQUAAQMIAEIiAEMjAEQAAAADCABCIgBDIwBEEAIBESMBEiQBEyUBFCYBFigBFyoJGCsKGS4BGjAJGzELHjMBHzQBIDUJJDgMJTkQJjsRJzwRKD8RKUARKkERK0MRLEUJLUYSLkgRL0oJMEsTMUwRMk0RM04JNFEUNVIYNlQZN1UZOFgZOVkZOloZO1wZPF4JPV8aPmEZP2MJQGQbQWUZQmYZQ2cJRGocRWsgRmwCR20CSG4CSW8CSnACS3ICTHQJTXUhTncCT3kJUHoiUXsCUnwCU30JVIABI1WBASdWggEDV4MBA1iEAQNZhQEDWoYBA1uIAQNcigEJXYsBKF6NAQNfjwEJYJABKWGRAQNikgEDY5MBCWSWASpllwEuZpgBBWeZAQVomgEFaZsBBWqcAQVrngEFbKABCW2hAS9uowEFb6UBCXCmATBxpwEFcqgBBXOpAQl2rAExd60BN3iuAQZ5rwEGerABBnuxAQZ8sgEGfbQBBn62AQl_twE4gAG5AQaBAbsBCYIBvAE5gwG9AQaEAb4BBoUBvwEJhgHCATqHAcMBPogBxAEHiQHFAQeKAcYBB4sBxwEHjAHIAQeNAcoBB44BzAEJjwHNAT-QAc8BB5EB0QEJkgHSAUCTAdMBB5QB1AEHlQHVAQmWAdgBQZcB2QFF" + strings: JSON.parse("[\"where\",\"parentTopic\",\"orderBy\",\"cursor\",\"childTopics\",\"topic\",\"post\",\"reactions\",\"_count\",\"posts\",\"ancestorTopic\",\"descendantTopic\",\"ancestorClosures\",\"descendantClosures\",\"watches\",\"readStates\",\"Topic.findUnique\",\"Topic.findUniqueOrThrow\",\"Topic.findFirst\",\"Topic.findFirstOrThrow\",\"Topic.findMany\",\"data\",\"Topic.createOne\",\"Topic.createMany\",\"Topic.createManyAndReturn\",\"Topic.updateOne\",\"Topic.updateMany\",\"Topic.updateManyAndReturn\",\"create\",\"update\",\"Topic.upsertOne\",\"Topic.deleteOne\",\"Topic.deleteMany\",\"having\",\"_min\",\"_max\",\"Topic.groupBy\",\"Topic.aggregate\",\"MemberBan.findUnique\",\"MemberBan.findUniqueOrThrow\",\"MemberBan.findFirst\",\"MemberBan.findFirstOrThrow\",\"MemberBan.findMany\",\"MemberBan.createOne\",\"MemberBan.createMany\",\"MemberBan.createManyAndReturn\",\"MemberBan.updateOne\",\"MemberBan.updateMany\",\"MemberBan.updateManyAndReturn\",\"MemberBan.upsertOne\",\"MemberBan.deleteOne\",\"MemberBan.deleteMany\",\"MemberBan.groupBy\",\"MemberBan.aggregate\",\"IpBan.findUnique\",\"IpBan.findUniqueOrThrow\",\"IpBan.findFirst\",\"IpBan.findFirstOrThrow\",\"IpBan.findMany\",\"IpBan.createOne\",\"IpBan.createMany\",\"IpBan.createManyAndReturn\",\"IpBan.updateOne\",\"IpBan.updateMany\",\"IpBan.updateManyAndReturn\",\"IpBan.upsertOne\",\"IpBan.deleteOne\",\"IpBan.deleteMany\",\"IpBan.groupBy\",\"IpBan.aggregate\",\"Post.findUnique\",\"Post.findUniqueOrThrow\",\"Post.findFirst\",\"Post.findFirstOrThrow\",\"Post.findMany\",\"Post.createOne\",\"Post.createMany\",\"Post.createManyAndReturn\",\"Post.updateOne\",\"Post.updateMany\",\"Post.updateManyAndReturn\",\"Post.upsertOne\",\"Post.deleteOne\",\"Post.deleteMany\",\"Post.groupBy\",\"Post.aggregate\",\"PostReaction.findUnique\",\"PostReaction.findUniqueOrThrow\",\"PostReaction.findFirst\",\"PostReaction.findFirstOrThrow\",\"PostReaction.findMany\",\"PostReaction.createOne\",\"PostReaction.createMany\",\"PostReaction.createManyAndReturn\",\"PostReaction.updateOne\",\"PostReaction.updateMany\",\"PostReaction.updateManyAndReturn\",\"PostReaction.upsertOne\",\"PostReaction.deleteOne\",\"PostReaction.deleteMany\",\"PostReaction.groupBy\",\"PostReaction.aggregate\",\"TopicClosure.findUnique\",\"TopicClosure.findUniqueOrThrow\",\"TopicClosure.findFirst\",\"TopicClosure.findFirstOrThrow\",\"TopicClosure.findMany\",\"TopicClosure.createOne\",\"TopicClosure.createMany\",\"TopicClosure.createManyAndReturn\",\"TopicClosure.updateOne\",\"TopicClosure.updateMany\",\"TopicClosure.updateManyAndReturn\",\"TopicClosure.upsertOne\",\"TopicClosure.deleteOne\",\"TopicClosure.deleteMany\",\"_avg\",\"_sum\",\"TopicClosure.groupBy\",\"TopicClosure.aggregate\",\"TopicWatch.findUnique\",\"TopicWatch.findUniqueOrThrow\",\"TopicWatch.findFirst\",\"TopicWatch.findFirstOrThrow\",\"TopicWatch.findMany\",\"TopicWatch.createOne\",\"TopicWatch.createMany\",\"TopicWatch.createManyAndReturn\",\"TopicWatch.updateOne\",\"TopicWatch.updateMany\",\"TopicWatch.updateManyAndReturn\",\"TopicWatch.upsertOne\",\"TopicWatch.deleteOne\",\"TopicWatch.deleteMany\",\"TopicWatch.groupBy\",\"TopicWatch.aggregate\",\"TopicReadState.findUnique\",\"TopicReadState.findUniqueOrThrow\",\"TopicReadState.findFirst\",\"TopicReadState.findFirstOrThrow\",\"TopicReadState.findMany\",\"TopicReadState.createOne\",\"TopicReadState.createMany\",\"TopicReadState.createManyAndReturn\",\"TopicReadState.updateOne\",\"TopicReadState.updateMany\",\"TopicReadState.updateManyAndReturn\",\"TopicReadState.upsertOne\",\"TopicReadState.deleteOne\",\"TopicReadState.deleteMany\",\"TopicReadState.groupBy\",\"TopicReadState.aggregate\",\"PublicForumCategory.findUnique\",\"PublicForumCategory.findUniqueOrThrow\",\"PublicForumCategory.findFirst\",\"PublicForumCategory.findFirstOrThrow\",\"PublicForumCategory.findMany\",\"PublicForumCategory.createOne\",\"PublicForumCategory.createMany\",\"PublicForumCategory.createManyAndReturn\",\"PublicForumCategory.updateOne\",\"PublicForumCategory.updateMany\",\"PublicForumCategory.updateManyAndReturn\",\"PublicForumCategory.upsertOne\",\"PublicForumCategory.deleteOne\",\"PublicForumCategory.deleteMany\",\"PublicForumCategory.groupBy\",\"PublicForumCategory.aggregate\",\"PublicForumRoleMember.findUnique\",\"PublicForumRoleMember.findUniqueOrThrow\",\"PublicForumRoleMember.findFirst\",\"PublicForumRoleMember.findFirstOrThrow\",\"PublicForumRoleMember.findMany\",\"PublicForumRoleMember.createOne\",\"PublicForumRoleMember.createMany\",\"PublicForumRoleMember.createManyAndReturn\",\"PublicForumRoleMember.updateOne\",\"PublicForumRoleMember.updateMany\",\"PublicForumRoleMember.updateManyAndReturn\",\"PublicForumRoleMember.upsertOne\",\"PublicForumRoleMember.deleteOne\",\"PublicForumRoleMember.deleteMany\",\"PublicForumRoleMember.groupBy\",\"PublicForumRoleMember.aggregate\",\"PublicForumImportRecord.findUnique\",\"PublicForumImportRecord.findUniqueOrThrow\",\"PublicForumImportRecord.findFirst\",\"PublicForumImportRecord.findFirstOrThrow\",\"PublicForumImportRecord.findMany\",\"PublicForumImportRecord.createOne\",\"PublicForumImportRecord.createMany\",\"PublicForumImportRecord.createManyAndReturn\",\"PublicForumImportRecord.updateOne\",\"PublicForumImportRecord.updateMany\",\"PublicForumImportRecord.updateManyAndReturn\",\"PublicForumImportRecord.upsertOne\",\"PublicForumImportRecord.deleteOne\",\"PublicForumImportRecord.deleteMany\",\"PublicForumImportRecord.groupBy\",\"PublicForumImportRecord.aggregate\",\"AND\",\"OR\",\"NOT\",\"entityType\",\"sourceId\",\"targetId\",\"sourceHash\",\"targetHash\",\"createdAt\",\"equals\",\"in\",\"notIn\",\"lt\",\"lte\",\"gt\",\"gte\",\"not\",\"contains\",\"startsWith\",\"endsWith\",\"entityType_targetId\",\"entityType_sourceId\",\"roleName\",\"memberId\",\"roleName_memberId\",\"topicId\",\"description\",\"displayAs\",\"sortOrder\",\"readRoles\",\"createRoles\",\"replyRoles\",\"legacyId\",\"legacySlug\",\"source\",\"string_contains\",\"string_starts_with\",\"string_ends_with\",\"array_starts_with\",\"array_ends_with\",\"array_contains\",\"has\",\"hasEvery\",\"hasSome\",\"lastReadAt\",\"updatedAt\",\"ancestorTopicId\",\"descendantTopicId\",\"depth\",\"postId\",\"PostReactionType\",\"reaction\",\"id\",\"parentType\",\"parentId\",\"authorMemberId\",\"authorHandle\",\"content\",\"deletedAt\",\"deletedByMemberId\",\"ipAddress\",\"createdByMemberId\",\"removedAt\",\"removedByMemberId\",\"parentTopicId\",\"challengeId\",\"title\",\"isAnnouncement\",\"locked\",\"lockedAt\",\"lockedByMemberId\",\"topicId_memberId\",\"ancestorTopicId_descendantTopicId\",\"postId_memberId\",\"every\",\"some\",\"none\",\"is\",\"isNot\",\"connectOrCreate\",\"upsert\",\"createMany\",\"set\",\"disconnect\",\"delete\",\"connect\",\"updateMany\",\"deleteMany\",\"push\",\"increment\",\"decrement\",\"multiply\",\"divide\"]"), + graph: "vgRfsAEZAQAA5wIAIAQAAOgCACAJAADpAgAgDAAA6gIAIA0AAOoCACAOAADrAgAgDwAA7AIAIMgBAADlAgAwyQEAAAMAEMoBAADlAgAw0AFAAK8CACHeAQEA0AIAIfUBQACvAgAh_AEBAAAAAf8BAQCuAgAhgAIBAK4CACGCAkAA0QIAIYMCAQDQAgAhiAIBANACACGJAgEA0AIAIYoCAQCuAgAhiwIgAOYCACGMAiAA5gIAIY0CQADRAgAhjgIBANACACEBAAAAAQAgGQEAAOcCACAEAADoAgAgCQAA6QIAIAwAAOoCACANAADqAgAgDgAA6wIAIA8AAOwCACDIAQAA5QIAMMkBAAADABDKAQAA5QIAMNABQACvAgAh3gEBANACACH1AUAArwIAIfwBAQCuAgAh_wEBAK4CACGAAgEArgIAIYICQADRAgAhgwIBANACACGIAgEA0AIAIYkCAQDQAgAhigIBAK4CACGLAiAA5gIAIYwCIADmAgAhjQJAANECACGOAgEA0AIAIQEAAAADACAOAQAAjQQAIAQAAJAEACAJAACRBAAgDAAAkgQAIA0AAJIEACAOAACTBAAgDwAAlAQAIN4BAACaAwAgggIAAJoDACCDAgAAmgMAIIgCAACaAwAgiQIAAJoDACCNAgAAmgMAII4CAACaAwAgAwAAAAMAIAIAAAUAMAMAAAEAIBAFAADaAgAgBwAA5AIAIMgBAADjAgAwyQEAAAcAEMoBAADjAgAw0AFAAK8CACHhAQEArgIAIfUBQACvAgAh_AEBAK4CACH9AQEArgIAIf4BAQCuAgAh_wEBAK4CACGAAgEArgIAIYECAQDQAgAhggJAANECACGDAgEA0AIAIQUFAACNBAAgBwAAjwQAIIECAACaAwAgggIAAJoDACCDAgAAmgMAIBAFAADaAgAgBwAA5AIAIMgBAADjAgAwyQEAAAcAEMoBAADjAgAw0AFAAK8CACHhAQEArgIAIfUBQACvAgAh_AEBAAAAAf0BAQCuAgAh_gEBAK4CACH_AQEArgIAIYACAQCuAgAhgQIBANACACGCAkAA0QIAIYMCAQDQAgAhAwAAAAcAIAIAAAgAMAMAAAkAIAkGAADiAgAgyAEAAOACADDJAQAACwAQygEAAOACADDQAUAArwIAId8BAQCuAgAh9QFAAK8CACH5AQEArgIAIfsBAADhAvsBIgEGAACOBAAgCgYAAOICACDIAQAA4AIAMMkBAAALABDKAQAA4AIAMNABQACvAgAh3wEBAK4CACH1AUAArwIAIfkBAQCuAgAh-wEAAOEC-wEikQIAAN8CACADAAAACwAgAgAADAAwAwAADQAgAQAAAAsAIAgKAADaAgAgCwAA2gIAIMgBAADeAgAwyQEAABAAEMoBAADeAgAw9gEBAK4CACH3AQEArgIAIfgBAgC9AgAhAgoAAI0EACALAACNBAAgCQoAANoCACALAADaAgAgyAEAAN4CADDJAQAAEAAQygEAAN4CADD2AQEArgIAIfcBAQCuAgAh-AECAL0CACGQAgAA3QIAIAMAAAAQACACAAARADADAAASACADAAAAEAAgAgAAEQAwAwAAEgAgBwUAANoCACDIAQAA3AIAMMkBAAAVABDKAQAA3AIAMNABQACvAgAh3wEBAK4CACHhAQEArgIAIQEFAACNBAAgCAUAANoCACDIAQAA3AIAMMkBAAAVABDKAQAA3AIAMNABQACvAgAh3wEBAK4CACHhAQEArgIAIY8CAADbAgAgAwAAABUAIAIAABYAMAMAABcAIAgFAADaAgAgyAEAANkCADDJAQAAGQAQygEAANkCADDfAQEArgIAIeEBAQCuAgAh9AFAAK8CACH1AUAArwIAIQEFAACNBAAgCQUAANoCACDIAQAA2QIAMMkBAAAZABDKAQAA2QIAMN8BAQCuAgAh4QEBAK4CACH0AUAArwIAIfUBQACvAgAhjwIAANgCACADAAAAGQAgAgAAGgAwAwAAGwAgAQAAAAMAIAEAAAAHACABAAAAEAAgAQAAABAAIAEAAAAVACABAAAAGQAgAQAAAAEAIAMAAAADACACAAAFADADAAABACADAAAAAwAgAgAABQAwAwAAAQAgAwAAAAMAIAIAAAUAMAMAAAEAIBYBAACMBAAgBAAAhgQAIAkAAIcEACAMAACIBAAgDQAAiQQAIA4AAIoEACAPAACLBAAg0AFAAAAAAd4BAQAAAAH1AUAAAAAB_AEBAAAAAf8BAQAAAAGAAgEAAAABggJAAAAAAYMCAQAAAAGIAgEAAAABiQIBAAAAAYoCAQAAAAGLAiAAAAABjAIgAAAAAY0CQAAAAAGOAgEAAAABARUAACcAIA_QAUAAAAAB3gEBAAAAAfUBQAAAAAH8AQEAAAAB_wEBAAAAAYACAQAAAAGCAkAAAAABgwIBAAAAAYgCAQAAAAGJAgEAAAABigIBAAAAAYsCIAAAAAGMAiAAAAABjQJAAAAAAY4CAQAAAAEBFQAAKQAwARUAACkAMAEAAAADACAWAQAAugMAIAQAALsDACAJAAC8AwAgDAAAvQMAIA0AAL4DACAOAAC_AwAgDwAAwAMAINABQADxAgAh3gEBAJ4DACH1AUAA8QIAIfwBAQDwAgAh_wEBAPACACGAAgEA8AIAIYICQACfAwAhgwIBAJ4DACGIAgEAngMAIYkCAQCeAwAhigIBAPACACGLAiAAuQMAIYwCIAC5AwAhjQJAAJ8DACGOAgEAngMAIQIAAAABACAVAAAtACAP0AFAAPECACHeAQEAngMAIfUBQADxAgAh_AEBAPACACH_AQEA8AIAIYACAQDwAgAhggJAAJ8DACGDAgEAngMAIYgCAQCeAwAhiQIBAJ4DACGKAgEA8AIAIYsCIAC5AwAhjAIgALkDACGNAkAAnwMAIY4CAQCeAwAhAgAAAAMAIBUAAC8AIAIAAAADACAVAAAvACABAAAAAwAgAwAAAAEAIBwAACcAIB0AAC0AIAEAAAABACABAAAAAwAgCggAALYDACAiAAC4AwAgIwAAtwMAIN4BAACaAwAgggIAAJoDACCDAgAAmgMAIIgCAACaAwAgiQIAAJoDACCNAgAAmgMAII4CAACaAwAgEsgBAADUAgAwyQEAADcAEMoBAADUAgAw0AFAAKcCACHeAQEAxwIAIfUBQACnAgAh_AEBAKYCACH_AQEApgIAIYACAQCmAgAhggJAAMgCACGDAgEAxwIAIYgCAQDHAgAhiQIBAMcCACGKAgEApgIAIYsCIADVAgAhjAIgANUCACGNAkAAyAIAIY4CAQDHAgAhAwAAAAMAIAIAADYAMCEAADcAIAMAAAADACACAAAFADADAAABACAJyAEAANMCADDJAQAAPQAQygEAANMCADDQAUAArwIAId8BAQCuAgAh_AEBAAAAAYUCAQDQAgAhhgJAANECACGHAgEA0AIAIQEAAAA6ACABAAAAOgAgCcgBAADTAgAwyQEAAD0AEMoBAADTAgAw0AFAAK8CACHfAQEArgIAIfwBAQCuAgAhhQIBANACACGGAkAA0QIAIYcCAQDQAgAhA4UCAACaAwAghgIAAJoDACCHAgAAmgMAIAMAAAA9ACACAAA-ADADAAA6ACADAAAAPQAgAgAAPgAwAwAAOgAgAwAAAD0AIAIAAD4AMAMAADoAIAbQAUAAAAAB3wEBAAAAAfwBAQAAAAGFAgEAAAABhgJAAAAAAYcCAQAAAAEBFQAAQgAgBtABQAAAAAHfAQEAAAAB_AEBAAAAAYUCAQAAAAGGAkAAAAABhwIBAAAAAQEVAABEADABFQAARAAwBtABQADxAgAh3wEBAPACACH8AQEA8AIAIYUCAQCeAwAhhgJAAJ8DACGHAgEAngMAIQIAAAA6ACAVAABHACAG0AFAAPECACHfAQEA8AIAIfwBAQDwAgAhhQIBAJ4DACGGAkAAnwMAIYcCAQCeAwAhAgAAAD0AIBUAAEkAIAIAAAA9ACAVAABJACADAAAAOgAgHAAAQgAgHQAARwAgAQAAADoAIAEAAAA9ACAGCAAAswMAICIAALUDACAjAAC0AwAghQIAAJoDACCGAgAAmgMAIIcCAACaAwAgCcgBAADSAgAwyQEAAFAAEMoBAADSAgAw0AFAAKcCACHfAQEApgIAIfwBAQCmAgAhhQIBAMcCACGGAkAAyAIAIYcCAQDHAgAhAwAAAD0AIAIAAE8AMCEAAFAAIAMAAAA9ACACAAA-ADADAAA6ACAJyAEAAM8CADDJAQAAVgAQygEAAM8CADDQAUAArwIAIfwBAQAAAAGEAgEArgIAIYUCAQDQAgAhhgJAANECACGHAgEA0AIAIQEAAABTACABAAAAUwAgCcgBAADPAgAwyQEAAFYAEMoBAADPAgAw0AFAAK8CACH8AQEArgIAIYQCAQCuAgAhhQIBANACACGGAkAA0QIAIYcCAQDQAgAhA4UCAACaAwAghgIAAJoDACCHAgAAmgMAIAMAAABWACACAABXADADAABTACADAAAAVgAgAgAAVwAwAwAAUwAgAwAAAFYAIAIAAFcAMAMAAFMAIAbQAUAAAAAB_AEBAAAAAYQCAQAAAAGFAgEAAAABhgJAAAAAAYcCAQAAAAEBFQAAWwAgBtABQAAAAAH8AQEAAAABhAIBAAAAAYUCAQAAAAGGAkAAAAABhwIBAAAAAQEVAABdADABFQAAXQAwBtABQADxAgAh_AEBAPACACGEAgEA8AIAIYUCAQCeAwAhhgJAAJ8DACGHAgEAngMAIQIAAABTACAVAABgACAG0AFAAPECACH8AQEA8AIAIYQCAQDwAgAhhQIBAJ4DACGGAkAAnwMAIYcCAQCeAwAhAgAAAFYAIBUAAGIAIAIAAABWACAVAABiACADAAAAUwAgHAAAWwAgHQAAYAAgAQAAAFMAIAEAAABWACAGCAAAsAMAICIAALIDACAjAACxAwAghQIAAJoDACCGAgAAmgMAIIcCAACaAwAgCcgBAADOAgAwyQEAAGkAEMoBAADOAgAw0AFAAKcCACH8AQEApgIAIYQCAQCmAgAhhQIBAMcCACGGAkAAyAIAIYcCAQDHAgAhAwAAAFYAIAIAAGgAMCEAAGkAIAMAAABWACACAABXADADAABTACABAAAACQAgAQAAAAkAIAMAAAAHACACAAAIADADAAAJACADAAAABwAgAgAACAAwAwAACQAgAwAAAAcAIAIAAAgAMAMAAAkAIA0FAACuAwAgBwAArwMAINABQAAAAAHhAQEAAAAB9QFAAAAAAfwBAQAAAAH9AQEAAAAB_gEBAAAAAf8BAQAAAAGAAgEAAAABgQIBAAAAAYICQAAAAAGDAgEAAAABARUAAHEAIAvQAUAAAAAB4QEBAAAAAfUBQAAAAAH8AQEAAAAB_QEBAAAAAf4BAQAAAAH_AQEAAAABgAIBAAAAAYECAQAAAAGCAkAAAAABgwIBAAAAAQEVAABzADABFQAAcwAwDQUAAKADACAHAAChAwAg0AFAAPECACHhAQEA8AIAIfUBQADxAgAh_AEBAPACACH9AQEA8AIAIf4BAQDwAgAh_wEBAPACACGAAgEA8AIAIYECAQCeAwAhggJAAJ8DACGDAgEAngMAIQIAAAAJACAVAAB2ACAL0AFAAPECACHhAQEA8AIAIfUBQADxAgAh_AEBAPACACH9AQEA8AIAIf4BAQDwAgAh_wEBAPACACGAAgEA8AIAIYECAQCeAwAhggJAAJ8DACGDAgEAngMAIQIAAAAHACAVAAB4ACACAAAABwAgFQAAeAAgAwAAAAkAIBwAAHEAIB0AAHYAIAEAAAAJACABAAAABwAgBggAAJsDACAiAACdAwAgIwAAnAMAIIECAACaAwAgggIAAJoDACCDAgAAmgMAIA7IAQAAxgIAMMkBAAB_ABDKAQAAxgIAMNABQACnAgAh4QEBAKYCACH1AUAApwIAIfwBAQCmAgAh_QEBAKYCACH-AQEApgIAIf8BAQCmAgAhgAIBAKYCACGBAgEAxwIAIYICQADIAgAhgwIBAMcCACEDAAAABwAgAgAAfgAwIQAAfwAgAwAAAAcAIAIAAAgAMAMAAAkAIAEAAAANACABAAAADQAgAwAAAAsAIAIAAAwAMAMAAA0AIAMAAAALACACAAAMADADAAANACADAAAACwAgAgAADAAwAwAADQAgBgYAAJkDACDQAUAAAAAB3wEBAAAAAfUBQAAAAAH5AQEAAAAB-wEAAAD7AQIBFQAAhwEAIAXQAUAAAAAB3wEBAAAAAfUBQAAAAAH5AQEAAAAB-wEAAAD7AQIBFQAAiQEAMAEVAACJAQAwBgYAAJgDACDQAUAA8QIAId8BAQDwAgAh9QFAAPECACH5AQEA8AIAIfsBAACXA_sBIgIAAAANACAVAACMAQAgBdABQADxAgAh3wEBAPACACH1AUAA8QIAIfkBAQDwAgAh-wEAAJcD-wEiAgAAAAsAIBUAAI4BACACAAAACwAgFQAAjgEAIAMAAAANACAcAACHAQAgHQAAjAEAIAEAAAANACABAAAACwAgAwgAAJQDACAiAACWAwAgIwAAlQMAIAjIAQAAwgIAMMkBAACVAQAQygEAAMICADDQAUAApwIAId8BAQCmAgAh9QFAAKcCACH5AQEApgIAIfsBAADDAvsBIgMAAAALACACAACUAQAwIQAAlQEAIAMAAAALACACAAAMADADAAANACABAAAAEgAgAQAAABIAIAMAAAAQACACAAARADADAAASACADAAAAEAAgAgAAEQAwAwAAEgAgAwAAABAAIAIAABEAMAMAABIAIAUKAACSAwAgCwAAkwMAIPYBAQAAAAH3AQEAAAAB-AECAAAAAQEVAACdAQAgA_YBAQAAAAH3AQEAAAAB-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_gIAIOYBAAD_AgAg5wEAAIADACDoAQIAAAAB6QEBAAAAAeoBgAAAAAEBFQAA5AEAMAEVAADkAQAwCuEBAQDwAgAh4gEBAPACACHjAQEA8AIAIeQBAgD6AgAh5QEAAPsCACDmAQAA_AIAIOcBAAD9AgAg6AECAPoCACHpAQEA8AIAIeoBgAAAAAECAAAA2gEAIBUAAOcBACAK4QEBAPACACHiAQEA8AIAIeMBAQDwAgAh5AECAPoCACHlAQAA-wIAIOYBAAD8AgAg5wEAAP0CACDoAQIA-gIAIekBAQDwAgAh6gGAAAAAAQIAAADdAQAgFQAA6QEAIAIAAADdAQAgFQAA6QEAIAMAAADaAQAgHAAA4gEAIB0AAOcBACABAAAA2gEAIAEAAADdAQAgBQgAAPUCACAiAAD4AgAgIwAA9wIAIHQAAPYCACB1AAD5AgAgDcgBAAC1AgAwyQEAAPABABDKAQAAtQIAMOEBAQCmAgAh4gEBAKYCACHjAQEApgIAIeQBAgC2AgAh5QEAALcCACDmAQAAtwIAIOcBAAC3AgAg6AECALYCACHpAQEApgIAIeoBAAC4AgAgAwAAAN0BACACAADvAQAwIQAA8AEAIAMAAADdAQAgAgAA3gEAMAMAANoBACAGyAEAALMCADDJAQAA9gEAEMoBAACzAgAw3gEBAK4CACHfAQEArgIAIeABAAC0AgAgAQAAAPMBACABAAAA8wEAIAXIAQAAswIAMMkBAAD2AQAQygEAALMCADDeAQEArgIAId8BAQCuAgAhAAMAAAD2AQAgAgAA9wEAMAMAAPMBACADAAAA9gEAIAIAAPcBADADAADzAQAgAwAAAPYBACACAAD3AQAwAwAA8wEAIALeAQEAAAAB3wEBAAAAAQEVAAD7AQAgAt4BAQAAAAHfAQEAAAABARUAAP0BADABFQAA_QEAMALeAQEA8AIAId8BAQDwAgAhAgAAAPMBACAVAACAAgAgAt4BAQDwAgAh3wEBAPACACECAAAA9gEAIBUAAIICACACAAAA9gEAIBUAAIICACADAAAA8wEAIBwAAPsBACAdAACAAgAgAQAAAPMBACABAAAA9gEAIAMIAADyAgAgIgAA9AIAICMAAPMCACAFyAEAALICADDJAQAAiQIAEMoBAACyAgAw3gEBAKYCACHfAQEApgIAIQMAAAD2AQAgAgAAiAIAMCEAAIkCACADAAAA9gEAIAIAAPcBADADAADzAQAgC8gBAACtAgAwyQEAAI8CABDKAQAArQIAMMsBAQCuAgAhzAEBAK4CACHNAQEArgIAIc4BAQCuAgAhzwEBAK4CACHQAUAArwIAIdwBAACwAgAg3QEAALECACABAAAAjAIAIAEAAACMAgAgCcgBAACtAgAwyQEAAI8CABDKAQAArQIAMMsBAQCuAgAhzAEBAK4CACHNAQEArgIAIc4BAQCuAgAhzwEBAK4CACHQAUAArwIAIQADAAAAjwIAIAIAAJACADADAACMAgAgAwAAAI8CACACAACQAgAwAwAAjAIAIAMAAACPAgAgAgAAkAIAMAMAAIwCACAGywEBAAAAAcwBAQAAAAHNAQEAAAABzgEBAAAAAc8BAQAAAAHQAUAAAAABARUAAJQCACAGywEBAAAAAcwBAQAAAAHNAQEAAAABzgEBAAAAAc8BAQAAAAHQAUAAAAABARUAAJYCADABFQAAlgIAMAbLAQEA8AIAIcwBAQDwAgAhzQEBAPACACHOAQEA8AIAIc8BAQDwAgAh0AFAAPECACECAAAAjAIAIBUAAJkCACAGywEBAPACACHMAQEA8AIAIc0BAQDwAgAhzgEBAPACACHPAQEA8AIAIdABQADxAgAhAgAAAI8CACAVAACbAgAgAgAAAI8CACAVAACbAgAgAwAAAIwCACAcAACUAgAgHQAAmQIAIAEAAACMAgAgAQAAAI8CACADCAAA7QIAICIAAO8CACAjAADuAgAgCcgBAAClAgAwyQEAAKICABDKAQAApQIAMMsBAQCmAgAhzAEBAKYCACHNAQEApgIAIc4BAQCmAgAhzwEBAKYCACHQAUAApwIAIQMAAACPAgAgAgAAoQIAMCEAAKICACADAAAAjwIAIAIAAJACADADAACMAgAgCcgBAAClAgAwyQEAAKICABDKAQAApQIAMMsBAQCmAgAhzAEBAKYCACHNAQEApgIAIc4BAQCmAgAhzwEBAKYCACHQAUAApwIAIQ4IAACpAgAgIgAArAIAICMAAKwCACDRAQEAAAAB0gEBAAAABNMBAQAAAATUAQEAAAAB1QEBAAAAAdYBAQAAAAHXAQEAAAAB2AEBAKsCACHZAQEAAAAB2gEBAAAAAdsBAQAAAAELCAAAqQIAICIAAKoCACAjAACqAgAg0QFAAAAAAdIBQAAAAATTAUAAAAAE1AFAAAAAAdUBQAAAAAHWAUAAAAAB1wFAAAAAAdgBQACoAgAhCwgAAKkCACAiAACqAgAgIwAAqgIAINEBQAAAAAHSAUAAAAAE0wFAAAAABNQBQAAAAAHVAUAAAAAB1gFAAAAAAdcBQAAAAAHYAUAAqAIAIQjRAQIAAAAB0gECAAAABNMBAgAAAATUAQIAAAAB1QECAAAAAdYBAgAAAAHXAQIAAAAB2AECAKkCACEI0QFAAAAAAdIBQAAAAATTAUAAAAAE1AFAAAAAAdUBQAAAAAHWAUAAAAAB1wFAAAAAAdgBQACqAgAhDggAAKkCACAiAACsAgAgIwAArAIAINEBAQAAAAHSAQEAAAAE0wEBAAAABNQBAQAAAAHVAQEAAAAB1gEBAAAAAdcBAQAAAAHYAQEAqwIAIdkBAQAAAAHaAQEAAAAB2wEBAAAAAQvRAQEAAAAB0gEBAAAABNMBAQAAAATUAQEAAAAB1QEBAAAAAdYBAQAAAAHXAQEAAAAB2AEBAKwCACHZAQEAAAAB2gEBAAAAAdsBAQAAAAEJyAEAAK0CADDJAQAAjwIAEMoBAACtAgAwywEBAK4CACHMAQEArgIAIc0BAQCuAgAhzgEBAK4CACHPAQEArgIAIdABQACvAgAhC9EBAQAAAAHSAQEAAAAE0wEBAAAABNQBAQAAAAHVAQEAAAAB1gEBAAAAAdcBAQAAAAHYAQEArAIAIdkBAQAAAAHaAQEAAAAB2wEBAAAAAQjRAUAAAAAB0gFAAAAABNMBQAAAAATUAUAAAAAB1QFAAAAAAdYBQAAAAAHXAUAAAAAB2AFAAKoCACECywEBAAAAAc0BAQAAAAECywEBAAAAAcwBAQAAAAEFyAEAALICADDJAQAAiQIAEMoBAACyAgAw3gEBAKYCACHfAQEApgIAIQXIAQAAswIAMMkBAAD2AQAQygEAALMCADDeAQEArgIAId8BAQCuAgAhAt4BAQAAAAHfAQEAAAABDcgBAAC1AgAwyQEAAPABABDKAQAAtQIAMOEBAQCmAgAh4gEBAKYCACHjAQEApgIAIeQBAgC2AgAh5QEAALcCACDmAQAAtwIAIOcBAAC3AgAg6AECALYCACHpAQEApgIAIeoBAAC4AgAgDQgAAKkCACAiAACpAgAgIwAAqQIAIHQAALsCACB1AACpAgAg0QECAAAAAdIBAgAAAATTAQIAAAAE1AECAAAAAdUBAgAAAAHWAQIAAAAB1wECAAAAAdgBAgC6AgAhBNEBAQAAAAXxAQEAAAAB8gEBAAAABPMBAQAAAAQPCAAAqQIAICIAALkCACAjAAC5AgAg0QGAAAAAAdQBgAAAAAHVAYAAAAAB1gGAAAAAAdcBgAAAAAHYAYAAAAAB6wEBAAAAAewBAQAAAAHtAQEAAAAB7gGAAAAAAe8BgAAAAAHwAYAAAAABDNEBgAAAAAHUAYAAAAAB1QGAAAAAAdYBgAAAAAHXAYAAAAAB2AGAAAAAAesBAQAAAAHsAQEAAAAB7QEBAAAAAe4BgAAAAAHvAYAAAAAB8AGAAAAAAQ0IAACpAgAgIgAAqQIAICMAAKkCACB0AAC7AgAgdQAAqQIAINEBAgAAAAHSAQIAAAAE0wECAAAABNQBAgAAAAHVAQIAAAAB1gECAAAAAdcBAgAAAAHYAQIAugIAIQjRAQgAAAAB0gEIAAAABNMBCAAAAATUAQgAAAAB1QEIAAAAAdYBCAAAAAHXAQgAAAAB2AEIALsCACENyAEAALwCADDJAQAA3QEAEMoBAAC8AgAw4QEBAK4CACHiAQEArgIAIeMBAQCuAgAh5AECAL0CACHlAQAAtwIAIOYBAAC3AgAg5wEAALcCACDoAQIAvQIAIekBAQCuAgAh6gEAAL4CACAI0QECAAAAAdIBAgAAAATTAQIAAAAE1AECAAAAAdUBAgAAAAHWAQIAAAAB1wECAAAAAdgBAgCpAgAhDNEBgAAAAAHUAYAAAAAB1QGAAAAAAdYBgAAAAAHXAYAAAAAB2AGAAAAAAesBAQAAAAHsAQEAAAAB7QEBAAAAAe4BgAAAAAHvAYAAAAAB8AGAAAAAAQfIAQAAvwIAMMkBAADXAQAQygEAAL8CADDfAQEApgIAIeEBAQCmAgAh9AFAAKcCACH1AUAApwIAIQbIAQAAwAIAMMkBAADBAQAQygEAAMACADDQAUAApwIAId8BAQCmAgAh4QEBAKYCACEGyAEAAMECADDJAQAAqwEAEMoBAADBAgAw9gEBAKYCACH3AQEApgIAIfgBAgC2AgAhCMgBAADCAgAwyQEAAJUBABDKAQAAwgIAMNABQACnAgAh3wEBAKYCACH1AUAApwIAIfkBAQCmAgAh-wEAAMMC-wEiBwgAAKkCACAiAADFAgAgIwAAxQIAINEBAAAA-wEC0gEAAAD7AQjTAQAAAPsBCNgBAADEAvsBIgcIAACpAgAgIgAAxQIAICMAAMUCACDRAQAAAPsBAtIBAAAA-wEI0wEAAAD7AQjYAQAAxAL7ASIE0QEAAAD7AQLSAQAAAPsBCNMBAAAA-wEI2AEAAMUC-wEiDsgBAADGAgAwyQEAAH8AEMoBAADGAgAw0AFAAKcCACHhAQEApgIAIfUBQACnAgAh_AEBAKYCACH9AQEApgIAIf4BAQCmAgAh_wEBAKYCACGAAgEApgIAIYECAQDHAgAhggJAAMgCACGDAgEAxwIAIQ4IAADKAgAgIgAAzQIAICMAAM0CACDRAQEAAAAB0gEBAAAABdMBAQAAAAXUAQEAAAAB1QEBAAAAAdYBAQAAAAHXAQEAAAAB2AEBAMwCACHZAQEAAAAB2gEBAAAAAdsBAQAAAAELCAAAygIAICIAAMsCACAjAADLAgAg0QFAAAAAAdIBQAAAAAXTAUAAAAAF1AFAAAAAAdUBQAAAAAHWAUAAAAAB1wFAAAAAAdgBQADJAgAhCwgAAMoCACAiAADLAgAgIwAAywIAINEBQAAAAAHSAUAAAAAF0wFAAAAABdQBQAAAAAHVAUAAAAAB1gFAAAAAAdcBQAAAAAHYAUAAyQIAIQjRAQIAAAAB0gECAAAABdMBAgAAAAXUAQIAAAAB1QECAAAAAdYBAgAAAAHXAQIAAAAB2AECAMoCACEI0QFAAAAAAdIBQAAAAAXTAUAAAAAF1AFAAAAAAdUBQAAAAAHWAUAAAAAB1wFAAAAAAdgBQADLAgAhDggAAMoCACAiAADNAgAgIwAAzQIAINEBAQAAAAHSAQEAAAAF0wEBAAAABdQBAQAAAAHVAQEAAAAB1gEBAAAAAdcBAQAAAAHYAQEAzAIAIdkBAQAAAAHaAQEAAAAB2wEBAAAAAQvRAQEAAAAB0gEBAAAABdMBAQAAAAXUAQEAAAAB1QEBAAAAAdYBAQAAAAHXAQEAAAAB2AEBAM0CACHZAQEAAAAB2gEBAAAAAdsBAQAAAAEJyAEAAM4CADDJAQAAaQAQygEAAM4CADDQAUAApwIAIfwBAQCmAgAhhAIBAKYCACGFAgEAxwIAIYYCQADIAgAhhwIBAMcCACEJyAEAAM8CADDJAQAAVgAQygEAAM8CADDQAUAArwIAIfwBAQCuAgAhhAIBAK4CACGFAgEA0AIAIYYCQADRAgAhhwIBANACACEL0QEBAAAAAdIBAQAAAAXTAQEAAAAF1AEBAAAAAdUBAQAAAAHWAQEAAAAB1wEBAAAAAdgBAQDNAgAh2QEBAAAAAdoBAQAAAAHbAQEAAAABCNEBQAAAAAHSAUAAAAAF0wFAAAAABdQBQAAAAAHVAUAAAAAB1gFAAAAAAdcBQAAAAAHYAUAAywIAIQnIAQAA0gIAMMkBAABQABDKAQAA0gIAMNABQACnAgAh3wEBAKYCACH8AQEApgIAIYUCAQDHAgAhhgJAAMgCACGHAgEAxwIAIQnIAQAA0wIAMMkBAAA9ABDKAQAA0wIAMNABQACvAgAh3wEBAK4CACH8AQEArgIAIYUCAQDQAgAhhgJAANECACGHAgEA0AIAIRLIAQAA1AIAMMkBAAA3ABDKAQAA1AIAMNABQACnAgAh3gEBAMcCACH1AUAApwIAIfwBAQCmAgAh_wEBAKYCACGAAgEApgIAIYICQADIAgAhgwIBAMcCACGIAgEAxwIAIYkCAQDHAgAhigIBAKYCACGLAiAA1QIAIYwCIADVAgAhjQJAAMgCACGOAgEAxwIAIQUIAACpAgAgIgAA1wIAICMAANcCACDRASAAAAAB2AEgANYCACEFCAAAqQIAICIAANcCACAjAADXAgAg0QEgAAAAAdgBIADWAgAhAtEBIAAAAAHYASAA1wIAIQLfAQEAAAAB4QEBAAAAAQgFAADaAgAgyAEAANkCADDJAQAAGQAQygEAANkCADDfAQEArgIAIeEBAQCuAgAh9AFAAK8CACH1AUAArwIAIRsBAADnAgAgBAAA6AIAIAkAAOkCACAMAADqAgAgDQAA6gIAIA4AAOsCACAPAADsAgAgyAEAAOUCADDJAQAAAwAQygEAAOUCADDQAUAArwIAId4BAQDQAgAh9QFAAK8CACH8AQEArgIAIf8BAQCuAgAhgAIBAK4CACGCAkAA0QIAIYMCAQDQAgAhiAIBANACACGJAgEA0AIAIYoCAQCuAgAhiwIgAOYCACGMAiAA5gIAIY0CQADRAgAhjgIBANACACGVAgAAAwAglgIAAAMAIALfAQEAAAAB4QEBAAAAAQcFAADaAgAgyAEAANwCADDJAQAAFQAQygEAANwCADDQAUAArwIAId8BAQCuAgAh4QEBAK4CACEC9gEBAAAAAfcBAQAAAAEICgAA2gIAIAsAANoCACDIAQAA3gIAMMkBAAAQABDKAQAA3gIAMPYBAQCuAgAh9wEBAK4CACH4AQIAvQIAIQLfAQEAAAAB-QEBAAAAAQkGAADiAgAgyAEAAOACADDJAQAACwAQygEAAOACADDQAUAArwIAId8BAQCuAgAh9QFAAK8CACH5AQEArgIAIfsBAADhAvsBIgTRAQAAAPsBAtIBAAAA-wEI0wEAAAD7AQjYAQAAxQL7ASISBQAA2gIAIAcAAOQCACDIAQAA4wIAMMkBAAAHABDKAQAA4wIAMNABQACvAgAh4QEBAK4CACH1AUAArwIAIfwBAQCuAgAh_QEBAK4CACH-AQEArgIAIf8BAQCuAgAhgAIBAK4CACGBAgEA0AIAIYICQADRAgAhgwIBANACACGVAgAABwAglgIAAAcAIBAFAADaAgAgBwAA5AIAIMgBAADjAgAwyQEAAAcAEMoBAADjAgAw0AFAAK8CACHhAQEArgIAIfUBQACvAgAh_AEBAK4CACH9AQEArgIAIf4BAQCuAgAh_wEBAK4CACGAAgEArgIAIYECAQDQAgAhggJAANECACGDAgEA0AIAIQOSAgAACwAgkwIAAAsAIJQCAAALACAZAQAA5wIAIAQAAOgCACAJAADpAgAgDAAA6gIAIA0AAOoCACAOAADrAgAgDwAA7AIAIMgBAADlAgAwyQEAAAMAEMoBAADlAgAw0AFAAK8CACHeAQEA0AIAIfUBQACvAgAh_AEBAK4CACH_AQEArgIAIYACAQCuAgAhggJAANECACGDAgEA0AIAIYgCAQDQAgAhiQIBANACACGKAgEArgIAIYsCIADmAgAhjAIgAOYCACGNAkAA0QIAIY4CAQDQAgAhAtEBIAAAAAHYASAA1wIAIRsBAADnAgAgBAAA6AIAIAkAAOkCACAMAADqAgAgDQAA6gIAIA4AAOsCACAPAADsAgAgyAEAAOUCADDJAQAAAwAQygEAAOUCADDQAUAArwIAId4BAQDQAgAh9QFAAK8CACH8AQEArgIAIf8BAQCuAgAhgAIBAK4CACGCAkAA0QIAIYMCAQDQAgAhiAIBANACACGJAgEA0AIAIYoCAQCuAgAhiwIgAOYCACGMAiAA5gIAIY0CQADRAgAhjgIBANACACGVAgAAAwAglgIAAAMAIAOSAgAAAwAgkwIAAAMAIJQCAAADACADkgIAAAcAIJMCAAAHACCUAgAABwAgA5ICAAAQACCTAgAAEAAglAIAABAAIAOSAgAAFQAgkwIAABUAIJQCAAAVACADkgIAABkAIJMCAAAZACCUAgAAGQAgAAAAAZoCAQAAAAEBmgJAAAAAAQAAAAAAAAAABZoCAgAAAAGhAgIAAAABogICAAAAAaMCAgAAAAGkAgIAAAABApoCAQAAAASgAgEAAAAFApoCAQAAAASgAgEAAAAFApoCAQAAAASgAgEAAAAFAZoCAQAAAAQBmgIBAAAABAGaAgEAAAAEAAAABRwAALoEACAdAAC9BAAglwIAALsEACCYAgAAvAQAIJ0CAAABACADHAAAugQAIJcCAAC7BAAgnQIAAAEAIAAAAAUcAAC1BAAgHQAAuAQAIJcCAAC2BAAgmAIAALcEACCdAgAAAQAgAxwAALUEACCXAgAAtgQAIJ0CAAABACAAAAAAAAUcAACtBAAgHQAAswQAIJcCAACuBAAgmAIAALIEACCdAgAAAQAgBRwAAKsEACAdAACwBAAglwIAAKwEACCYAgAArwQAIJ0CAAABACADHAAArQQAIJcCAACuBAAgnQIAAAEAIAMcAACrBAAglwIAAKwEACCdAgAAAQAgAAAAAZoCAAAA-wECBRwAAKYEACAdAACpBAAglwIAAKcEACCYAgAAqAQAIJ0CAAAJACADHAAApgQAIJcCAACnBAAgnQIAAAkAIAAAAAABmgIBAAAAAQGaAkAAAAABBRwAAKAEACAdAACkBAAglwIAAKEEACCYAgAAowQAIJ0CAAABACALHAAAogMAMB0AAKcDADCXAgAAowMAMJgCAACkAwAwmQIAAKUDACCaAgAApgMAMJsCAACmAwAwnAIAAKYDADCdAgAApgMAMJ4CAACoAwAwnwIAAKkDADAE0AFAAAAAAd8BAQAAAAH1AUAAAAAB-wEAAAD7AQICAAAADQAgHAAArQMAIAMAAAANACAcAACtAwAgHQAArAMAIAEVAACiBAAwCgYAAOICACDIAQAA4AIAMMkBAAALABDKAQAA4AIAMNABQACvAgAh3wEBAK4CACH1AUAArwIAIfkBAQCuAgAh-wEAAOEC-wEikQIAAN8CACACAAAADQAgFQAArAMAIAIAAACqAwAgFQAAqwMAIAjIAQAAqQMAMMkBAACqAwAQygEAAKkDADDQAUAArwIAId8BAQCuAgAh9QFAAK8CACH5AQEArgIAIfsBAADhAvsBIgjIAQAAqQMAMMkBAACqAwAQygEAAKkDADDQAUAArwIAId8BAQCuAgAh9QFAAK8CACH5AQEArgIAIfsBAADhAvsBIgTQAUAA8QIAId8BAQDwAgAh9QFAAPECACH7AQAAlwP7ASIE0AFAAPECACHfAQEA8AIAIfUBQADxAgAh-wEAAJcD-wEiBNABQAAAAAHfAQEAAAAB9QFAAAAAAfsBAAAA-wECAxwAAKAEACCXAgAAoQQAIJ0CAAABACAEHAAAogMAMJcCAACjAwAwmQIAAKUDACCdAgAApgMAMAAAAAAAAAAAAAGaAiAAAAABBxwAAJUEACAdAACeBAAglwIAAJYEACCYAgAAnQQAIJsCAAADACCcAgAAAwAgnQIAAAEAIAscAAD6AwAwHQAA_wMAMJcCAAD7AwAwmAIAAPwDADCZAgAA_QMAIJoCAAD-AwAwmwIAAP4DADCcAgAA_gMAMJ0CAAD-AwAwngIAAIAEADCfAgAAgQQAMAscAADuAwAwHQAA8wMAMJcCAADvAwAwmAIAAPADADCZAgAA8QMAIJoCAADyAwAwmwIAAPIDADCcAgAA8gMAMJ0CAADyAwAwngIAAPQDADCfAgAA9QMAMAscAADlAwAwHQAA6QMAMJcCAADmAwAwmAIAAOcDADCZAgAA6AMAIJoCAADdAwAwmwIAAN0DADCcAgAA3QMAMJ0CAADdAwAwngIAAOoDADCfAgAA4AMAMAscAADZAwAwHQAA3gMAMJcCAADaAwAwmAIAANsDADCZAgAA3AMAIJoCAADdAwAwmwIAAN0DADCcAgAA3QMAMJ0CAADdAwAwngIAAN8DADCfAgAA4AMAMAscAADNAwAwHQAA0gMAMJcCAADOAwAwmAIAAM8DADCZAgAA0AMAIJoCAADRAwAwmwIAANEDADCcAgAA0QMAMJ0CAADRAwAwngIAANMDADCfAgAA1AMAMAscAADBAwAwHQAAxgMAMJcCAADCAwAwmAIAAMMDADCZAgAAxAMAIJoCAADFAwAwmwIAAMUDADCcAgAAxQMAMJ0CAADFAwAwngIAAMcDADCfAgAAyAMAMAPfAQEAAAAB9AFAAAAAAfUBQAAAAAECAAAAGwAgHAAAzAMAIAMAAAAbACAcAADMAwAgHQAAywMAIAEVAACcBAAwCQUAANoCACDIAQAA2QIAMMkBAAAZABDKAQAA2QIAMN8BAQCuAgAh4QEBAK4CACH0AUAArwIAIfUBQACvAgAhjwIAANgCACACAAAAGwAgFQAAywMAIAIAAADJAwAgFQAAygMAIAfIAQAAyAMAMMkBAADJAwAQygEAAMgDADDfAQEArgIAIeEBAQCuAgAh9AFAAK8CACH1AUAArwIAIQfIAQAAyAMAMMkBAADJAwAQygEAAMgDADDfAQEArgIAIeEBAQCuAgAh9AFAAK8CACH1AUAArwIAIQPfAQEA8AIAIfQBQADxAgAh9QFAAPECACED3wEBAPACACH0AUAA8QIAIfUBQADxAgAhA98BAQAAAAH0AUAAAAAB9QFAAAAAAQLQAUAAAAAB3wEBAAAAAQIAAAAXACAcAADYAwAgAwAAABcAIBwAANgDACAdAADXAwAgARUAAJsEADAIBQAA2gIAIMgBAADcAgAwyQEAABUAEMoBAADcAgAw0AFAAK8CACHfAQEArgIAIeEBAQCuAgAhjwIAANsCACACAAAAFwAgFQAA1wMAIAIAAADVAwAgFQAA1gMAIAbIAQAA1AMAMMkBAADVAwAQygEAANQDADDQAUAArwIAId8BAQCuAgAh4QEBAK4CACEGyAEAANQDADDJAQAA1QMAEMoBAADUAwAw0AFAAK8CACHfAQEArgIAIeEBAQCuAgAhAtABQADxAgAh3wEBAPACACEC0AFAAPECACHfAQEA8AIAIQLQAUAAAAAB3wEBAAAAAQMKAACSAwAg9gEBAAAAAfgBAgAAAAECAAAAEgAgHAAA5AMAIAMAAAASACAcAADkAwAgHQAA4wMAIAEVAACaBAAwCQoAANoCACALAADaAgAgyAEAAN4CADDJAQAAEAAQygEAAN4CADD2AQEArgIAIfcBAQCuAgAh-AECAL0CACGQAgAA3QIAIAIAAAASACAVAADjAwAgAgAAAOEDACAVAADiAwAgBsgBAADgAwAwyQEAAOEDABDKAQAA4AMAMPYBAQCuAgAh9wEBAK4CACH4AQIAvQIAIQbIAQAA4AMAMMkBAADhAwAQygEAAOADADD2AQEArgIAIfcBAQCuAgAh-AECAL0CACEC9gEBAPACACH4AQIA-gIAIQMKAACQAwAg9gEBAPACACH4AQIA-gIAIQMKAACSAwAg9gEBAAAAAfgBAgAAAAEDCwAAkwMAIPcBAQAAAAH4AQIAAAABAgAAABIAIBwAAO0DACADAAAAEgAgHAAA7QMAIB0AAOwDACABFQAAmQQAMAIAAAASACAVAADsAwAgAgAAAOEDACAVAADrAwAgAvcBAQDwAgAh-AECAPoCACEDCwAAkQMAIPcBAQDwAgAh-AECAPoCACEDCwAAkwMAIPcBAQAAAAH4AQIAAAABCwcAAK8DACDQAUAAAAAB9QFAAAAAAfwBAQAAAAH9AQEAAAAB_gEBAAAAAf8BAQAAAAGAAgEAAAABgQIBAAAAAYICQAAAAAGDAgEAAAABAgAAAAkAIBwAAPkDACADAAAACQAgHAAA-QMAIB0AAPgDACABFQAAmAQAMBAFAADaAgAgBwAA5AIAIMgBAADjAgAwyQEAAAcAEMoBAADjAgAw0AFAAK8CACHhAQEArgIAIfUBQACvAgAh_AEBAAAAAf0BAQCuAgAh_gEBAK4CACH_AQEArgIAIYACAQCuAgAhgQIBANACACGCAkAA0QIAIYMCAQDQAgAhAgAAAAkAIBUAAPgDACACAAAA9gMAIBUAAPcDACAOyAEAAPUDADDJAQAA9gMAEMoBAAD1AwAw0AFAAK8CACHhAQEArgIAIfUBQACvAgAh_AEBAK4CACH9AQEArgIAIf4BAQCuAgAh_wEBAK4CACGAAgEArgIAIYECAQDQAgAhggJAANECACGDAgEA0AIAIQ7IAQAA9QMAMMkBAAD2AwAQygEAAPUDADDQAUAArwIAIeEBAQCuAgAh9QFAAK8CACH8AQEArgIAIf0BAQCuAgAh_gEBAK4CACH_AQEArgIAIYACAQCuAgAhgQIBANACACGCAkAA0QIAIYMCAQDQAgAhCtABQADxAgAh9QFAAPECACH8AQEA8AIAIf0BAQDwAgAh_gEBAPACACH_AQEA8AIAIYACAQDwAgAhgQIBAJ4DACGCAkAAnwMAIYMCAQCeAwAhCwcAAKEDACDQAUAA8QIAIfUBQADxAgAh_AEBAPACACH9AQEA8AIAIf4BAQDwAgAh_wEBAPACACGAAgEA8AIAIYECAQCeAwAhggJAAJ8DACGDAgEAngMAIQsHAACvAwAg0AFAAAAAAfUBQAAAAAH8AQEAAAAB_QEBAAAAAf4BAQAAAAH_AQEAAAABgAIBAAAAAYECAQAAAAGCAkAAAAABgwIBAAAAARQEAACGBAAgCQAAhwQAIAwAAIgEACANAACJBAAgDgAAigQAIA8AAIsEACDQAUAAAAAB3gEBAAAAAfUBQAAAAAH8AQEAAAAB_wEBAAAAAYACAQAAAAGCAkAAAAABgwIBAAAAAYkCAQAAAAGKAgEAAAABiwIgAAAAAYwCIAAAAAGNAkAAAAABjgIBAAAAAQIAAAABACAcAACFBAAgAwAAAAEAIBwAAIUEACAdAACEBAAgARUAAJcEADAZAQAA5wIAIAQAAOgCACAJAADpAgAgDAAA6gIAIA0AAOoCACAOAADrAgAgDwAA7AIAIMgBAADlAgAwyQEAAAMAEMoBAADlAgAw0AFAAK8CACHeAQEA0AIAIfUBQACvAgAh_AEBAAAAAf8BAQCuAgAhgAIBAK4CACGCAkAA0QIAIYMCAQDQAgAhiAIBANACACGJAgEA0AIAIYoCAQCuAgAhiwIgAOYCACGMAiAA5gIAIY0CQADRAgAhjgIBANACACECAAAAAQAgFQAAhAQAIAIAAACCBAAgFQAAgwQAIBLIAQAAgQQAMMkBAACCBAAQygEAAIEEADDQAUAArwIAId4BAQDQAgAh9QFAAK8CACH8AQEArgIAIf8BAQCuAgAhgAIBAK4CACGCAkAA0QIAIYMCAQDQAgAhiAIBANACACGJAgEA0AIAIYoCAQCuAgAhiwIgAOYCACGMAiAA5gIAIY0CQADRAgAhjgIBANACACESyAEAAIEEADDJAQAAggQAEMoBAACBBAAw0AFAAK8CACHeAQEA0AIAIfUBQACvAgAh_AEBAK4CACH_AQEArgIAIYACAQCuAgAhggJAANECACGDAgEA0AIAIYgCAQDQAgAhiQIBANACACGKAgEArgIAIYsCIADmAgAhjAIgAOYCACGNAkAA0QIAIY4CAQDQAgAhDtABQADxAgAh3gEBAJ4DACH1AUAA8QIAIfwBAQDwAgAh_wEBAPACACGAAgEA8AIAIYICQACfAwAhgwIBAJ4DACGJAgEAngMAIYoCAQDwAgAhiwIgALkDACGMAiAAuQMAIY0CQACfAwAhjgIBAJ4DACEUBAAAuwMAIAkAALwDACAMAAC9AwAgDQAAvgMAIA4AAL8DACAPAADAAwAg0AFAAPECACHeAQEAngMAIfUBQADxAgAh_AEBAPACACH_AQEA8AIAIYACAQDwAgAhggJAAJ8DACGDAgEAngMAIYkCAQCeAwAhigIBAPACACGLAiAAuQMAIYwCIAC5AwAhjQJAAJ8DACGOAgEAngMAIRQEAACGBAAgCQAAhwQAIAwAAIgEACANAACJBAAgDgAAigQAIA8AAIsEACDQAUAAAAAB3gEBAAAAAfUBQAAAAAH8AQEAAAAB_wEBAAAAAYACAQAAAAGCAkAAAAABgwIBAAAAAYkCAQAAAAGKAgEAAAABiwIgAAAAAYwCIAAAAAGNAkAAAAABjgIBAAAAAQQcAAD6AwAwlwIAAPsDADCZAgAA_QMAIJ0CAAD-AwAwBBwAAO4DADCXAgAA7wMAMJkCAADxAwAgnQIAAPIDADAEHAAA5QMAMJcCAADmAwAwmQIAAOgDACCdAgAA3QMAMAQcAADZAwAwlwIAANoDADCZAgAA3AMAIJ0CAADdAwAwBBwAAM0DADCXAgAAzgMAMJkCAADQAwAgnQIAANEDADAEHAAAwQMAMJcCAADCAwAwmQIAAMQDACCdAgAAxQMAMAMcAACVBAAglwIAAJYEACCdAgAAAQAgDgEAAI0EACAEAACQBAAgCQAAkQQAIAwAAJIEACANAACSBAAgDgAAkwQAIA8AAJQEACDeAQAAmgMAIIICAACaAwAggwIAAJoDACCIAgAAmgMAIIkCAACaAwAgjQIAAJoDACCOAgAAmgMAIAUFAACNBAAgBwAAjwQAIIECAACaAwAgggIAAJoDACCDAgAAmgMAIAAAAAAAABUBAACMBAAgCQAAhwQAIAwAAIgEACANAACJBAAgDgAAigQAIA8AAIsEACDQAUAAAAAB3gEBAAAAAfUBQAAAAAH8AQEAAAAB_wEBAAAAAYACAQAAAAGCAkAAAAABgwIBAAAAAYgCAQAAAAGJAgEAAAABigIBAAAAAYsCIAAAAAGMAiAAAAABjQJAAAAAAY4CAQAAAAECAAAAAQAgHAAAlQQAIA7QAUAAAAAB3gEBAAAAAfUBQAAAAAH8AQEAAAAB_wEBAAAAAYACAQAAAAGCAkAAAAABgwIBAAAAAYkCAQAAAAGKAgEAAAABiwIgAAAAAYwCIAAAAAGNAkAAAAABjgIBAAAAAQrQAUAAAAAB9QFAAAAAAfwBAQAAAAH9AQEAAAAB_gEBAAAAAf8BAQAAAAGAAgEAAAABgQIBAAAAAYICQAAAAAGDAgEAAAABAvcBAQAAAAH4AQIAAAABAvYBAQAAAAH4AQIAAAABAtABQAAAAAHfAQEAAAABA98BAQAAAAH0AUAAAAAB9QFAAAAAAQMAAAADACAcAACVBAAgHQAAnwQAIBcAAAADACABAAC6AwAgCQAAvAMAIAwAAL0DACANAAC-AwAgDgAAvwMAIA8AAMADACAVAACfBAAg0AFAAPECACHeAQEAngMAIfUBQADxAgAh_AEBAPACACH_AQEA8AIAIYACAQDwAgAhggJAAJ8DACGDAgEAngMAIYgCAQCeAwAhiQIBAJ4DACGKAgEA8AIAIYsCIAC5AwAhjAIgALkDACGNAkAAnwMAIY4CAQCeAwAhFQEAALoDACAJAAC8AwAgDAAAvQMAIA0AAL4DACAOAAC_AwAgDwAAwAMAINABQADxAgAh3gEBAJ4DACH1AUAA8QIAIfwBAQDwAgAh_wEBAPACACGAAgEA8AIAIYICQACfAwAhgwIBAJ4DACGIAgEAngMAIYkCAQCeAwAhigIBAPACACGLAiAAuQMAIYwCIAC5AwAhjQJAAJ8DACGOAgEAngMAIRUBAACMBAAgBAAAhgQAIAwAAIgEACANAACJBAAgDgAAigQAIA8AAIsEACDQAUAAAAAB3gEBAAAAAfUBQAAAAAH8AQEAAAAB_wEBAAAAAYACAQAAAAGCAkAAAAABgwIBAAAAAYgCAQAAAAGJAgEAAAABigIBAAAAAYsCIAAAAAGMAiAAAAABjQJAAAAAAY4CAQAAAAECAAAAAQAgHAAAoAQAIATQAUAAAAAB3wEBAAAAAfUBQAAAAAH7AQAAAPsBAgMAAAADACAcAACgBAAgHQAApQQAIBcAAAADACABAAC6AwAgBAAAuwMAIAwAAL0DACANAAC-AwAgDgAAvwMAIA8AAMADACAVAAClBAAg0AFAAPECACHeAQEAngMAIfUBQADxAgAh_AEBAPACACH_AQEA8AIAIYACAQDwAgAhggJAAJ8DACGDAgEAngMAIYgCAQCeAwAhiQIBAJ4DACGKAgEA8AIAIYsCIAC5AwAhjAIgALkDACGNAkAAnwMAIY4CAQCeAwAhFQEAALoDACAEAAC7AwAgDAAAvQMAIA0AAL4DACAOAAC_AwAgDwAAwAMAINABQADxAgAh3gEBAJ4DACH1AUAA8QIAIfwBAQDwAgAh_wEBAPACACGAAgEA8AIAIYICQACfAwAhgwIBAJ4DACGIAgEAngMAIYkCAQCeAwAhigIBAPACACGLAiAAuQMAIYwCIAC5AwAhjQJAAJ8DACGOAgEAngMAIQwFAACuAwAg0AFAAAAAAeEBAQAAAAH1AUAAAAAB_AEBAAAAAf0BAQAAAAH-AQEAAAAB_wEBAAAAAYACAQAAAAGBAgEAAAABggJAAAAAAYMCAQAAAAECAAAACQAgHAAApgQAIAMAAAAHACAcAACmBAAgHQAAqgQAIA4AAAAHACAFAACgAwAgFQAAqgQAINABQADxAgAh4QEBAPACACH1AUAA8QIAIfwBAQDwAgAh_QEBAPACACH-AQEA8AIAIf8BAQDwAgAhgAIBAPACACGBAgEAngMAIYICQACfAwAhgwIBAJ4DACEMBQAAoAMAINABQADxAgAh4QEBAPACACH1AUAA8QIAIfwBAQDwAgAh_QEBAPACACH-AQEA8AIAIf8BAQDwAgAhgAIBAPACACGBAgEAngMAIYICQACfAwAhgwIBAJ4DACEVAQAAjAQAIAQAAIYEACAJAACHBAAgDAAAiAQAIA4AAIoEACAPAACLBAAg0AFAAAAAAd4BAQAAAAH1AUAAAAAB_AEBAAAAAf8BAQAAAAGAAgEAAAABggJAAAAAAYMCAQAAAAGIAgEAAAABiQIBAAAAAYoCAQAAAAGLAiAAAAABjAIgAAAAAY0CQAAAAAGOAgEAAAABAgAAAAEAIBwAAKsEACAVAQAAjAQAIAQAAIYEACAJAACHBAAgDQAAiQQAIA4AAIoEACAPAACLBAAg0AFAAAAAAd4BAQAAAAH1AUAAAAAB_AEBAAAAAf8BAQAAAAGAAgEAAAABggJAAAAAAYMCAQAAAAGIAgEAAAABiQIBAAAAAYoCAQAAAAGLAiAAAAABjAIgAAAAAY0CQAAAAAGOAgEAAAABAgAAAAEAIBwAAK0EACADAAAAAwAgHAAAqwQAIB0AALEEACAXAAAAAwAgAQAAugMAIAQAALsDACAJAAC8AwAgDAAAvQMAIA4AAL8DACAPAADAAwAgFQAAsQQAINABQADxAgAh3gEBAJ4DACH1AUAA8QIAIfwBAQDwAgAh_wEBAPACACGAAgEA8AIAIYICQACfAwAhgwIBAJ4DACGIAgEAngMAIYkCAQCeAwAhigIBAPACACGLAiAAuQMAIYwCIAC5AwAhjQJAAJ8DACGOAgEAngMAIRUBAAC6AwAgBAAAuwMAIAkAALwDACAMAAC9AwAgDgAAvwMAIA8AAMADACDQAUAA8QIAId4BAQCeAwAh9QFAAPECACH8AQEA8AIAIf8BAQDwAgAhgAIBAPACACGCAkAAnwMAIYMCAQCeAwAhiAIBAJ4DACGJAgEAngMAIYoCAQDwAgAhiwIgALkDACGMAiAAuQMAIY0CQACfAwAhjgIBAJ4DACEDAAAAAwAgHAAArQQAIB0AALQEACAXAAAAAwAgAQAAugMAIAQAALsDACAJAAC8AwAgDQAAvgMAIA4AAL8DACAPAADAAwAgFQAAtAQAINABQADxAgAh3gEBAJ4DACH1AUAA8QIAIfwBAQDwAgAh_wEBAPACACGAAgEA8AIAIYICQACfAwAhgwIBAJ4DACGIAgEAngMAIYkCAQCeAwAhigIBAPACACGLAiAAuQMAIYwCIAC5AwAhjQJAAJ8DACGOAgEAngMAIRUBAAC6AwAgBAAAuwMAIAkAALwDACANAAC-AwAgDgAAvwMAIA8AAMADACDQAUAA8QIAId4BAQCeAwAh9QFAAPECACH8AQEA8AIAIf8BAQDwAgAhgAIBAPACACGCAkAAnwMAIYMCAQCeAwAhiAIBAJ4DACGJAgEAngMAIYoCAQDwAgAhiwIgALkDACGMAiAAuQMAIY0CQACfAwAhjgIBAJ4DACEVAQAAjAQAIAQAAIYEACAJAACHBAAgDAAAiAQAIA0AAIkEACAPAACLBAAg0AFAAAAAAd4BAQAAAAH1AUAAAAAB_AEBAAAAAf8BAQAAAAGAAgEAAAABggJAAAAAAYMCAQAAAAGIAgEAAAABiQIBAAAAAYoCAQAAAAGLAiAAAAABjAIgAAAAAY0CQAAAAAGOAgEAAAABAgAAAAEAIBwAALUEACADAAAAAwAgHAAAtQQAIB0AALkEACAXAAAAAwAgAQAAugMAIAQAALsDACAJAAC8AwAgDAAAvQMAIA0AAL4DACAPAADAAwAgFQAAuQQAINABQADxAgAh3gEBAJ4DACH1AUAA8QIAIfwBAQDwAgAh_wEBAPACACGAAgEA8AIAIYICQACfAwAhgwIBAJ4DACGIAgEAngMAIYkCAQCeAwAhigIBAPACACGLAiAAuQMAIYwCIAC5AwAhjQJAAJ8DACGOAgEAngMAIRUBAAC6AwAgBAAAuwMAIAkAALwDACAMAAC9AwAgDQAAvgMAIA8AAMADACDQAUAA8QIAId4BAQCeAwAh9QFAAPECACH8AQEA8AIAIf8BAQDwAgAhgAIBAPACACGCAkAAnwMAIYMCAQCeAwAhiAIBAJ4DACGJAgEAngMAIYoCAQDwAgAhiwIgALkDACGMAiAAuQMAIY0CQACfAwAhjgIBAJ4DACEVAQAAjAQAIAQAAIYEACAJAACHBAAgDAAAiAQAIA0AAIkEACAOAACKBAAg0AFAAAAAAd4BAQAAAAH1AUAAAAAB_AEBAAAAAf8BAQAAAAGAAgEAAAABggJAAAAAAYMCAQAAAAGIAgEAAAABiQIBAAAAAYoCAQAAAAGLAiAAAAABjAIgAAAAAY0CQAAAAAGOAgEAAAABAgAAAAEAIBwAALoEACADAAAAAwAgHAAAugQAIB0AAL4EACAXAAAAAwAgAQAAugMAIAQAALsDACAJAAC8AwAgDAAAvQMAIA0AAL4DACAOAAC_AwAgFQAAvgQAINABQADxAgAh3gEBAJ4DACH1AUAA8QIAIfwBAQDwAgAh_wEBAPACACGAAgEA8AIAIYICQACfAwAhgwIBAJ4DACGIAgEAngMAIYkCAQCeAwAhigIBAPACACGLAiAAuQMAIYwCIAC5AwAhjQJAAJ8DACGOAgEAngMAIRUBAAC6AwAgBAAAuwMAIAkAALwDACAMAAC9AwAgDQAAvgMAIA4AAL8DACDQAUAA8QIAId4BAQCeAwAh9QFAAPECACH8AQEA8AIAIf8BAQDwAgAhgAIBAPACACGCAkAAnwMAIYMCAQCeAwAhiAIBAJ4DACGJAgEAngMAIYoCAQDwAgAhiwIgALkDACGMAiAAuQMAIY0CQACfAwAhjgIBAJ4DACEIAQQBBAYBCAAICQoCDBMFDRQFDhgGDxwHAwUAAQcOAwgABAEGAAIBBw8AAgoAAQsAAQEFAAEBBQABBgQdAAkeAAwfAA0gAA4hAA8iAAABASwBAQEyAQMIAA0iAA4jAA8AAAADCAANIgAOIwAPAAAAAwgAFSIAFiMAFwAAAAMIABUiABYjABcAAAADCAAdIgAeIwAfAAAAAwgAHSIAHiMAHwEFAAEBBQABAwgAJCIAJSMAJgAAAAMIACQiACUjACYBBgACAQYAAgMIACsiACwjAC0AAAADCAArIgAsIwAtAgoAAQsAAQIKAAELAAEFCAAyIgA1IwA2dAAzdQA0AAAAAAAFCAAyIgA1IwA2dAAzdQA0AQUAAQEFAAEDCAA7IgA8IwA9AAAAAwgAOyIAPCMAPQEFAAEBBQABAwgAQiIAQyMARAAAAAMIAEIiAEMjAEQAAAAFCABKIgBNIwBOdABLdQBMAAAAAAAFCABKIgBNIwBOdABLdQBMAAAAAwgAVCIAVSMAVgAAAAMIAFQiAFUjAFYAAAADCABcIgBdIwBeAAAAAwgAXCIAXSMAXhACAREjARIkARMlARQmARYoARcqCRgrChkuARowCRsxCx4zAR80ASA1CSQ4DCU5ECY7ESc8ESg_ESlAESpBEStDESxFCS1GEi5IES9KCTBLEzFMETJNETNOCTRRFDVSGDZUGTdVGThYGTlZGTpaGTtcGTxeCT1fGj5hGT9jCUBkG0FlGUJmGUNnCURqHEVrIEZsAkdtAkhuAklvAkpwAktyAkx0CU11IU53Ak95CVB6IlF7AlJ8AlN9CVSAASNVgQEnVoIBA1eDAQNYhAEDWYUBA1qGAQNbiAEDXIoBCV2LAShejQEDX48BCWCQASlhkQEDYpIBA2OTAQlklgEqZZcBLmaYAQVnmQEFaJoBBWmbAQVqnAEFa54BBWygAQltoQEvbqMBBW-lAQlwpgEwcacBBXKoAQVzqQEJdqwBMXetATd4rgEGea8BBnqwAQZ7sQEGfLIBBn20AQZ-tgEJf7cBOIABuQEGgQG7AQmCAbwBOYMBvQEGhAG-AQaFAb8BCYYBwgE6hwHDAT6IAcQBB4kBxQEHigHGAQeLAccBB4wByAEHjQHKAQeOAcwBCY8BzQE_kAHPAQeRAdEBCZIB0gFAkwHTAQeUAdQBB5UB1QEJlgHYAUGXAdkBRZgB2wFGmQHcAUaaAd8BRpsB4AFGnAHhAUadAeMBRp4B5QEJnwHmAUegAegBRqEB6gEJogHrAUijAewBRqQB7QFGpQHuAQmmAfEBSacB8gFPqAH0AVCpAfUBUKoB-AFQqwH5AVCsAfoBUK0B_AFQrgH-AQmvAf8BUbABgQJQsQGDAgmyAYQCUrMBhQJQtAGGAlC1AYcCCbYBigJTtwGLAle4AY0CWLkBjgJYugGRAli7AZICWLwBkwJYvQGVAli-AZcCCb8BmAJZwAGaAljBAZwCCcIBnQJawwGeAljEAZ8CWMUBoAIJxgGjAlvHAaQCXw" } config.compilerWasm = { getRuntime: async () => require('./query_compiler_fast_bg.js'), diff --git a/packages/forums-prisma-client/package.json b/packages/forums-prisma-client/package.json index 8779070..84195b1 100644 --- a/packages/forums-prisma-client/package.json +++ b/packages/forums-prisma-client/package.json @@ -1,5 +1,5 @@ { - "name": "prisma-client-ef34779367716a040c952441ff72fcb57b8c12c5a26fab35714dccb03ead1789", + "name": "prisma-client-07279bd72154b430dd00280c65b4b8b34dd7a3e1e8abdf51a8f22b2cf0650926", "main": "index.js", "types": "index.d.ts", "browser": "default.js", diff --git a/packages/forums-prisma-client/schema.prisma b/packages/forums-prisma-client/schema.prisma index b836603..67b2c09 100644 --- a/packages/forums-prisma-client/schema.prisma +++ b/packages/forums-prisma-client/schema.prisma @@ -156,3 +156,43 @@ model TopicReadState { @@index([lastReadAt]) @@schema("forums") } + +// Presentation and access rules for imported public category topics only. +model PublicForumCategory { + topicId String @id @db.VarChar(14) + description String @default("") @db.Text + displayAs String @db.VarChar(16) + sortOrder Int + readRoles String[] + createRoles String[] + replyRoles String[] + legacyId Int @unique + legacySlug String @db.Text + source Json + + @@schema("forums") +} + +// Vanilla-only role assignments never grant identity-wide roles. +model PublicForumRoleMember { + roleName String @db.VarChar(128) + memberId String @db.VarChar(64) + + @@id([roleName, memberId]) + @@index([memberId]) + @@schema("forums") +} + +// Immutable provenance makes imports insert-only and safely repeatable. +model PublicForumImportRecord { + entityType String @db.VarChar(32) + sourceId String @db.VarChar(64) + targetId String @db.VarChar(64) + sourceHash String @db.VarChar(64) + targetHash String @db.VarChar(64) + createdAt DateTime @default(now()) + + @@id([entityType, sourceId]) + @@unique([entityType, targetId]) + @@schema("forums") +} diff --git a/prisma/migrations/20261007000000_public_forums/migration.sql b/prisma/migrations/20261007000000_public_forums/migration.sql new file mode 100644 index 0000000..ca803c5 --- /dev/null +++ b/prisma/migrations/20261007000000_public_forums/migration.sql @@ -0,0 +1,20 @@ +-- Additive only: no existing Topic, Post, watch, read-state, or challenge rows change. +CREATE TABLE "forums"."PublicForumCategory" ( + "topicId" VARCHAR(14) PRIMARY KEY REFERENCES "forums"."Topic"("id"), + "description" TEXT NOT NULL DEFAULT '', "displayAs" VARCHAR(16) NOT NULL, + "sortOrder" INTEGER NOT NULL, "readRoles" TEXT[] NOT NULL, + "createRoles" TEXT[] NOT NULL, "replyRoles" TEXT[] NOT NULL, + "legacyId" INTEGER NOT NULL UNIQUE, "legacySlug" TEXT NOT NULL, "source" JSONB NOT NULL, + CONSTRAINT "PublicForumCategory_displayAs_check" CHECK ("displayAs" IN ('Categories','Discussions')) +); +CREATE TABLE "forums"."PublicForumRoleMember" ( + "roleName" VARCHAR(128) NOT NULL, "memberId" VARCHAR(64) NOT NULL, + PRIMARY KEY ("roleName", "memberId") +); +CREATE INDEX "PublicForumRoleMember_memberId_idx" ON "forums"."PublicForumRoleMember"("memberId"); +CREATE TABLE "forums"."PublicForumImportRecord" ( + "entityType" VARCHAR(32) NOT NULL, "sourceId" VARCHAR(64) NOT NULL, + "targetId" VARCHAR(64) NOT NULL, "sourceHash" VARCHAR(64) NOT NULL, + "targetHash" VARCHAR(64) NOT NULL, "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY ("entityType", "sourceId"), UNIQUE ("entityType", "targetId") +); diff --git a/prisma/schema.prisma b/prisma/schema.prisma index b836603..ed1c300 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -156,3 +156,41 @@ model TopicReadState { @@index([lastReadAt]) @@schema("forums") } + + +// Presentation and access rules for imported public category topics only. +model PublicForumCategory { + topicId String @id @db.VarChar(14) + description String @default("") @db.Text + displayAs String @db.VarChar(16) + sortOrder Int + readRoles String[] + createRoles String[] + replyRoles String[] + legacyId Int @unique + legacySlug String @db.Text + source Json + @@schema("forums") +} + +// Vanilla-only role assignments never grant identity-wide roles. +model PublicForumRoleMember { + roleName String @db.VarChar(128) + memberId String @db.VarChar(64) + @@id([roleName, memberId]) + @@index([memberId]) + @@schema("forums") +} + +// Immutable provenance makes imports insert-only and safely repeatable. +model PublicForumImportRecord { + entityType String @db.VarChar(32) + sourceId String @db.VarChar(64) + targetId String @db.VarChar(64) + sourceHash String @db.VarChar(64) + targetHash String @db.VarChar(64) + createdAt DateTime @default(now()) + @@id([entityType, sourceId]) + @@unique([entityType, targetId]) + @@schema("forums") +} diff --git a/src/auth/decorators/optional-read.decorator.ts b/src/auth/decorators/optional-read.decorator.ts new file mode 100644 index 0000000..f51b233 --- /dev/null +++ b/src/auth/decorators/optional-read.decorator.ts @@ -0,0 +1,8 @@ +import { SetMetadata } from '@nestjs/common'; + +export const OPTIONAL_READ_KEY = 'forums:optional-read'; + +/** Allows missing tokens on policy-filtered reads while retaining validated-token scope checks. + * @returns Nest route metadata consumed by TokenRolesGuard. @throws Never. + */ +export const OptionalRead = () => SetMetadata(OPTIONAL_READ_KEY, true); diff --git a/src/auth/guards/token-roles.guard.ts b/src/auth/guards/token-roles.guard.ts index 0a5f9df..e078aa2 100644 --- a/src/auth/guards/token-roles.guard.ts +++ b/src/auth/guards/token-roles.guard.ts @@ -10,6 +10,7 @@ import { AUTHENTICATED_USER_ROLE, ROLES_KEY, } from '../decorators/roles.decorator'; +import { OPTIONAL_READ_KEY } from '../decorators/optional-read.decorator'; import { SCOPES_KEY } from '../decorators/scopes.decorator'; import { AuthenticatedRequest } from '../request/authenticated-request.interface'; @@ -61,6 +62,10 @@ export class TokenRolesGuard implements CanActivate { const request = context.switchToHttp().getRequest(); const user = request.user; + if (!user && this.reflector.getAllAndOverride(OPTIONAL_READ_KEY, [context.getHandler(), context.getClass()])) { + return true; + } + if (!user) { throw new UnauthorizedException('Missing or invalid token.'); } diff --git a/src/forums/dto/forums-read.dto.ts b/src/forums/dto/forums-read.dto.ts index 109fba4..8e270c9 100644 --- a/src/forums/dto/forums-read.dto.ts +++ b/src/forums/dto/forums-read.dto.ts @@ -333,6 +333,8 @@ export class ForumsPostTreeNodeDto { * topic-detail surface. */ export class ForumsTopicDetailDto { + @ApiProperty({ description: "Actions authorized for this reader; commands recheck permissions." }) + permissions?: { createPost: boolean; createTopic: boolean; watch: boolean; update: boolean; delete: boolean }; @ApiProperty({ description: 'Topic summary header for the requested topic.', type: ForumsTopicSummaryDto, diff --git a/src/forums/dto/public-forums.dto.ts b/src/forums/dto/public-forums.dto.ts new file mode 100644 index 0000000..3491437 --- /dev/null +++ b/src/forums/dto/public-forums.dto.ts @@ -0,0 +1,39 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsIn, IsOptional, IsString, Max, MaxLength } from 'class-validator'; +import { + ForumsTopicListQueryDto, + ForumsTopicSummaryDto, +} from './forums-read.dto'; + +/** Validated public search/list inputs. Pagination follows visibility filtering. */ +export class PublicForumsQueryDto extends ForumsTopicListQueryDto { + @IsOptional() + @IsString() + @MaxLength(14) + @ApiPropertyOptional() + categoryId?: string; + @IsOptional() + @IsString() + @MaxLength(200) + @ApiPropertyOptional() + search?: string; + @IsOptional() + @IsIn(['true', 'false']) + @ApiPropertyOptional() + watching?: string; + @IsOptional() + @IsIn(['active', 'recent', 'oldest']) + @ApiPropertyOptional() + sort?: string; + @Max(100) + perPage?: number = 20; +} + +/** Visible category metadata and summary; content counts exclude inaccessible descendants. */ +export interface PublicForumCategoryDto extends ForumsTopicSummaryDto { + description: string; + displayAs: string; + sortOrder: number; + topicsCount: number; + canCreate: boolean; +} diff --git a/src/forums/forums-access-policy.service.spec.ts b/src/forums/forums-access-policy.service.spec.ts index 9b676c9..990e4e3 100644 --- a/src/forums/forums-access-policy.service.spec.ts +++ b/src/forums/forums-access-policy.service.spec.ts @@ -1,3 +1,4 @@ +import { PublicForumAccessService } from './public-forum-access.service'; import { Post, Topic } from '../../prisma/generated/client'; import { ChallengeAccessFacts, @@ -75,6 +76,7 @@ function createPolicyHarness( service: new ForumsAccessPolicyService( challengeAccessService as unknown as ChallengeAccessService, resourceAccessService as unknown as ResourceAccessService, + { decide: jest.fn().mockResolvedValue({ view: true, create: true, reply: true, category: false, managed: false }) } as unknown as PublicForumAccessService, ), }; } diff --git a/src/forums/forums-access-policy.service.ts b/src/forums/forums-access-policy.service.ts index 772f42c..ac02890 100644 --- a/src/forums/forums-access-policy.service.ts +++ b/src/forums/forums-access-policy.service.ts @@ -19,6 +19,8 @@ import { ResourceAccessService, } from './resource-access.service'; +import { PublicForumAccessService } from './public-forum-access.service'; + const CHALLENGE_CHILD_TOPIC_DENY_REASON = 'Child-topic creation is only allowed for non-challenge effective contexts.'; @@ -43,11 +45,13 @@ export class ForumsAccessPolicyService { * * @param challengeAccessService Adapter for challenge existence and member access facts. * @param resourceAccessService Adapter for resource-role and challenge-copilot facts. + * @param publicAccess Inherited public category ACLs, including Vanilla-only memberships. * @throws Does not throw directly; dependencies are resolved by Nest. */ constructor( private readonly challengeAccessService: ChallengeAccessService, private readonly resourceAccessService: ResourceAccessService, + private readonly publicAccess: PublicForumAccessService, ) {} /** @@ -77,8 +81,13 @@ export class ForumsAccessPolicyService { }, options, ); + const categoryAccess = await this.publicAccess.decide(this.resolveEvaluationPrincipal(principal, options), context.topic.id); + if (!categoryAccess.view) return this.denyTopicDecisions('Required public forum access is missing.'); + if (!evaluation.visibility.allowed) return this.denyTopicDecisions(evaluation.visibility.reason ?? 'Insufficient forums access.'); + const authenticated = !!principal.memberId || principal.isMachine; + const canParticipate = evaluation.visibility.allowed && authenticated; const canOwnContent = - evaluation.visibility.allowed && context.isTopicAuthor; + evaluation.visibility.allowed && context.isTopicAuthor && (!categoryAccess.category || evaluation.isElevated); const canMutateContent = evaluation.isElevated || canOwnContent ? this.allow() @@ -91,15 +100,15 @@ export class ForumsAccessPolicyService { return { canView: evaluation.visibility, canCreateTopLevelTopic: this.deny('Use create-topic evaluation.'), - canCreateChildTopic: evaluation.visibility, - canCreatePost: evaluation.visibility, + canCreateChildTopic: canParticipate && categoryAccess.create ? this.allow() : this.deny('Topic creation is not allowed.'), + canCreatePost: canParticipate && categoryAccess.reply ? this.allow() : this.deny('Replying is not allowed.'), canUpdateTopic: canMutateContent, canDeleteTopic, - canAddWatch: evaluation.visibility, - canRemoveWatch: evaluation.visibility, - canMarkRead: evaluation.visibility, + canAddWatch: canParticipate ? this.allow() : this.deny('Authenticated member required.'), + canRemoveWatch: canParticipate ? this.allow() : this.deny('Authenticated member required.'), + canMarkRead: canParticipate ? this.allow() : this.deny('Authenticated member required.'), canControlAnnouncement, - canReceiveNotification: evaluation.visibility, + canReceiveNotification: canParticipate ? this.allow() : this.deny('Authenticated member required.'), }; } @@ -123,7 +132,7 @@ export class ForumsAccessPolicyService { options, ); - if (!this.isTopicTargetAvailable(context)) { + if (!this.isTopicTargetAvailable(context) || !topicDecisions.canView.allowed) { return { ...topicDecisions, canUpdatePost: this.deny('Post not found.'), @@ -190,6 +199,11 @@ export class ForumsAccessPolicyService { return this.denyTopicDecisions('Topic not found.'); } + if (!principal.memberId && !principal.isMachine) return this.denyTopicDecisions('Authenticated member required.'); + if (parentContext && !(await this.publicAccess.decide(principal, parentContext.topic.id)).create) { + return this.denyTopicDecisions('Topic creation is not allowed in this category.'); + } + const parentVisibility = parentContext ? ( await this.evaluateRestrictions( @@ -323,6 +337,9 @@ export class ForumsAccessPolicyService { target: ForumsRestrictionVisibilityTarget, options: ForumsAccessEvaluationOptions = {}, ): Promise { + if (target.topicId && !(await this.publicAccess.decide(this.resolveEvaluationPrincipal(principal, options), target.topicId)).view) { + return this.deny('Required public forum access is missing.'); + } return (await this.evaluateRestrictions(principal, target, options)) .visibility; } @@ -346,6 +363,10 @@ export class ForumsAccessPolicyService { options, ); + if (!evaluationPrincipal.memberId && !evaluationPrincipal.isMachine && (input.challengeId || input.roleName)) { + return { visibility: this.deny('Authenticated member required.'), isElevated: false, isChallengeCopilot: false }; + } + if ( input.hasRestrictionConflict && !evaluationPrincipal.isMachine && diff --git a/src/forums/forums-access.types.ts b/src/forums/forums-access.types.ts index af2a2a5..d460a4f 100644 --- a/src/forums/forums-access.types.ts +++ b/src/forums/forums-access.types.ts @@ -49,6 +49,8 @@ export interface ForumsEffectiveRestrictions { */ export interface ForumsRestrictionVisibilityTarget extends ForumsEffectiveRestrictions { hasRestrictionConflict: boolean; + /** Existing topic whose public category ACL must also be evaluated. */ + topicId?: string; } /** diff --git a/src/forums/forums-read-query.service.ts b/src/forums/forums-read-query.service.ts index 4125b7f..a14a1a4 100644 --- a/src/forums/forums-read-query.service.ts +++ b/src/forums/forums-read-query.service.ts @@ -341,7 +341,7 @@ export class ForumsReadQueryService { * @returns Ordered candidate topic summary rows with lock, read, watch, excerpt, view, and participant metadata. * @throws Prisma errors when the raw query fails. */ - private findTopicSummaryRows( + findTopicSummaryRows( whereClause: Prisma.Sql, memberId: string | null, client: ForumsReadQueryClient = this.db, diff --git a/src/forums/forums-read.service.ts b/src/forums/forums-read.service.ts index 631892b..8cee791 100644 --- a/src/forums/forums-read.service.ts +++ b/src/forums/forums-read.service.ts @@ -150,10 +150,9 @@ export class ForumsReadService { * filtering. * * @param query Pagination query parameters. - * @param user Authenticated token payload for the read caller. + * @param user Optional validated token; guests can read unrestricted public content. * @param trustedClientIp Optional trusted client IP resolved at the HTTP boundary. * @returns Paginated visible general topic summaries. - * @throws UnauthorizedException when no authenticated read caller is present. * @throws ForbiddenException when the caller is globally banned. */ async listGeneralRootTopics( @@ -161,7 +160,7 @@ export class ForumsReadService { user: JwtUser | undefined, trustedClientIp?: string, ): Promise { - const principal = this.requirePrincipal(user); + const principal = this.readPrincipal(user); this.assertAllowed( await this.moderationService.decideForRequestActorBan( principal, @@ -187,10 +186,9 @@ export class ForumsReadService { * liveness and child summaries are re-read from one snapshot before return. * * @param topicId Parent topic id supplied in the route. - * @param user Authenticated token payload for the read caller. + * @param user Optional validated token; guests can read unrestricted public content. * @param trustedClientIp Optional trusted client IP resolved at the HTTP boundary. * @returns Ordered child topic summaries visible to the caller. - * @throws UnauthorizedException when no authenticated read caller is present. * @throws ForbiddenException when the caller is globally banned. * @throws ForbiddenException when parent topic visibility is denied. * @throws NotFoundException when the parent topic is missing or hidden. @@ -200,7 +198,7 @@ export class ForumsReadService { user: JwtUser | undefined, trustedClientIp?: string, ): Promise { - const principal = this.requirePrincipal(user); + const principal = this.readPrincipal(user); this.assertAllowed( await this.moderationService.decideForRequestActorBan( principal, @@ -245,10 +243,9 @@ export class ForumsReadService { * placeholders. * * @param topicId Topic id supplied in the route. - * @param user Authenticated token payload for the read caller. + * @param user Optional validated token; guests can read unrestricted public content. * @param trustedClientIp Optional trusted client IP resolved at the HTTP boundary. * @returns Topic summary and nested post tree. - * @throws UnauthorizedException when no authenticated read caller is present. * @throws ForbiddenException when the caller is globally banned. * @throws ForbiddenException when topic visibility is denied. * @throws NotFoundException when the topic is missing or hidden. @@ -258,7 +255,7 @@ export class ForumsReadService { user: JwtUser | undefined, trustedClientIp?: string, ): Promise { - const principal = this.requirePrincipal(user); + const principal = this.readPrincipal(user); this.assertAllowed( await this.moderationService.decideForRequestActorBan( principal, @@ -291,6 +288,7 @@ export class ForumsReadService { ); return { + permissions: { createPost: topicDecisions.canCreatePost.allowed, createTopic: topicDecisions.canCreateChildTopic.allowed, watch: topicDecisions.canAddWatch.allowed, update: topicDecisions.canUpdateTopic.allowed, delete: topicDecisions.canDeleteTopic.allowed }, topic: this.mapTopicSummary(detailSnapshot.summaryRow), posts: this.buildPostTree( topicId, @@ -301,11 +299,18 @@ export class ForumsReadService { } /** - * Builds the reusable forums principal required by policy checks. + * Builds a validated reader or a non-elevated anonymous principal. * * @param user Authenticated token payload from request middleware. * @returns Normalized forums principal. - * @throws UnauthorizedException when no authenticated token is present. + * @throws Does not throw. + */ + private readPrincipal(user: JwtUser | undefined): ForumsPrincipal { + return buildForumsPrincipal(user) ?? { memberId: null, roles: [], scopes: [], isAdmin: false, isMachine: false }; + } + + /** Requires a validated identity for challenge-specific listing. + * @param user Validated JWT. @returns Principal. @throws UnauthorizedException for guests. */ private requirePrincipal(user: JwtUser | undefined): ForumsPrincipal { const principal = buildForumsPrincipal(user); @@ -351,7 +356,7 @@ export class ForumsReadService { * @returns Candidate rows visible to the caller. * @throws Prisma errors when external policy fact lookups fail. */ - private async filterVisibleRows( + async filterVisibleRows( principal: ForumsPrincipal, rows: readonly ForumsTopicSummaryRow[], resolveRestrictions: ( @@ -401,6 +406,7 @@ export class ForumsReadService { row: ForumsTopicSummaryRow, ): ForumsRestrictionVisibilityTarget { return { + topicId: row.id, challengeId: normalizeForumsOptionalText(row.challengeId) ?? null, roleName: normalizeForumRoleName(row.roleName), hasRestrictionConflict: false, @@ -424,6 +430,7 @@ export class ForumsReadService { const directRoleName = normalizeForumRoleName(row.roleName); return { + topicId: row.id, challengeId: directChallengeId ?? parentContext.effectiveChallengeId, roleName: directRoleName ?? parentContext.effectiveRoleName, hasRestrictionConflict: @@ -467,6 +474,7 @@ export class ForumsReadService { target: ForumsRestrictionVisibilityTarget, ): string { return [ + target.topicId ?? '', target.challengeId ?? '', target.roleName ?? '', target.hasRestrictionConflict ? 'conflict' : 'consistent', @@ -481,7 +489,7 @@ export class ForumsReadService { * @returns Paginated response containing summary DTOs and metadata. * @throws Does not throw. */ - private paginateRows( + paginateRows( rows: readonly ForumsTopicSummaryRow[], query: ForumsTopicListQueryDto, ): ForumsTopicSummaryPageDto { @@ -514,7 +522,7 @@ export class ForumsReadService { * @returns Topic summary DTO. * @throws Does not throw. */ - private mapTopicSummary(row: ForumsTopicSummaryRow): ForumsTopicSummaryDto { + mapTopicSummary(row: ForumsTopicSummaryRow): ForumsTopicSummaryDto { return { id: row.id, parentTopicId: row.parentTopicId, diff --git a/src/forums/forums-watch-notification.service.spec.ts b/src/forums/forums-watch-notification.service.spec.ts index 7affc0f..a691ca5 100644 --- a/src/forums/forums-watch-notification.service.spec.ts +++ b/src/forums/forums-watch-notification.service.spec.ts @@ -230,6 +230,7 @@ describe('ForumsWatchNotificationService', () => { ).toHaveBeenCalledWith( expect.objectContaining({ memberId: '2', isMachine: false }), { + topicId: 'topic-1', challengeId: 'challenge-1', roleName: 'reviewer', hasRestrictionConflict: false, diff --git a/src/forums/forums-watch-notification.service.ts b/src/forums/forums-watch-notification.service.ts index 10e6518..df12c02 100644 --- a/src/forums/forums-watch-notification.service.ts +++ b/src/forums/forums-watch-notification.service.ts @@ -318,7 +318,7 @@ export class ForumsWatchNotificationService { const decision = await this.accessPolicyService.decideForRestrictionVisibility( principal, - params.restrictions, + { ...params.restrictions, topicId: params.topic.id }, ); if (!decision.allowed) { diff --git a/src/forums/forums.module.ts b/src/forums/forums.module.ts index be92ae0..548bb44 100644 --- a/src/forums/forums.module.ts +++ b/src/forums/forums.module.ts @@ -16,6 +16,9 @@ import { MemberHandleService } from './member-handle.service'; import { ModerationController } from './moderation.controller'; import { PostsController } from './posts.controller'; import { ResourceAccessService } from './resource-access.service'; +import { PublicForumsController } from './public-forums.controller'; +import { PublicForumsService } from './public-forums.service'; +import { PublicForumAccessService } from './public-forum-access.service'; import { TopicsController } from './topics.controller'; /** @@ -29,12 +32,14 @@ import { TopicsController } from './topics.controller'; */ @Module({ imports: [DbModule], - controllers: [TopicsController, PostsController, ModerationController], + controllers: [TopicsController, PostsController, ModerationController, PublicForumsController], providers: [ ChallengeAccessService, ChallengeApiService, EventBusService, ForumsAccessPolicyService, + PublicForumAccessService, + PublicForumsService, ForumsCommandService, ForumsMemberDirectoryService, ForumsModerationService, diff --git a/src/forums/import/public-vanilla-import.spec.ts b/src/forums/import/public-vanilla-import.spec.ts new file mode 100644 index 0000000..b1361b1 --- /dev/null +++ b/src/forums/import/public-vanilla-import.spec.ts @@ -0,0 +1,136 @@ +import { + publicCategories, + publicImportId, + publicMarkdown, + planPublicImport, + PublicImportSnapshot, +} from './public-vanilla-import'; + +const date = new Date('2020-01-01T00:00:00Z'); +/** Creates a minimal public/private migration source. @returns Fixture. @throws Never. */ +function fixture(): PublicImportSnapshot { + return { + categories: [ + { + CategoryID: 1, + ParentCategoryID: -1, + Name: 'General', + DisplayAs: 'Categories', + Sort: 1, + PermissionCategoryID: 1, + InsertUserID: 1, + DateInserted: date, + DateUpdated: date, + UrlCode: 'general', + }, + { + CategoryID: 2, + ParentCategoryID: 1, + Name: 'Reviewer Discussions', + DisplayAs: 'Discussions', + Sort: 2, + PermissionCategoryID: 1, + InsertUserID: 1, + DateInserted: date, + DateUpdated: date, + UrlCode: 'reviewers', + }, + ], + roles: [{ RoleID: 2, Name: 'Guest', Type: 'guest' }], + permissions: [{ JunctionID: 1, RoleID: 2, 'Vanilla.Discussions.View': 1 }], + discussions: [ + { + DiscussionID: 10, + CategoryID: 2, + Name: 'Review', + Body: '@member ![file](https://cdn.filestackcontent.com/file)', + Format: 'Markdown', + InsertUserID: 1, + DateInserted: date, + DateUpdated: date, + }, + ], + comments: [ + { + CommentID: 20, + DiscussionID: 10, + Body: 'Reply', + Format: 'Markdown', + InsertUserID: 1, + DateInserted: date, + DateUpdated: date, + ParentCommentID: 0, + }, + ], + users: [{ UserID: 1, Name: 'missing' }], + memberships: [], + watches: [], + categoryWatches: [], + media: [], + }; +} + +describe('insert-only public Vanilla planning', () => { + it('excludes challenge and group descendants even if a child has no group', () => { + expect( + publicCategories([ + { CategoryID: -1 }, + { CategoryID: 1, ParentCategoryID: -1, UrlCode: 'general' }, + { CategoryID: 2, ParentCategoryID: -1, UrlCode: 'challenges-forums' }, + { CategoryID: 3, ParentCategoryID: 2 }, + { CategoryID: 4, ParentCategoryID: 1, GroupID: 3 }, + { CategoryID: 5, ParentCategoryID: 4 }, + ]).map((row) => row.CategoryID), + ).toEqual([1]); + expect(() => + publicCategories([{ CategoryID: 1, ParentCategoryID: 9 }]), + ).toThrow('Missing category ancestor'); + }); + it('preserves every post, Markdown attachment/mention and anonymous legacy author without granting identity access', () => { + const plan = planPublicImport(fixture(), new Map()); + expect(plan.entries.filter((row) => row.table === 'Post')).toHaveLength(2); + expect( + plan.entries.find((row) => row.type === 'starter')?.row, + ).toMatchObject({ + authorMemberId: 'vanilla:1', + authorHandle: 'missing', + content: fixture().discussions[0].Body, + }); + expect( + plan.entries.find( + (row) => row.type === 'category' && row.sourceId === '2', + )?.row.readRoles, + ).toEqual(['reviewer']); + expect( + plan.entries.filter( + (row) => + row.type === 'closure' && + row.row.descendantTopicId === publicImportId('discussion', 10), + ), + ).toHaveLength(3); + }); + it('rejects cross-thread reply parents and native media that have not been copied', () => { + const source = fixture(); + source.comments[0].ParentCommentID = 999; + expect(() => planPublicImport(source, new Map())).toThrow( + 'Invalid reply parent', + ); + const media = fixture(); + media.media.push({ MediaID: 1, ForeignTable: 'discussion', ForeignID: 10 }); + expect(() => planPublicImport(media, new Map())).toThrow( + 'native media require migration', + ); + }); + it('converts Quill links and retains Markdown unchanged, rejecting unsupported formats', () => { + expect( + publicMarkdown( + '[{"insert":"Hi "},{"insert":"link","attributes":{"link":"https://example.org"}}]', + 'Rich', + ), + ).toBe('Hi [link]()'); + expect( + publicMarkdown('**Hi**\n![image](https://example.org/x)', 'Markdown'), + ).toBe('**Hi**\n![image](https://example.org/x)'); + expect(() => publicMarkdown('html', 'Html')).toThrow('Unsupported'); + }); +}); diff --git a/src/forums/import/public-vanilla-import.ts b/src/forums/import/public-vanilla-import.ts new file mode 100644 index 0000000..dd2b7b1 --- /dev/null +++ b/src/forums/import/public-vanilla-import.ts @@ -0,0 +1,708 @@ +import { createHash } from 'crypto'; +import { Client } from 'pg'; +import { Connection, createConnection, RowDataPacket } from 'mysql2/promise'; + +export type LegacyRow = Record; +export interface PublicImportSnapshot { + categories: LegacyRow[]; + permissions: LegacyRow[]; + roles: LegacyRow[]; + discussions: LegacyRow[]; + comments: LegacyRow[]; + users: LegacyRow[]; + memberships: LegacyRow[]; + watches: LegacyRow[]; + categoryWatches: LegacyRow[]; + media: LegacyRow[]; +} +export interface PublicImportEntry { + table: string; + type: string; + sourceId: string; + key: string[]; + row: LegacyRow; +} + +/** Computes stable JSON for provenance comparisons, including database timestamps. + * @param value JSON-compatible data. @returns Canonical JSON. @throws Invalid cyclic inputs. + */ +export function canonical(value: any): string { + if (value instanceof Date) return JSON.stringify(value.toISOString()); + if (Array.isArray(value)) return `[${value.map(canonical).join(',')}]`; + if (value && typeof value === 'object') + return `{${Object.keys(value) + .filter((key) => value[key] !== undefined) + .sort() + .map((key) => `${JSON.stringify(key)}:${canonical(value[key])}`) + .join(',')}}`; + return JSON.stringify(value); +} +/** Hashes source/target projections without exposing their text in reports. + * @param value JSON input. @returns SHA-256 hex. @throws Canonicalization errors. + */ +export function publicImportHash(value: any): string { + return createHash('sha256').update(canonical(value)).digest('hex'); +} +/** Creates a deterministic ID in a distinct public-import namespace. + * @param type Entity kind. @param sourceId Vanilla ID. @returns 14-character target ID. @throws Never. + */ +export function publicImportId( + type: string, + sourceId: string | number, +): string { + return ( + 'pf' + + createHash('sha256') + .update(`vanilla-public-v1:${type}:${sourceId}`) + .digest('base64url') + .slice(0, 12) + ); +} +/** Selects non-challenge/non-group category subtrees, checking the entire ancestry. + * @param categories Full Vanilla category inventory. @returns Eligible categories in source sort order. + * @throws Missing ancestors or cyclic categories, rather than accidentally publishing an unknown subtree. + */ +export function publicCategories(categories: LegacyRow[]): LegacyRow[] { + const byId = new Map(categories.map((row) => [Number(row.CategoryID), row])); + return categories + .filter((row) => { + if (Number(row.CategoryID) === -1 || row.UrlCode === 'groups') + return false; + let current: LegacyRow | undefined = row; + const seen = new Set(); + while (current && Number(current.CategoryID) !== -1) { + const id = Number(current.CategoryID); + if (seen.has(id)) throw new Error(`Category cycle at ${id}`); + seen.add(id); + if ( + current.GroupID || + current.UrlCode === 'challenges-forums' || + current.UrlCode === 'groups' + ) + return false; + if (current.ParentCategoryID === null) + throw new Error(`Missing parent for category ${id}`); + current = byId.get(Number(current.ParentCategoryID)); + if (!current) throw new Error(`Missing category ancestor for ${id}`); + } + return true; + }) + .sort((a, b) => Number(a.Sort) - Number(b.Sort)); +} +/** Converts the legacy Rich (Quill Delta) format to Markdown while preserving supported formatting. + * Markdown input is retained byte for byte, including attachment URLs and mentions. + * @param body Source text. @param format Vanilla format. @returns Markdown. @throws Unsupported formats/embeds/attributes. + */ +export function publicMarkdown( + body: string | null, + format: string | null, +): string | null { + if (!body || !format || format.toLowerCase() === 'markdown') return body; + if (format.toLowerCase() !== 'rich') + throw new Error(`Unsupported Vanilla format: ${format}`); + const parsed = JSON.parse(body) as LegacyRow[]; + if (!Array.isArray(parsed)) throw new Error('Invalid Rich document'); + let output = ''; + for (const operation of parsed) { + const attributes = operation.attributes ?? {}; + if (typeof operation.insert !== 'string') + throw new Error('Unsupported Rich embed; review required'); + let text = operation.insert as string; + for (const key of Object.keys(attributes)) { + if ( + ![ + 'bold', + 'italic', + 'underline', + 'strike', + 'link', + 'code', + 'header', + 'list', + 'blockquote', + 'code-block', + ].includes(key) + ) + throw new Error(`Unsupported Rich attribute ${key}`); + } + if (attributes.link) + text = `[${text}](<${String(attributes.link).replace(/>/g, '%3E')}>)`; + if (attributes.bold) text = `**${text}**`; + if (attributes.italic) text = `_${text}_`; + if (attributes.underline) text = `${text}`; + if (attributes.strike) text = `~~${text}~~`; + if (attributes.code) text = '`' + text + '`'; + if (text === '\n' && Object.keys(attributes).length) { + const start = output.lastIndexOf('\n') + 1; + const prefix = attributes.header + ? '#'.repeat(Number(attributes.header)) + ' ' + : attributes.list === 'ordered' + ? '1. ' + : attributes.list + ? '- ' + : attributes.blockquote + ? '> ' + : ''; + output = output.slice(0, start) + prefix + output.slice(start); + if (attributes['code-block']) + throw new Error('Rich code block requires review'); + } + output += text; + } + return output; +} + +/** Reads a consistent, explicitly read-only source snapshot with no global bans or challenge content. + * @param url Authorized production Vanilla URL. @returns Public source snapshot. @throws SQL/inventory errors. + */ +export async function readPublicVanilla( + url: string, +): Promise { + const connection = await createConnection({ uri: url, timezone: 'Z' }); + try { + await connection.query( + 'SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ', + ); + await connection.query('SET SESSION TRANSACTION READ ONLY'); + await connection.query('START TRANSACTION WITH CONSISTENT SNAPSHOT'); + const allCategories = await rows(connection, 'SELECT * FROM GDN_Category'); + const categories = publicCategories(allCategories); + const ids = categories.map((row) => row.CategoryID); + if (!ids.length) throw new Error('No public categories found'); + const snapshot: PublicImportSnapshot = { + categories, + permissions: await rows( + connection, + "SELECT * FROM GDN_Permission WHERE JunctionTable = 'Category'", + ), + roles: await rows(connection, 'SELECT RoleID,Name,Type FROM GDN_Role'), + discussions: await rows( + connection, + 'SELECT DiscussionID,CategoryID,InsertUserID,Name,Body,Format,Closed,Announce,CountViews,DateInserted,DateUpdated,GroupID FROM GDN_Discussion WHERE CategoryID IN (?)', + [ids], + ), + comments: await rows( + connection, + 'SELECT c.CommentID,c.DiscussionID,c.InsertUserID,c.Body,c.Format,c.DateInserted,c.DateUpdated,c.DateDeleted,c.DeleteUserID,c.ParentCommentID FROM GDN_Comment c JOIN GDN_Discussion d ON d.DiscussionID=c.DiscussionID WHERE d.CategoryID IN (?)', + [ids], + ), + users: await rows(connection, 'SELECT UserID,Name FROM GDN_User'), + memberships: await rows( + connection, + "SELECT UserID,RoleID FROM GDN_UserRole WHERE RoleID IN (SELECT RoleID FROM GDN_Role WHERE Type IS NULL OR Type <> 'topcoder')", + ), + watches: await rows( + connection, + 'SELECT u.* FROM GDN_UserDiscussion u JOIN GDN_Discussion d ON d.DiscussionID=u.DiscussionID WHERE d.CategoryID IN (?) AND (u.Bookmarked=1 OR u.DateLastViewed IS NOT NULL)', + [ids], + ), + categoryWatches: await rows( + connection, + 'SELECT * FROM GDN_UserCategory WHERE CategoryID IN (?)', + [ids], + ), + media: await rows(connection, 'SELECT * FROM GDN_Media'), + }; + if (snapshot.discussions.some((row) => row.GroupID)) + throw new Error( + 'Group-linked discussion under public category; review required', + ); + await connection.query('COMMIT'); + return snapshot; + } finally { + await connection.end(); + } +} +/** Executes an importer SELECT using bound parameters. @param db Read-only connection. @param sql SQL. @param values Parameters. + * @returns Rows. @throws MySQL errors. + */ +async function rows( + db: Connection, + sql: string, + values: unknown[] = [], +): Promise { + return (await db.query(sql, values))[0]; +} + +/** Plans immutable public forum rows using target-member handle matches, preserving unknown authors with non-login IDs. + * @param source Consistent Vanilla snapshot. @param members Target member projection keyed by lowercase handle. + * @returns Insert plan and explicit identity/attachment exceptions. @throws Unresolvable nesting or source formats. + */ +export function planPublicImport( + source: PublicImportSnapshot, + members: Map, +) { + const entries: PublicImportEntry[] = []; + const unmapped = new Set(); + const users = new Map(source.users.map((row) => [Number(row.UserID), row])); + const actor = ( + id: number, + contentAuthor = true, + ): { authorMemberId: string; authorHandle: string } => { + const user = users.get(Number(id)); + const member = members.get( + String(user?.Name ?? '') + .trim() + .toLowerCase(), + ); + if (!member && contentAuthor) unmapped.add(Number(id)); + return { + authorMemberId: member ? String(member.userId) : `vanilla:${id}`, + authorHandle: + member?.handle ?? + String(user?.Name || `Vanilla member ${id}`).slice(0, 128), + }; + }; + const add = ( + table: string, + type: string, + sourceId: string | number, + key: string[], + row: LegacyRow, + ): void => { + entries.push({ table, type, sourceId: String(sourceId), key, row }); + }; + const topicRows = new Map(); + const categoryIds = new Map( + source.categories.map((row) => [ + Number(row.CategoryID), + publicImportId('category', row.CategoryID), + ]), + ); + const topic = ( + id: string, + parentTopicId: string | null, + title: string, + authorId: number, + createdAt: Date, + updatedAt: Date, + locked = false, + announcement = false, + ): LegacyRow => ({ + id, + parentTopicId, + challengeId: null, + roleName: null, + title, + ...actor(authorId), + createdAt, + updatedAt: updatedAt ?? createdAt, + isAnnouncement: announcement, + locked, + lockedAt: null, + lockedByMemberId: null, + deletedAt: null, + deletedByMemberId: null, + }); + const roleById = new Map( + source.roles.map((role) => [Number(role.RoleID), role]), + ); + const permissions = (category: LegacyRow, field: string): string[] => + [ + ...new Set( + source.permissions + .filter( + (permission) => + Number(permission.JunctionID) === + Number(category.PermissionCategoryID) && !!permission[field], + ) + .map((permission) => { + const role = roleById.get(Number(permission.RoleID)); + if (!role) throw new Error('Missing Vanilla permission role'); + if (role.Type === 'guest') return '$public'; + if (role.RoleID === 8 || role.Name === 'Topcoder User') + return '$authenticated'; + if (role.Type === 'topcoder') + return String(role.Name).trim().toLowerCase(); + return `vanilla-role:${role.RoleID}`; + }), + ), + ].sort(); + for (const category of source.categories) { + const id = categoryIds.get(Number(category.CategoryID))!; + const parentId = + Number(category.ParentCategoryID) === -1 + ? null + : categoryIds.get(Number(category.ParentCategoryID)); + if (parentId === undefined) + throw new Error(`Excluded category parent ${category.CategoryID}`); + const row = topic( + id, + parentId, + category.Name, + category.InsertUserID, + category.DateInserted, + category.DateUpdated, + ); + topicRows.set(id, row); + add('Topic', 'category-topic', category.CategoryID, ['id'], row); + let readRoles = permissions(category, 'Vanilla.Discussions.View'); + let createRoles = permissions(category, 'Vanilla.Discussions.Add'); + let replyRoles = permissions(category, 'Vanilla.Comments.Add'); + // Explicit product requirements: new public forums reopen authenticated conversation. + // Private membership ACLs remain exactly scoped; only ordinary public/member categories get this override. + if (readRoles.includes('$public') || readRoles.includes('$authenticated')) { + createRoles = ['$authenticated']; + replyRoles = ['$authenticated']; + } + if (/^copilot discussions?$/i.test(category.Name)) { + readRoles = ['copilot']; + createRoles = ['copilot']; + replyRoles = ['copilot']; + } + if (/^reviewer discussions?$/i.test(category.Name)) { + readRoles = ['reviewer']; + createRoles = ['reviewer']; + replyRoles = ['reviewer']; + } + if (category.DisplayAs === 'Categories') createRoles = []; + add('PublicForumCategory', 'category', category.CategoryID, ['topicId'], { + topicId: id, + description: category.Description ?? '', + displayAs: category.DisplayAs, + sortOrder: Number(category.Sort), + readRoles, + createRoles, + replyRoles, + legacyId: Number(category.CategoryID), + legacySlug: category.UrlCode, + source: { + permissionCategoryId: category.PermissionCategoryID, + archived: category.Archived, + allowDiscussions: category.AllowDiscussions, + }, + }); + } + const discussions = new Map( + source.discussions.map((row) => [Number(row.DiscussionID), row]), + ); + const post = ( + id: string, + topicId: string, + parentType: string, + parentId: string, + sourceRow: LegacyRow, + ): LegacyRow => ({ + id, + topicId, + parentType, + parentId, + ...actor(sourceRow.InsertUserID), + content: publicMarkdown(sourceRow.Body, sourceRow.Format), + createdAt: sourceRow.DateInserted, + updatedAt: sourceRow.DateUpdated ?? sourceRow.DateInserted, + deletedAt: sourceRow.DateDeleted ?? null, + deletedByMemberId: sourceRow.DeleteUserID + ? actor(sourceRow.DeleteUserID).authorMemberId + : null, + }); + for (const discussion of source.discussions) { + const id = publicImportId('discussion', discussion.DiscussionID); + const parent = categoryIds.get(Number(discussion.CategoryID)); + if (!parent) throw new Error('Discussion has excluded category'); + const row = topic( + id, + parent, + discussion.Name, + discussion.InsertUserID, + discussion.DateInserted, + discussion.DateUpdated, + !!discussion.Closed, + !!discussion.Announce, + ); + topicRows.set(id, row); + add('Topic', 'discussion', discussion.DiscussionID, ['id'], row); + add( + 'Post', + 'starter', + discussion.DiscussionID, + ['id'], + post( + publicImportId('starter', discussion.DiscussionID), + id, + 'TOPIC', + id, + discussion, + ), + ); + } + const comments = new Map( + source.comments.map((row) => [Number(row.CommentID), row]), + ); + for (const comment of source.comments) { + if (!discussions.has(Number(comment.DiscussionID))) + throw new Error('Orphan comment'); + const topicId = publicImportId('discussion', comment.DiscussionID); + const parent = + Number(comment.ParentCommentID) > 0 + ? comments.get(Number(comment.ParentCommentID)) + : undefined; + if ( + Number(comment.ParentCommentID) > 0 && + (!parent || Number(parent.DiscussionID) !== Number(comment.DiscussionID)) + ) + throw new Error(`Invalid reply parent ${comment.CommentID}`); + const seen = new Set([Number(comment.CommentID)]); + let ancestor = parent; + while (ancestor) { + if (seen.has(Number(ancestor.CommentID))) throw new Error('Reply cycle'); + seen.add(Number(ancestor.CommentID)); + ancestor = comments.get(Number(ancestor.ParentCommentID)); + } + add( + 'Post', + 'comment', + comment.CommentID, + ['id'], + post( + publicImportId('comment', comment.CommentID), + topicId, + parent ? 'POST' : 'TOPIC', + parent ? publicImportId('comment', parent.CommentID) : topicId, + comment, + ), + ); + } + for (const row of topicRows.values()) { + let ancestor: LegacyRow | undefined = row; + let depth = 0; + while (ancestor) { + add( + 'TopicClosure', + 'closure', + `${row.id}:${ancestor.id}`, + ['ancestorTopicId', 'descendantTopicId'], + { ancestorTopicId: ancestor.id, descendantTopicId: row.id, depth }, + ); + ancestor = ancestor.parentTopicId + ? topicRows.get(ancestor.parentTopicId) + : undefined; + depth += 1; + } + } + const usedLocalRoles = new Set( + entries + .filter((entry) => entry.table === 'PublicForumCategory') + .flatMap((entry) => [ + ...entry.row.readRoles, + ...entry.row.createRoles, + ...entry.row.replyRoles, + ]) + .filter((role) => role.startsWith('vanilla-role:')), + ); + for (const membership of source.memberships) { + const roleName = `vanilla-role:${membership.RoleID}`; + if (!usedLocalRoles.has(roleName)) continue; + const memberId = actor(membership.UserID, false).authorMemberId; + if (memberId.startsWith('vanilla:')) continue; + add( + 'PublicForumRoleMember', + 'role-member', + `${membership.RoleID}:${membership.UserID}`, + ['roleName', 'memberId'], + { roleName, memberId }, + ); + } + let unmappedStates = 0; + const state = (row: LegacyRow, category = false): void => { + const memberId = actor(row.UserID, false).authorMemberId; + if (memberId.startsWith('vanilla:')) { + unmappedStates += 1; + return; + } + const topicId = publicImportId( + category ? 'category' : 'discussion', + category ? row.CategoryID : row.DiscussionID, + ); + const sourceId = `${category ? 'category' : 'discussion'}:${category ? row.CategoryID : row.DiscussionID}:${row.UserID}`; + const read = category ? row.DateMarkedRead : row.DateLastViewed; + if (category ? row.Followed && !row.Unfollow : row.Bookmarked) + add('TopicWatch', 'watch', sourceId, ['topicId', 'memberId'], { + topicId, + memberId, + createdAt: read ?? topicRows.get(topicId)!.createdAt, + }); + if (read) + add('TopicReadState', 'read', sourceId, ['topicId', 'memberId'], { + topicId, + memberId, + lastReadAt: read, + updatedAt: read, + }); + }; + source.watches.forEach((row) => state(row)); + source.categoryWatches.forEach((row) => state(row, true)); + const referencedMedia = source.media.filter((media) => + /^discussion$/i.test(media.ForeignTable) + ? discussions.has(Number(media.ForeignID)) + : /^comment$/i.test(media.ForeignTable) && + comments.has(Number(media.ForeignID)), + ); + // Native Media URLs must remain reachable after cutover; stop for a separate attachment-copy plan. + if (referencedMedia.length) + throw new Error( + `Public native media require migration: ${referencedMedia.map((row) => row.MediaID).join(',')}`, + ); + return { + entries, + unmappedAuthorIds: [...unmapped].sort((a, b) => a - b), + unmappedStates, + }; +} + +const IMPORT_TABLES = [ + 'Topic', + 'Post', + 'PublicForumCategory', + 'TopicClosure', + 'PublicForumRoleMember', + 'TopicWatch', + 'TopicReadState', +]; +/** Validates existing provenance and inserts only new rows in one transaction. + * @param db Target PostgreSQL connection. @param entries Audited plan. @param apply False for a read-only dry run. + * @returns Counts, challenge fingerprints and plan digest. @throws Any conflict, source drift, target edit or SQL failure; rolls back all writes. + */ +export async function writePublicImport( + db: Client, + entries: PublicImportEntry[], + apply: boolean, +) { + await db.query( + apply + ? 'BEGIN ISOLATION LEVEL SERIALIZABLE' + : 'BEGIN ISOLATION LEVEL REPEATABLE READ READ ONLY', + ); + try { + if (apply) + await db.query( + "SELECT pg_advisory_xact_lock(hashtext('vanilla-public-v1'))", + ); + const before = await challengeFingerprint(db); + const existingRecords = ( + await db.query( + 'SELECT * FROM forums."PublicForumImportRecord"', + ) + ).rows; + const provenance = new Map( + existingRecords.map((row) => [`${row.entityType}:${row.sourceId}`, row]), + ); + const newEntries: PublicImportEntry[] = []; + for (const table of IMPORT_TABLES) { + const planned = entries.filter((entry) => entry.table === table); + if (!planned.length) continue; + const existing = ( + await db.query(`SELECT * FROM forums."${table}"`) + ).rows; + const keys = planned[0].key; + const keyOf = (row: LegacyRow): string => + canonical(keys.map((key) => row[key])); + const byKey = new Map(existing.map((row) => [keyOf(row), row])); + const seen = new Set(); + for (const entry of planned) { + const key = keyOf(entry.row); + if (seen.has(key)) throw new Error(`Duplicate planned key in ${table}`); + seen.add(key); + const previous = provenance.get(`${entry.type}:${entry.sourceId}`); + const target = byKey.get(key); + const hash = publicImportHash(entry.row); + if (previous) { + if ( + !target || + previous.sourceHash !== hash || + previous.targetHash !== + publicImportHash( + Object.fromEntries( + Object.keys(entry.row).map((name) => [name, target[name]]), + ), + ) + ) + throw new Error( + `Provenance conflict ${entry.type}:${entry.sourceId}; review required`, + ); + } else { + if (target) + throw new Error( + `Unowned existing ${entry.type}:${entry.sourceId}; refusing overwrite`, + ); + newEntries.push(entry); + } + } + } + if (apply) { + for (const table of IMPORT_TABLES) { + const planned = newEntries.filter((entry) => entry.table === table); + if (!planned.length) continue; + const columns = Object.keys(planned[0].row) + .map((column) => `"${column}"`) + .join(','); + await db.query( + `INSERT INTO forums."${table}" (${columns}) SELECT ${columns} FROM jsonb_populate_recordset(NULL::forums."${table}",$1::jsonb)`, + [JSON.stringify(planned.map((entry) => entry.row))], + ); + } + const records = newEntries.map((entry) => ({ + entityType: entry.type, + sourceId: entry.sourceId, + targetId: publicImportId(entry.type, entry.sourceId), + sourceHash: publicImportHash(entry.row), + targetHash: publicImportHash(entry.row), + createdAt: new Date().toISOString(), + })); + if (records.length) + await db.query( + 'INSERT INTO forums."PublicForumImportRecord" SELECT * FROM jsonb_populate_recordset(NULL::forums."PublicForumImportRecord",$1::jsonb)', + [JSON.stringify(records)], + ); + } + const after = await challengeFingerprint(db); + if (canonical(before) !== canonical(after)) + throw new Error('Challenge data fingerprint changed; rolling back'); + await db.query('COMMIT'); + return { + applied: apply, + planned: entries.length, + inserted: apply ? newEntries.length : 0, + pending: newEntries.length, + existing: entries.length - newEntries.length, + planHash: publicImportHash(entries), + challengeBefore: before, + challengeAfter: after, + counts: Object.fromEntries( + IMPORT_TABLES.map((table) => [ + table, + entries.filter((entry) => entry.table === table).length, + ]), + ), + }; + } catch (error) { + await db.query('ROLLBACK'); + throw error; + } +} +/** Computes content fingerprints covering every existing challenge topic and its related rows. + * @param db Transaction connection. @returns Per-table count and hash. @throws SQL errors. + */ +export async function challengeFingerprint( + db: Client, +): Promise> { + const output: Record = {}; + const scopes: Record = { + Topic: 'id', + Post: '"topicId"', + TopicWatch: '"topicId"', + TopicReadState: '"topicId"', + TopicClosure: '"descendantTopicId"', + }; + for (const [table, column] of Object.entries(scopes)) { + const result = ( + await db.query<{ value: LegacyRow }>( + `SELECT to_jsonb(t) AS value FROM forums."${table}" t WHERE t.${column} IN (SELECT "descendantTopicId" FROM forums."TopicClosure" c JOIN forums."Topic" root ON root.id=c."ancestorTopicId" WHERE root."challengeId" IS NOT NULL)`, + ) + ).rows + .map((row) => canonical(row.value)) + .sort(); + output[table] = { count: result.length, hash: publicImportHash(result) }; + } + return output; +} diff --git a/src/forums/public-forum-access.service.spec.ts b/src/forums/public-forum-access.service.spec.ts new file mode 100644 index 0000000..57da4a9 --- /dev/null +++ b/src/forums/public-forum-access.service.spec.ts @@ -0,0 +1,114 @@ +import { DbService } from '../db/db.service'; +import { ForumsPrincipal } from './forums-access.types'; +import { PublicForumAccessService } from './public-forum-access.service'; + +const guest: ForumsPrincipal = { + memberId: null, + roles: [], + scopes: [], + isAdmin: false, + isMachine: false, +}; +const member: ForumsPrincipal = { ...guest, memberId: '42' }; +/** Builds isolated inherited ACL fixtures. @param rules Ancestor rows. @param roles Imported local grants. + * @returns Real ACL service with database fixtures. @throws Never. + */ +function service(rules: unknown[], roles: string[] = []) { + return new PublicForumAccessService({ + $queryRaw: jest.fn().mockResolvedValue(rules), + publicForumRoleMember: { + findMany: jest + .fn() + .mockResolvedValue(roles.map((roleName) => ({ roleName }))), + }, + } as unknown as DbService); +} +const open = { + topicId: 'category', + depth: 1, + readRoles: ['$public'], + createRoles: ['$authenticated'], + replyRoles: ['$authenticated'], + displayAs: 'Discussions', +}; + +describe('public category ACLs', () => { + it('allows anonymous reading and forbids all guest participation', async () => { + expect(await service([open]).decide(guest, 'thread')).toMatchObject({ + view: true, + create: false, + reply: false, + }); + expect( + await service([{ ...open, depth: 0 }]).decide(guest, 'category'), + ).toMatchObject({ view: true, create: false, reply: false }); + }); + it('permits members to start threads only in discussion categories and reply only in threads', async () => { + expect(await service([open]).decide(member, 'thread')).toMatchObject({ + view: true, + create: false, + reply: true, + }); + expect(await service([open]).decide(member, 'category')).toMatchObject({ + create: true, + reply: false, + }); + expect( + await service([{ ...open, displayAs: 'Categories' }]).decide( + member, + 'category', + ), + ).toMatchObject({ create: false, reply: false }); + }); + it('requires every ancestor ACL and does not treat local membership as a global role', async () => { + const restricted = { ...open, readRoles: ['reviewer'] }; + const ancestor = { + ...open, + topicId: 'private', + depth: 2, + readRoles: ['vanilla-role:71'], + }; + expect( + ( + await service([restricted, ancestor], ['vanilla-role:71']).decide( + member, + 'thread', + ) + ).view, + ).toBe(false); + expect( + ( + await service([restricted, ancestor]).decide( + { ...member, roles: ['reviewer'] }, + 'thread', + ) + ).view, + ).toBe(false); + expect( + ( + await service([restricted, ancestor], ['vanilla-role:71']).decide( + { ...member, roles: ['reviewer'] }, + 'thread', + ) + ).view, + ).toBe(true); + }); + it('supports read-only custom roles and fails closed on empty grant lists', async () => { + expect( + await service([ + { ...open, readRoles: ['auditor'], replyRoles: [] }, + ]).decide({ ...member, roles: ['auditor'] }, 'thread'), + ).toMatchObject({ view: true, reply: false }); + expect( + (await service([{ ...open, readRoles: [] }]).decide(member, 'thread')) + .view, + ).toBe(false); + }); + it('leaves challenge restrictions to the existing policy and propagates database failures', async () => { + expect((await service([]).decide(guest, 'challenge')).managed).toBe(false); + const failing = new PublicForumAccessService({ + $queryRaw: jest.fn().mockRejectedValue(new Error('offline')), + } as unknown as DbService); + await expect(failing.decide(member, 'thread')).rejects.toThrow('offline'); + }); +}); diff --git a/src/forums/public-forum-access.service.ts b/src/forums/public-forum-access.service.ts new file mode 100644 index 0000000..0fc8cac --- /dev/null +++ b/src/forums/public-forum-access.service.ts @@ -0,0 +1,80 @@ +import { Injectable } from '@nestjs/common'; +import { PublicForumCategory } from '../../prisma/generated/client'; +import { DbService } from '../db/db.service'; +import { ForumsPrincipal } from './forums-access.types'; + +export interface PublicForumAccess { + view: boolean; + create: boolean; + reply: boolean; + category: boolean; + managed: boolean; +} + +/** Enforces public category ACLs for every read, command and notification. + * Rules are inherited through TopicClosure; each ancestor must permit reading. + * Vanilla-only memberships are scoped to forums and cannot elevate identity roles. + */ +@Injectable() +export class PublicForumAccessService { + /** @param db Forums database used to read category ACLs and imported memberships. @throws Never. */ + constructor(private readonly db: DbService) {} + + /** Evaluates inherited read rules and the nearest category's posting rules. + * @param principal Validated member, machine, or anonymous reader. + * @param topicId Existing topic being authorized. + * @returns Public access gates; unmanaged challenge topics retain their existing policy. + * @throws Database errors; callers must fail closed. + */ + async decide( + principal: ForumsPrincipal, + topicId: string, + ): Promise { + const categories = await this.db.$queryRaw< + (PublicForumCategory & { depth: number })[] + >` + SELECT category.*, closure.depth FROM "forums"."PublicForumCategory" category + JOIN "forums"."TopicClosure" closure ON closure."ancestorTopicId" = category."topicId" + WHERE closure."descendantTopicId" = ${topicId} ORDER BY closure.depth ASC`; + if (!categories.length) + return { + view: true, + create: true, + reply: true, + category: false, + managed: false, + }; + const roles = new Set(principal.roles); + if (principal.memberId) { + const memberships = await this.db.publicForumRoleMember.findMany({ + where: { memberId: principal.memberId }, + }); + memberships.forEach((row) => roles.add(row.roleName)); + } + const elevated = principal.isAdmin || principal.isMachine; + const matches = (allowed: string[]): boolean => + elevated || + allowed.some( + (role) => + role === '$public' || + (role === '$authenticated' && !!principal.memberId) || + roles.has(role), + ); + const view = categories.every((category) => matches(category.readRoles)); + const nearest = categories[0]; + const category = nearest.topicId === topicId; + const authenticated = !!principal.memberId || principal.isMachine; + return { + view, + category, + managed: true, + create: + view && + authenticated && + category && + nearest.displayAs === 'Discussions' && + matches(nearest.createRoles), + reply: view && authenticated && !category && matches(nearest.replyRoles), + }; + } +} diff --git a/src/forums/public-forums.controller.ts b/src/forums/public-forums.controller.ts new file mode 100644 index 0000000..c772ea4 --- /dev/null +++ b/src/forums/public-forums.controller.ts @@ -0,0 +1,48 @@ +import { Controller, Get, Query } from '@nestjs/common'; +import { ApiOperation, ApiTags } from '@nestjs/swagger'; +import { OptionalRead } from '../auth/decorators/optional-read.decorator'; +import { + Roles, + AUTHENTICATED_USER_ROLE, +} from '../auth/decorators/roles.decorator'; +import { Scopes } from '../auth/decorators/scopes.decorator'; +import { CurrentUser } from '../auth/decorators/user.decorator'; +import { ClientIp } from '../auth/decorators/client-ip.decorator'; +import { FORUMS_SCOPE_READ_TOPICS } from '../auth/scope-mappings'; +import { JwtUser } from '../auth/jwt.service'; +import { PublicForumsQueryDto } from './dto/public-forums.dto'; +import { PublicForumsService } from './public-forums.service'; + +/** Public catalog routes reuse the existing authenticated topic/post commands for interaction. */ +@ApiTags('Public forums') +@Controller('public') +@OptionalRead() +@Roles(AUTHENTICATED_USER_ROLE) +@Scopes(FORUMS_SCOPE_READ_TOPICS) +export class PublicForumsController { + /** @param service Visibility-filtered catalog service. @throws Never. */ + constructor(private readonly service: PublicForumsService) {} + + /** @param user Optional validated identity. @param ip Trusted IP. @returns Visible category cards. @throws Ban/database errors. */ + @Get('categories') + @ApiOperation({ + summary: + 'Read the public category tree; role restrictions apply before statistics', + }) + categories(@CurrentUser() user?: JwtUser, @ClientIp() ip?: string) { + return this.service.categories(user, ip); + } + + /** @param query Validated filters/page. @param user Optional identity. @param ip Trusted IP. + * @returns Visible thread page. @throws Access, validation and database errors. + */ + @Get('topics') + @ApiOperation({ summary: 'Search public threads or list watched content' }) + topics( + @Query() query: PublicForumsQueryDto, + @CurrentUser() user?: JwtUser, + @ClientIp() ip?: string, + ) { + return this.service.topics(query, user, ip); + } +} diff --git a/src/forums/public-forums.integration.spec.ts b/src/forums/public-forums.integration.spec.ts new file mode 100644 index 0000000..0ed5adb --- /dev/null +++ b/src/forums/public-forums.integration.spec.ts @@ -0,0 +1,415 @@ +import { + INestApplication, + UnauthorizedException, + ValidationPipe, +} from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { Test } from '@nestjs/testing'; +import { Client } from 'pg'; +import * as request from 'supertest'; +import { AppModule } from '../app.module'; +import { JwtService } from '../auth/jwt.service'; +import { DbService } from '../db/db.service'; +import { EventBusService } from './event-bus.service'; +import { ForumsWatchNotificationService } from './forums-watch-notification.service'; +import { + challengeFingerprint, + planPublicImport, + PublicImportSnapshot, + writePublicImport, +} from './import/public-vanilla-import'; + +const url = process.env.PUBLIC_FORUMS_TEST_DATABASE_URL; +const integration = url ? describe : describe.skip; +/** Local-only HTTP and migration regression suite. It owns the entire *_http_test database. + * Runs against real Prisma/SQL, middleware, guards, controllers and policies; JWT validation and email delivery are isolated. + */ +integration('public forums PostgreSQL/HTTP access boundaries', () => { + let app: INestApplication; + let db: DbService; + let sql: Client; + let createdId: string; + beforeAll(async () => { + const parsed = new URL(url!); + if ( + !['127.0.0.1', 'localhost'].includes(parsed.hostname) || + !parsed.pathname.endsWith('_http_test') + ) { + throw new Error( + 'Integration tests require a dedicated local *_http_test database', + ); + } + sql = new Client({ connectionString: url }); + await sql.connect(); + await sql.query( + 'TRUNCATE forums."Topic", forums."PublicForumCategory", forums."PublicForumRoleMember", forums."PublicForumImportRecord" CASCADE', + ); + parsed.searchParams.set('options', '-c search_path=forums,public'); + const module = await Test.createTestingModule({ imports: [AppModule] }) + .overrideProvider(ConfigService) + .useValue( + new ConfigService({ + database: { url: parsed.href }, + auth: {}, + notifications: {}, + }), + ) + .overrideProvider(JwtService) + .useValue({ + validateToken: async (token: string) => { + if (token === 'invalid') + throw new UnauthorizedException('Invalid token'); + return { + userId: token === 'local' ? '202' : '101', + handle: 'test-member', + roles: + token === 'copilot' + ? ['copilot'] + : token === 'reviewer' + ? ['reviewer'] + : token === 'admin' + ? ['administrator'] + : [], + isMachine: token === 'machine', + scopes: [], + }; + }, + }) + .overrideProvider(EventBusService) + .useValue({}) + .overrideProvider(ForumsWatchNotificationService) + .useValue({ + publishPostNotification: jest + .fn() + .mockResolvedValue({ published: false }), + }) + .compile(); + app = module.createNestApplication(); + app.useGlobalPipes( + new ValidationPipe({ + transform: true, + whitelist: true, + forbidNonWhitelisted: true, + }), + ); + await app.init(); + db = app.get(DbService); + for (const [id, parent, role, local] of [ + ['pfpublic', null, '$public', false], + ['pfcopilot', null, 'copilot', false], + ['pfreviewer', null, 'reviewer', false], + ['pfprivate', null, '$authenticated', false], + ['pflocal', 'pfprivate', 'vanilla-role:69', true], + ] as const) { + await db.topic.create({ + data: { + id, + parentTopicId: parent, + title: id, + authorMemberId: '101', + authorHandle: 'test-member', + }, + }); + await db.topicClosure.create({ + data: { ancestorTopicId: id, descendantTopicId: id, depth: 0 }, + }); + if (parent) + await db.topicClosure.create({ + data: { ancestorTopicId: parent, descendantTopicId: id, depth: 1 }, + }); + await db.publicForumCategory.create({ + data: { + topicId: id, + legacyId: [ + 'pfpublic', + 'pfcopilot', + 'pfreviewer', + 'pfprivate', + 'pflocal', + ].indexOf(id), + legacySlug: id, + source: {}, + description: '', + displayAs: 'Discussions', + sortOrder: 0, + readRoles: [role], + createRoles: [role === '$public' ? '$authenticated' : role], + replyRoles: [role === '$public' ? '$authenticated' : role], + }, + }); + const tid = `${id}t`; + await db.topic.create({ + data: { + id: tid, + parentTopicId: id, + title: `${id} secret-search`, + authorMemberId: '101', + authorHandle: 'test-member', + }, + }); + await db.topicClosure.createMany({ + data: [ + { ancestorTopicId: tid, descendantTopicId: tid, depth: 0 }, + { ancestorTopicId: id, descendantTopicId: tid, depth: 1 }, + ...(parent + ? [{ ancestorTopicId: parent, descendantTopicId: tid, depth: 2 }] + : []), + ], + }); + await db.post.create({ + data: { + id: `${id}p`, + topicId: tid, + parentType: 'TOPIC', + parentId: tid, + authorMemberId: '101', + authorHandle: `${id}-author`, + content: `${id} content secret-search`, + }, + }); + await db.topicWatch.create({ data: { topicId: tid, memberId: '101' } }); + if (local) + await db.publicForumRoleMember.create({ + data: { roleName: role, memberId: '202' }, + }); + } + await db.topic.create({ + data: { + id: 'pfchallenge', + title: 'Existing challenge', + challengeId: 'challenge-id', + authorMemberId: '101', + authorHandle: 'test-member', + }, + }); + await db.topicClosure.create({ + data: { + ancestorTopicId: 'pfchallenge', + descendantTopicId: 'pfchallenge', + depth: 0, + }, + }); + }, 30000); + afterAll(async () => { + await app?.close(); + await sql?.end(); + }); + + it('allows anonymous public reads but hides roles, private parents, search results and identities', async () => { + const categories = await request(app.getHttpServer()) + .get('/public/categories') + .expect(200); + expect( + categories.body.map((category: { id: string }) => category.id), + ).toEqual(['pfpublic']); + expect(categories.body[0]).toMatchObject({ + topicsCount: 1, + postsCount: 1, + participantsCount: 1, + canCreate: false, + }); + const search = await request(app.getHttpServer()) + .get('/public/topics?search=secret-search') + .expect(200); + expect(JSON.stringify(search.body)).not.toMatch( + /copilot|reviewer|private|local/, + ); + const detail = await request(app.getHttpServer()) + .get('/topics/pfpublict') + .expect(200); + expect(detail.body.permissions).toMatchObject({ + createPost: false, + watch: false, + }); + for (const id of [ + 'pfcopilott', + 'pfreviewert', + 'pflocalt', + 'pfprivatet', + 'pfchallenge', + ]) { + await request(app.getHttpServer()).get(`/topics/${id}`).expect(403); + } + await request(app.getHttpServer()) + .get('/topics/pfprivate/children') + .expect(403); + await request(app.getHttpServer()) + .get('/public/topics?categoryId=pfprivate') + .expect(403); + }); + it('enforces roles and local memberships without granting identity privileges', async () => { + await request(app.getHttpServer()) + .get('/topics/pfcopilott') + .set('Authorization', 'Bearer copilot') + .expect(200); + await request(app.getHttpServer()) + .get('/topics/pfreviewert') + .set('Authorization', 'Bearer reviewer') + .expect(200); + await request(app.getHttpServer()) + .get('/topics/pfreviewert') + .set('Authorization', 'Bearer copilot') + .expect(403); + await request(app.getHttpServer()) + .get('/topics/pflocalt') + .set('Authorization', 'Bearer local') + .expect(200); + await request(app.getHttpServer()) + .get('/topics/pflocalt') + .set('Authorization', 'Bearer member') + .expect(403); + const watches = await request(app.getHttpServer()) + .get('/public/topics?watching=true') + .set('Authorization', 'Bearer member') + .expect(200); + expect(JSON.stringify(watches.body)).not.toMatch(/copilot|reviewer|local/); + await request(app.getHttpServer()) + .post('/topics') + .set('Authorization', 'Bearer local') + .send({ title: 'Root', content: 'No escalation' }) + .expect(403); + }); + it('requires authentication for mutations and rejects invalid/scopeless credentials', async () => { + await request(app.getHttpServer()) + .post('/topics/pfpublict/posts') + .send({ content: 'Guest' }) + .expect(401); + await request(app.getHttpServer()) + .put('/topics/pfpublict/watch') + .send({}) + .expect(401); + await request(app.getHttpServer()) + .put('/posts/pfpublicp/reaction') + .send({ reaction: 'THUMBS_UP' }) + .expect(401); + await request(app.getHttpServer()) + .get('/public/categories') + .set('Authorization', 'Bearer invalid') + .expect(401); + await request(app.getHttpServer()) + .get('/public/categories') + .set('Authorization', 'Bearer machine') + .expect(403); + }); + it('supports member creation, replies, reactions, watch and read state while rejecting forbidden categories', async () => { + const created = await request(app.getHttpServer()) + .post('/topics') + .set('Authorization', 'Bearer member') + .send({ + parentTopicId: 'pfpublic', + title: 'New member thread', + content: '**Markdown** @test-member', + }) + .expect(201); + createdId = created.body.topic.id; + await request(app.getHttpServer()) + .post(`/topics/${createdId}/posts`) + .set('Authorization', 'Bearer member') + .send({ content: 'needle-in-reply' }) + .expect(201); + const matches = await request(app.getHttpServer()) + .get('/public/topics?search=needle-in-reply') + .expect(200); + expect( + matches.body.data.map((topic: { id: string }) => topic.id), + ).toContain(createdId); + await request(app.getHttpServer()) + .put(`/topics/${createdId}/watch`) + .set('Authorization', 'Bearer member') + .send({}) + .expect(200); + await request(app.getHttpServer()) + .put(`/topics/${createdId}/read-state`) + .set('Authorization', 'Bearer member') + .send({}) + .expect(200); + await request(app.getHttpServer()) + .put('/posts/pfpublicp/reaction') + .set('Authorization', 'Bearer member') + .send({ reaction: 'THUMBS_UP' }) + .expect(200); + await request(app.getHttpServer()) + .post('/topics') + .set('Authorization', 'Bearer member') + .send({ parentTopicId: 'pfcopilot', title: 'Denied', content: 'Denied' }) + .expect(403); + await request(app.getHttpServer()) + .post('/topics/pfcopilott/posts') + .set('Authorization', 'Bearer member') + .send({ content: 'Denied' }) + .expect(403); + }); + it('imports atomically, preserves challenge rows, reruns safely, and refuses changed targets/source', async () => { + const date = new Date('2026-01-01T00:00:00Z'); + const source: PublicImportSnapshot = { + categories: [ + { + CategoryID: 99001, + ParentCategoryID: -1, + Name: 'Import test', + UrlCode: 'import-test', + DisplayAs: 'Discussions', + Sort: 1, + PermissionCategoryID: 99001, + InsertUserID: 1, + DateInserted: date, + DateUpdated: date, + }, + ], + roles: [{ RoleID: 2, Name: 'Guest', Type: 'guest' }], + permissions: [ + { JunctionID: 99001, RoleID: 2, 'Vanilla.Discussions.View': 1 }, + ], + discussions: [ + { + DiscussionID: 99001, + CategoryID: 99001, + Name: 'Import thread', + Body: '**original**', + Format: 'Markdown', + InsertUserID: 1, + DateInserted: date, + DateUpdated: date, + }, + ], + comments: [], + users: [{ UserID: 1, Name: 'unknown' }], + memberships: [], + watches: [], + categoryWatches: [], + media: [], + }; + const plan = planPublicImport(source, new Map()); + const before = await challengeFingerprint(sql); + expect((await writePublicImport(sql, plan.entries, false)).inserted).toBe( + 0, + ); + expect((await writePublicImport(sql, plan.entries, true)).inserted).toBe( + plan.entries.length, + ); + expect((await writePublicImport(sql, plan.entries, true)).inserted).toBe(0); + expect(await challengeFingerprint(sql)).toEqual(before); + const changed = plan.entries.map((entry) => ({ + ...entry, + row: { ...entry.row }, + })); + changed.find((entry) => entry.type === 'starter')!.row.content = + 'Source changed'; + await expect(writePublicImport(sql, changed, true)).rejects.toThrow( + 'Provenance conflict', + ); + const postId = plan.entries.find((entry) => entry.type === 'starter')!.row + .id; + await db.post.update({ + where: { id: postId }, + data: { content: 'A real user edit' }, + }); + await expect(writePublicImport(sql, plan.entries, true)).rejects.toThrow( + 'Provenance conflict', + ); + expect((await db.post.findUnique({ where: { id: postId } }))?.content).toBe( + 'A real user edit', + ); + expect(await challengeFingerprint(sql)).toEqual(before); + }); +}); diff --git a/src/forums/public-forums.service.ts b/src/forums/public-forums.service.ts new file mode 100644 index 0000000..3a81d2d --- /dev/null +++ b/src/forums/public-forums.service.ts @@ -0,0 +1,247 @@ +import { ForbiddenException, Injectable } from '@nestjs/common'; +import { Prisma } from '../../prisma/generated/client'; +import { JwtUser } from '../auth/jwt.service'; +import { DbService } from '../db/db.service'; +import { + PublicForumCategoryDto, + PublicForumsQueryDto, +} from './dto/public-forums.dto'; +import { ForumsTopicSummaryPageDto } from './dto/forums-read.dto'; +import { buildForumsPrincipal, ForumsPrincipal } from './forums-access.types'; +import { ForumsModerationService } from './forums-moderation.service'; +import { ForumsReadQueryService } from './forums-read-query.service'; +import { ForumsReadService } from './forums-read.service'; +import { ForumsTopicContextService } from './forums-topic-context.service'; +import { ForumsAccessPolicyService } from './forums-access-policy.service'; + +/** Presents public categories and paginated threads using the existing topic/post engine. + * Visibility is checked before search, watches, aggregate counts, and pagination. + */ +@Injectable() +export class PublicForumsService { + /** @param db Forums database. @param reader Shared SQL summaries. @param reads Shared mapping/pagination. + * @param contexts Effective restrictions. @param policy Shared action policy. @param moderation Ban checks. + * @throws Never; dependencies are supplied by Nest. + */ + constructor( + private readonly db: DbService, + private readonly reader: ForumsReadQueryService, + private readonly reads: ForumsReadService, + private readonly contexts: ForumsTopicContextService, + private readonly policy: ForumsAccessPolicyService, + private readonly moderation: ForumsModerationService, + ) {} + + /** Loads category cards in Vanilla order with visible subtree statistics. + * @param user Optional validated identity. @param ip Trusted request IP. + * @returns Accessible categories; private empty containers are omitted. + * @throws ForbiddenException for bans; database errors propagate. + */ + async categories( + user?: JwtUser, + ip?: string, + ): Promise { + const principal = await this.principal(user, ip); + const metadata = await this.db.publicForumCategory.findMany({ + orderBy: { sortOrder: 'asc' }, + }); + const summaries = metadata.length + ? await this.reader.findTopicSummaryRows( + Prisma.sql`t.id IN (${Prisma.join(metadata.map((category) => category.topicId))}) AND t."deletedAt" IS NULL`, + principal.memberId, + ) + : []; + const candidates = await Promise.all( + metadata.map(async (category) => { + const context = await this.contexts.loadTopicContext( + category.topicId, + principal, + ); + const access = await this.policy.decideForTopic(principal, context); + const summary = summaries.find((row) => row.id === category.topicId); + if (!access.canView.allowed || !summary) return null; + return { + ...this.reads.mapTopicSummary(summary), + description: category.description, + displayAs: category.displayAs, + sortOrder: category.sortOrder, + topicsCount: 0, + canCreate: access.canCreateChildTopic.allowed, + }; + }), + ); + const visible: PublicForumCategoryDto[] = candidates.filter( + (category) => category !== null, + ); + if (!visible.length) return []; + const ids = visible.map((category) => category.id); + const threads = await this.visibleThreads(principal, ids); + const authors = threads.length + ? await this.db.post.findMany({ + where: { + topicId: { in: threads.map((thread) => thread.id) }, + deletedAt: null, + }, + select: { topicId: true, authorMemberId: true, authorHandle: true }, + distinct: ['topicId', 'authorMemberId'], + }) + : []; + // Roll up only authorized rows; a private child must never affect public counts or avatars. + for (const category of visible) { + const descendantIds = new Set([category.id]); + for (let changed = true; changed; ) { + changed = false; + for (const candidate of visible) { + if ( + candidate.parentTopicId && + descendantIds.has(candidate.parentTopicId) && + !descendantIds.has(candidate.id) + ) { + descendantIds.add(candidate.id); + changed = true; + } + } + } + const descendants = threads.filter( + (thread) => + !!thread.parentTopicId && descendantIds.has(thread.parentTopicId), + ); + category.topicsCount = descendants.length; + category.postsCount = descendants.reduce( + (sum, thread) => sum + thread.postsCount, + 0, + ); + const threadIds = new Set(descendants.map((thread) => thread.id)); + const participants = new Map( + authors + .filter((author) => threadIds.has(author.topicId)) + .map((author) => [ + author.authorMemberId, + { memberId: author.authorMemberId, handle: author.authorHandle }, + ]), + ); + category.participants = [...participants.values()].slice(0, 5); + category.participantsCount = participants.size; + const newest = [...descendants].sort( + (a, b) => + (b.latestActivityAt?.getTime() ?? 0) - + (a.latestActivityAt?.getTime() ?? 0), + )[0]; + category.latestActivity = newest + ? this.reads.mapTopicSummary(newest).latestActivity + : null; + category.unread = descendants.some((thread) => thread.unread); + } + return visible.filter( + (category) => + category.displayAs !== 'Categories' || + visible.some((child) => child.parentTopicId === category.id), + ); + } + + /** Lists visible threads, or explicitly watched categories and threads. + * @param query Validated search, category, sort, watch and page inputs. + * @param user Optional validated identity. @param ip Trusted request IP. + * @returns Page whose totals disclose only authorized content. + * @throws ForbiddenException for bans or inaccessible categories; database failures propagate. + */ + async topics( + query: PublicForumsQueryDto, + user?: JwtUser, + ip?: string, + ): Promise { + const principal = await this.principal(user, ip); + const categories = await this.categories(user, ip); + if ( + query.categoryId && + !categories.some((category) => category.id === query.categoryId) + ) { + throw new ForbiddenException('Category is unavailable.'); + } + const ids = categories + .filter( + (category) => !query.categoryId || category.id === query.categoryId, + ) + .map((category) => category.id); + if (!ids.length) return this.reads.paginateRows([], query); + let rows = await this.visibleThreads(principal, ids); + if (query.watching === 'true') { + const categoryRows = await this.reader.findTopicSummaryRows( + Prisma.sql`t.id IN (${Prisma.join(ids)}) AND t."deletedAt" IS NULL`, + principal.memberId, + ); + rows = [...rows, ...categoryRows].filter((row) => row.watching); + } + const search = query.search?.trim().toLowerCase(); + if (search && rows.length) { + const matches = await this.db.post.findMany({ + where: { + topicId: { in: rows.map((row) => row.id) }, + deletedAt: null, + content: { contains: search, mode: 'insensitive' }, + }, + select: { topicId: true }, + distinct: ['topicId'], + }); + const matchingIds = new Set(matches.map((post) => post.topicId)); + rows = rows.filter( + (row) => + row.title.toLowerCase().includes(search) || matchingIds.has(row.id), + ); + } + if (query.sort === 'oldest' || query.sort === 'recent') { + rows.sort( + (a, b) => + (a.createdAt.getTime() - b.createdAt.getTime()) * + (query.sort === 'oldest' ? 1 : -1), + ); + } + return this.reads.paginateRows(rows, query); + } + + /** Reads thread candidates only beneath visible categories, then checks narrower topic restrictions. + * @param principal Reader identity. @param categoryIds Authorized category IDs. + * @returns Authorized summaries ordered by latest activity. @throws Database failures. + */ + private async visibleThreads( + principal: ForumsPrincipal, + categoryIds: string[], + ) { + const rows = await this.reader.findTopicSummaryRows( + Prisma.sql` + t."parentTopicId" IN (${Prisma.join(categoryIds)}) AND t."challengeId" IS NULL AND t."deletedAt" IS NULL + AND NOT EXISTS (SELECT 1 FROM "forums"."PublicForumCategory" category WHERE category."topicId" = t.id) + AND NOT EXISTS (SELECT 1 FROM "TopicClosure" c JOIN "Topic" a ON a.id = c."ancestorTopicId" + WHERE c."descendantTopicId" = t.id AND a."deletedAt" IS NOT NULL)`, + principal.memberId, + ); + // The parent ACL was evaluated above. The only additional restrictions a thread can add are role/challenge values. + return this.reads.filterVisibleRows(principal, rows, (row) => ({ + challengeId: row.challengeId, + roleName: row.roleName, + hasRestrictionConflict: false, + })); + } + + /** Builds an anonymous/validated reader and enforces member/IP bans before querying content. + * @param user Optional JWT. @param ip Trusted IP. @returns Principal. @throws ForbiddenException for bans. + */ + private async principal( + user?: JwtUser, + ip?: string, + ): Promise { + const principal = buildForumsPrincipal(user) ?? { + memberId: null, + roles: [], + scopes: [], + isAdmin: false, + isMachine: false, + }; + const access = await this.moderation.decideForRequestActorBan( + principal, + ip, + ); + if (!access.allowed) throw new ForbiddenException(access.reason); + return principal; + } +} diff --git a/src/forums/topics.controller.ts b/src/forums/topics.controller.ts index 9374b78..c572b67 100644 --- a/src/forums/topics.controller.ts +++ b/src/forums/topics.controller.ts @@ -25,6 +25,7 @@ import { AUTHENTICATED_USER_ROLE, Roles, } from '../auth/decorators/roles.decorator'; +import { OptionalRead } from '../auth/decorators/optional-read.decorator'; import { ClientIp } from '../auth/decorators/client-ip.decorator'; import { Scopes } from '../auth/decorators/scopes.decorator'; import { CurrentUser } from '../auth/decorators/user.decorator'; @@ -66,7 +67,8 @@ import { ForumsReadService } from './forums-read.service'; * read-state routes. * * Routes are mounted under the global `/v6/forums` prefix. Topic list and - * detail reads are protected by `read:forums-topics`; embedded posts in topic + * detail reads accept guests after visibility checks; authenticated M2M reads + * require `read:forums-topics`. Embedded posts in topic * detail remain on the topics read surface rather than `read:forums-posts`. * Trusted client-IP context is resolved at the request boundary and forwarded * to services as a string for runtime ban checks. @@ -138,12 +140,13 @@ export class TopicsController { * @throws UnauthorizedException when no authenticated read caller is present. */ @Get() + @OptionalRead() @Roles(AUTHENTICATED_USER_ROLE) @Scopes(FORUMS_SCOPE_READ_TOPICS) @ApiOperation({ summary: 'List general forum root topics', description: - 'Requires an authenticated member token or `read:forums-topics`. Active member bans and trusted exact-IP bans return 403 before visibility checks. Returns non-challenge root topics the caller can see, including role-restricted general topics after centralized forums policy filtering.', + 'Accepts guests for unrestricted public topics; M2M requires `read:forums-topics`. Active member bans and trusted exact-IP bans return 403 before visibility checks. Returns non-challenge root topics the caller can see, including role-restricted general topics after centralized forums policy filtering.', }) @ApiQuery({ name: 'page', @@ -239,6 +242,7 @@ export class TopicsController { * @throws NotFoundException when the parent topic is missing or hidden. */ @Get(':topicId/children') + @OptionalRead() @Roles(AUTHENTICATED_USER_ROLE) @Scopes(FORUMS_SCOPE_READ_TOPICS) @ApiOperation({ @@ -277,6 +281,7 @@ export class TopicsController { * @throws NotFoundException when the topic is missing or hidden. */ @Get(':topicId') + @OptionalRead() @Roles(AUTHENTICATED_USER_ROLE) @Scopes(FORUMS_SCOPE_READ_TOPICS) @ApiOperation({ diff --git a/src/scripts/public-vanilla-import.ts b/src/scripts/public-vanilla-import.ts new file mode 100644 index 0000000..6a7ecae --- /dev/null +++ b/src/scripts/public-vanilla-import.ts @@ -0,0 +1,92 @@ +import { writeFile } from 'fs/promises'; +import { Client } from 'pg'; +import { + planPublicImport, + readPublicVanilla, + writePublicImport, +} from '../forums/import/public-vanilla-import'; + +/** Runs an explicit-source, explicit-target public import; dry-run unless --apply is supplied. + * Required env: VANILLA_DB_URL, FORUMS_DATABASE_URL, MEMBER_DB_URL, PUBLIC_IMPORT_TARGET_HOST. + * @returns Completion after an owner-only report is written. @throws Configuration, audit, SQL and file errors. + */ +async function main(): Promise { + process.env.TZ = 'UTC'; + const { + VANILLA_DB_URL, + FORUMS_DATABASE_URL, + MEMBER_DB_URL, + PUBLIC_IMPORT_TARGET_HOST, + } = process.env; + if ( + !VANILLA_DB_URL || + !FORUMS_DATABASE_URL || + !MEMBER_DB_URL || + !PUBLIC_IMPORT_TARGET_HOST + ) + throw new Error( + 'Explicit Vanilla, forums, members URLs and expected target host are required', + ); + if (new URL(FORUMS_DATABASE_URL).hostname !== PUBLIC_IMPORT_TARGET_HOST) + throw new Error('Target host does not match the explicit migration target'); + if (new URL(MEMBER_DB_URL).hostname !== PUBLIC_IMPORT_TARGET_HOST) + throw new Error('Member mapping must use the target environment'); + const args = process.argv.slice(2); + if (args.some((arg) => arg !== '--apply' && !arg.startsWith('--report='))) + throw new Error( + 'Usage: public-vanilla-import [--apply] [--report=path.json]', + ); + const reportPath = + args + .find((arg) => arg.startsWith('--report=')) + ?.slice('--report='.length) ?? 'public-forums-import-report.json'; + const source = await readPublicVanilla(VANILLA_DB_URL); + const membersDb = new Client({ connectionString: MEMBER_DB_URL }); + const target = new Client({ connectionString: FORUMS_DATABASE_URL }); + try { + await membersDb.connect(); + await target.connect(); + const handles = [ + ...new Set( + source.users.map((user) => String(user.Name).trim().toLowerCase()), + ), + ]; + const rows = ( + await membersDb.query<{ userId: string; handle: string }>( + 'SELECT "userId"::text,handle FROM members.member WHERE "handleLower"=ANY($1::text[])', + [handles], + ) + ).rows; + const members = new Map( + rows.map((member) => [member.handle.toLowerCase(), member]), + ); + const plan = planPublicImport(source, members); + const result = await writePublicImport( + target, + plan.entries, + args.includes('--apply'), + ); + const report = { + ...result, + source: { + categories: source.categories.length, + discussions: source.discussions.length, + comments: source.comments.length, + }, + unmappedAuthorIds: plan.unmappedAuthorIds, + unmappedStates: plan.unmappedStates, + generatedAt: new Date().toISOString(), + }; + await writeFile(reportPath, JSON.stringify(report, null, 2) + '\n', { + mode: 0o600, + }); + console.log(JSON.stringify({ ...result, reportPath })); + } finally { + await membersDb.end(); + await target.end(); + } +} +void main().catch((error: unknown) => { + console.error(error instanceof Error ? error.message : 'Import failed'); + process.exitCode = 1; +}); From 90f244ac012b2369eb94fed78e1d0a359fef3385 Mon Sep 17 00:00:00 2001 From: jmgasper Date: Wed, 7 Oct 2026 09:10:36 +1100 Subject: [PATCH 2/6] fix: prevent shared caching of identity-dependent forum reads --- docs/public-forums.md | 1 + src/forums/public-forums.controller.ts | 4 +++- src/forums/public-forums.integration.spec.ts | 1 + src/forums/topics.controller.ts | 5 +++++ 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/docs/public-forums.md b/docs/public-forums.md index 4f2da52..fdfac40 100644 --- a/docs/public-forums.md +++ b/docs/public-forums.md @@ -11,6 +11,7 @@ The public catalog is a projection over that engine, not a second post store. - `GET /v6/forums/public/topics` accepts `categoryId`, `search`, `watching=true`, `sort=active|recent|oldest`, `page` and `perPage` (maximum 100). Search includes non-deleted post content. Visibility is applied before totals and pagination. + Identity-dependent read responses send `Cache-Control: private, no-store`. - Existing general topic list, children and detail GET routes accept guests. Challenge-specific lists still require authentication, and challenge visibility still requires the existing challenge/resource checks. Supplied invalid tokens diff --git a/src/forums/public-forums.controller.ts b/src/forums/public-forums.controller.ts index c772ea4..0cd2a86 100644 --- a/src/forums/public-forums.controller.ts +++ b/src/forums/public-forums.controller.ts @@ -1,4 +1,4 @@ -import { Controller, Get, Query } from '@nestjs/common'; +import { Controller, Get, Header, Query } from '@nestjs/common'; import { ApiOperation, ApiTags } from '@nestjs/swagger'; import { OptionalRead } from '../auth/decorators/optional-read.decorator'; import { @@ -24,6 +24,7 @@ export class PublicForumsController { constructor(private readonly service: PublicForumsService) {} /** @param user Optional validated identity. @param ip Trusted IP. @returns Visible category cards. @throws Ban/database errors. */ + @Header('Cache-Control', 'private, no-store') @Get('categories') @ApiOperation({ summary: @@ -36,6 +37,7 @@ export class PublicForumsController { /** @param query Validated filters/page. @param user Optional identity. @param ip Trusted IP. * @returns Visible thread page. @throws Access, validation and database errors. */ + @Header('Cache-Control', 'private, no-store') @Get('topics') @ApiOperation({ summary: 'Search public threads or list watched content' }) topics( diff --git a/src/forums/public-forums.integration.spec.ts b/src/forums/public-forums.integration.spec.ts index 0ed5adb..20171fd 100644 --- a/src/forums/public-forums.integration.spec.ts +++ b/src/forums/public-forums.integration.spec.ts @@ -198,6 +198,7 @@ integration('public forums PostgreSQL/HTTP access boundaries', () => { it('allows anonymous public reads but hides roles, private parents, search results and identities', async () => { const categories = await request(app.getHttpServer()) .get('/public/categories') + .expect('Cache-Control', 'private, no-store') .expect(200); expect( categories.body.map((category: { id: string }) => category.id), diff --git a/src/forums/topics.controller.ts b/src/forums/topics.controller.ts index c572b67..3948366 100644 --- a/src/forums/topics.controller.ts +++ b/src/forums/topics.controller.ts @@ -3,6 +3,7 @@ import { Controller, Delete, Get, + Header, Param, Patch, Post, @@ -139,6 +140,7 @@ export class TopicsController { * @returns Paginated visible general root topics. * @throws UnauthorizedException when no authenticated read caller is present. */ + @Header('Cache-Control', 'private, no-store') @Get() @OptionalRead() @Roles(AUTHENTICATED_USER_ROLE) @@ -186,6 +188,7 @@ export class TopicsController { * @throws ForbiddenException when challenge visibility is denied. * @throws NotFoundException when the challenge is missing or hidden by policy. */ + @Header('Cache-Control', 'private, no-store') @Get('challenges/:challengeId') @Roles(AUTHENTICATED_USER_ROLE) @Scopes(FORUMS_SCOPE_READ_TOPICS) @@ -241,6 +244,7 @@ export class TopicsController { * @throws ForbiddenException when parent topic visibility is denied. * @throws NotFoundException when the parent topic is missing or hidden. */ + @Header('Cache-Control', 'private, no-store') @Get(':topicId/children') @OptionalRead() @Roles(AUTHENTICATED_USER_ROLE) @@ -280,6 +284,7 @@ export class TopicsController { * @throws ForbiddenException when topic visibility is denied. * @throws NotFoundException when the topic is missing or hidden. */ + @Header('Cache-Control', 'private, no-store') @Get(':topicId') @OptionalRead() @Roles(AUTHENTICATED_USER_ROLE) From 7bff9ff60cb4a81829c3dfaa35979d7b171d512d Mon Sep 17 00:00:00 2001 From: jmgasper Date: Wed, 7 Oct 2026 09:20:22 +1100 Subject: [PATCH 3/6] docs: record verified dev migration and access checks --- docs/public-forums-dev-migration.md | 84 +++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 docs/public-forums-dev-migration.md diff --git a/docs/public-forums-dev-migration.md b/docs/public-forums-dev-migration.md new file mode 100644 index 0000000..1d776c4 --- /dev/null +++ b/docs/public-forums-dev-migration.md @@ -0,0 +1,84 @@ +# Dev public-forums migration verification + +Executed on 7 October 2026 (Australia/Hobart), using production Vanilla as a +read-only source and the AWS dev forums/Member databases as the target. +Production databases were not written to. + +The API was deployed before importing restricted categories. The dev service +`forums-api-v6` is on task revision 18, using code commit `90f244a` and image tag +`public-forums-90f244a`. ECS reached a stable deployment and live public reads +return `Cache-Control: private, no-store`. + +## Imported data + +| Data | Rows | +| --- | ---: | +| Categories | 57 | +| Discussion threads | 1,355 | +| Starter posts and comments | 3,756 | +| Closure relationships | 4,295 | +| Mapped forum-only role memberships | 139 | +| Watches | 619 | +| Read states | 11,162 | +| Total imported data rows | 21,440 | + +The immediate second apply found all 21,440 rows and inserted **zero** rows. +The dry-run, apply and rerun plan digest was: + +`aeccd893e9d6486b0750dea69d9e00a51374772c309d3c9da08f342155dcab5b` + +There were 1,079 source author IDs without a matching dev member. Their original +handles and content were preserved using non-login legacy IDs. 16,013 source +state records belonged to members absent from dev and were omitted. Production +mapping must be rerun against production Members; dev IDs must not be reused. +No native Vanilla media or reactions required copying. All 99 Filestack links +in the source post bodies were preserved unchanged. + +## Existing challenge data + +These counts and SHA-256 fingerprints were identical in dry-run, before and +after apply, and after the no-op rerun: + +| Table | Rows | SHA-256 | +| --- | ---: | --- | +| Topic | 6,437 | `3dae055b315e049750e2aff4fbbed47cdfcd24f009792235709d950a3a012f83` | +| Post | 6,484 | `c0967e56431ddddbcdd832ad7e6f2cfa70274c9557781dcffdfd9dd4b394a5d3` | +| TopicWatch | 69 | `1e9c7c8269d3c4e6f6ecfefe7617e845f0f7b93f41f7c89e0e4d90ef90e84134` | +| TopicReadState | 6,522 | `ac0b498e58c2a643303783e8ba12d65a028487b474062a9c55bffcdf164ce21b` | +| TopicClosure | 6,437 | `2d1c882cb7691562120e1222254ecbf66500eb7f0924a216d951d7abc4079a9c` | + +## Live dev verification + +Read-only probes used short-lived dev tokens for a synthetic QA principal; no +member roles, posts, watches or read states were created by the probes. Category +IDs and topic totals matched an independently computed source ACL projection: + +| Reader | Visible categories | Visible threads | +| --- | ---: | ---: | +| Guest | 30 | 1,111 | +| Member | 48 | 1,286 | +| Copilot | 52 | 1,323 | +| Reviewer | 49 | 1,288 | + +Copilot and Reviewer Discussions returned 403 to guests, ordinary members and +the other role, and 200 to the required role. Invalid tokens returned 401. +Anonymous Watching returned zero results. Measured catalog requests completed +in approximately 1.4–3.5 seconds including network latency from the test host. + +The API lint/build, production Docker build and 167 tests passed. Local real +PostgreSQL/HTTP tests cover posting, replies, reactions, watches, nested private +membership, migration conflicts and reruns without sending notifications. + +## Remaining production gates + +The [UI PR](https://github.com/topcoder-platform/platform-ui/pull/2444) contains +the new app and passed lint, build and focused regression tests. No browser was +connected for live desktop/mobile Figma comparison or real login/attachment +smoke tests. Complete those checks and accept the dev data/role mappings before +production cutover. Follow [the migration runbook](public-forums.md); production +has not been migrated. + +Full owner-readable reports were retained outside the repository at +`/tmp/public-forums-dev-{dry-run,applied,rerun}.json` and +`/tmp/public-forums-live-access.json`. No credentials, post contents or member +lists are included in this document. From 94db0c2f61020d0a11ca73b771ff45c3cd0be919 Mon Sep 17 00:00:00 2001 From: jmgasper Date: Wed, 7 Oct 2026 09:24:42 +1100 Subject: [PATCH 4/6] docs: clarify optional authentication for public forum reads --- src/forums/topics.controller.ts | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/forums/topics.controller.ts b/src/forums/topics.controller.ts index 3948366..d60c255 100644 --- a/src/forums/topics.controller.ts +++ b/src/forums/topics.controller.ts @@ -135,10 +135,10 @@ export class TopicsController { * Lists visible non-challenge root topics. * * @param query Pagination query parameters. - * @param user Authenticated token payload for the read caller. + * @param user Optional validated token payload; guests remain subject to inherited visibility policy. * @param trustedClientIp Optional trusted client IP resolved at the HTTP boundary. * @returns Paginated visible general root topics. - * @throws UnauthorizedException when no authenticated read caller is present. + * @throws UnauthorizedException when a supplied bearer token is invalid. */ @Header('Cache-Control', 'private, no-store') @Get() @@ -166,7 +166,7 @@ export class TopicsController { type: ForumsTopicSummaryPageDto, }) @ApiBadRequestResponse({ description: 'Invalid pagination query.' }) - @ApiUnauthorizedResponse({ description: 'Authenticated token required.' }) + @ApiUnauthorizedResponse({ description: 'A supplied bearer token is invalid.' }) @ApiForbiddenResponse({ description: 'Active forums ban.' }) listGeneralRootTopics( @Query() query: ForumsTopicListQueryDto, @@ -237,10 +237,10 @@ export class TopicsController { * Lists visible direct child topics for a parent topic. * * @param topicId Parent topic id from the route. - * @param user Authenticated token payload for the read caller. + * @param user Optional validated token payload; guests remain subject to inherited visibility policy. * @param trustedClientIp Optional trusted client IP resolved at the HTTP boundary. * @returns Ordered visible direct child topic summaries. - * @throws UnauthorizedException when no authenticated read caller is present. + * @throws UnauthorizedException when a supplied bearer token is invalid. * @throws ForbiddenException when parent topic visibility is denied. * @throws NotFoundException when the parent topic is missing or hidden. */ @@ -252,7 +252,7 @@ export class TopicsController { @ApiOperation({ summary: 'List direct child forum topics', description: - 'Requires an authenticated member token or `read:forums-topics`. Active member bans and trusted exact-IP bans return 403 before parent visibility checks. The parent topic must be visible first, then child rows are filtered through centralized forums policy using inherited parent restrictions plus each child direct restriction. Locked topics remain readable.', + 'Accepts guest reads when inherited visibility policy permits; M2M tokens require `read:forums-topics`. Active member bans and trusted exact-IP bans return 403 before parent visibility checks. The parent topic must be visible first, then child rows are filtered through centralized forums policy using inherited parent restrictions plus each child direct restriction. Locked topics remain readable.', }) @ApiParam({ name: 'topicId', description: 'Parent topic id.' }) @ApiResponse({ @@ -260,7 +260,7 @@ export class TopicsController { description: 'Visible direct child topics.', type: [ForumsTopicSummaryDto], }) - @ApiUnauthorizedResponse({ description: 'Authenticated token required.' }) + @ApiUnauthorizedResponse({ description: 'A supplied bearer token is invalid.' }) @ApiForbiddenResponse({ description: 'Active forums ban or parent topic visibility denied.', }) @@ -277,10 +277,10 @@ export class TopicsController { * Loads topic detail with embedded posts. * * @param topicId Topic id from the route. - * @param user Authenticated token payload for the read caller. + * @param user Optional validated token payload; guests remain subject to inherited visibility policy. * @param trustedClientIp Optional trusted client IP resolved at the HTTP boundary. * @returns Topic detail with nested posts and replies. - * @throws UnauthorizedException when no authenticated read caller is present. + * @throws UnauthorizedException when a supplied bearer token is invalid. * @throws ForbiddenException when topic visibility is denied. * @throws NotFoundException when the topic is missing or hidden. */ @@ -292,7 +292,7 @@ export class TopicsController { @ApiOperation({ summary: 'Get forum topic detail', description: - 'Requires an authenticated member token or `read:forums-topics`. Active member bans and trusted exact-IP bans return 403 before topic visibility checks. Returns the topic summary and embedded post tree under the topics read scope; locked topics remain readable. `read:forums-posts` remains reserved for future post-specific reads.', + 'Accepts guest reads when inherited visibility policy permits; M2M tokens require `read:forums-topics`. Active member bans and trusted exact-IP bans return 403 before topic visibility checks. Returns the topic summary and embedded post tree under the topics read scope; locked topics remain readable. `read:forums-posts` remains reserved for future post-specific reads.', }) @ApiParam({ name: 'topicId', description: 'Topic id.' }) @ApiResponse({ @@ -300,7 +300,7 @@ export class TopicsController { description: 'Topic detail with embedded post tree.', type: ForumsTopicDetailDto, }) - @ApiUnauthorizedResponse({ description: 'Authenticated token required.' }) + @ApiUnauthorizedResponse({ description: 'A supplied bearer token is invalid.' }) @ApiForbiddenResponse({ description: 'Active forums ban or topic visibility denied.', }) From 866d18ff35ec80345181b56d634b7a7e2972cfa2 Mon Sep 17 00:00:00 2001 From: jmgasper Date: Wed, 7 Oct 2026 10:00:39 +1100 Subject: [PATCH 5/6] docs: record signed-in browser verification for dev forums --- docs/public-forums-dev-migration.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/docs/public-forums-dev-migration.md b/docs/public-forums-dev-migration.md index 1d776c4..8872448 100644 --- a/docs/public-forums-dev-migration.md +++ b/docs/public-forums-dev-migration.md @@ -72,11 +72,15 @@ membership, migration conflicts and reruns without sending notifications. ## Remaining production gates The [UI PR](https://github.com/topcoder-platform/platform-ui/pull/2444) contains -the new app and passed lint, build and focused regression tests. No browser was -connected for live desktop/mobile Figma comparison or real login/attachment -smoke tests. Complete those checks and accept the dev data/role mappings before -production cutover. Follow [the migration runbook](public-forums.md); production -has not been migrated. +the new app and passed lint, dev/prod builds and 58 focused regression tests. +Connected Chrome verification covered desktop/mobile Figma layouts, category +navigation, search/pagination, guest login return URLs, signed-in composers, +live member suggestions and Markdown preview. No test posts were submitted to +migrated discussions. A real attachment upload still awaits the browser +extension's file-URL access setting; automated upload tests pass. Accept the +dev data/role mappings and finish attachment verification before production +cutover. Follow [the migration runbook](public-forums.md); production has not +been migrated. Full owner-readable reports were retained outside the repository at `/tmp/public-forums-dev-{dry-run,applied,rerun}.json` and From 85400113b15ddc4ee7cf0eeb6773ec92102d562c Mon Sep 17 00:00:00 2001 From: jmgasper Date: Wed, 7 Oct 2026 10:04:02 +1100 Subject: [PATCH 6/6] docs: record successful live forum attachment upload --- docs/public-forums-dev-migration.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/docs/public-forums-dev-migration.md b/docs/public-forums-dev-migration.md index 8872448..7e608f6 100644 --- a/docs/public-forums-dev-migration.md +++ b/docs/public-forums-dev-migration.md @@ -75,12 +75,11 @@ The [UI PR](https://github.com/topcoder-platform/platform-ui/pull/2444) contains the new app and passed lint, dev/prod builds and 58 focused regression tests. Connected Chrome verification covered desktop/mobile Figma layouts, category navigation, search/pagination, guest login return URLs, signed-in composers, -live member suggestions and Markdown preview. No test posts were submitted to -migrated discussions. A real attachment upload still awaits the browser -extension's file-URL access setting; automated upload tests pass. Accept the -dev data/role mappings and finish attachment verification before production -cutover. Follow [the migration runbook](public-forums.md); production has not -been migrated. +live member suggestions and Markdown preview. The approved generated text +attachment uploaded to Filestack, inserted an HTTPS Markdown link, and rendered +in preview. No test posts were submitted to migrated discussions. Accept the dev +data/role mappings before production cutover. Follow +[the migration runbook](public-forums.md); production has not been migrated. Full owner-readable reports were retained outside the repository at `/tmp/public-forums-dev-{dry-run,applied,rerun}.json` and