diff --git a/README.md b/README.md index df1c3af..1c9c31a 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,9 @@ and post-commit watch notifications. - `PATCH /v6/forums/topics/:topicId` - `DELETE /v6/forums/topics/:topicId` - `POST /v6/forums/topics/:topicId/posts` +- `GET /v6/forums/challenges/:challengeId/watch` +- `PUT /v6/forums/challenges/:challengeId/watch` +- `DELETE /v6/forums/challenges/:challengeId/watch` - `PUT /v6/forums/topics/:topicId/watch` - `DELETE /v6/forums/topics/:topicId/watch` - `PUT /v6/forums/topics/:topicId/read-state` @@ -32,7 +35,7 @@ and post-commit watch notifications. - `PUT /v6/forums/moderation/ip-bans/:ipAddress` - `DELETE /v6/forums/moderation/ip-bans/:ipAddress` -This service includes the forums schema, Prisma client export, topic read workflows, transactional command-side workflows, per-member post reactions, centralized forums authorization and runtime moderation for topics, posts, watches, and explicit read-state updates, plus best-effort watch notification publishing for new posts and successful allowed child-topic starter posts. The policy resolves inherited topic restrictions, challenge access, resource-role/copilot elevation, role matching, ownership, scoped M2M write access, and M2M on-behalf target-member visibility before writes, reads, or notification delivery. Runtime moderation enforces active member bans, trusted exact-IP bans for human request traffic, and locked-topic mutation rules. +This service includes the forums schema, Prisma client export, topic read workflows, transactional command-side workflows, per-member post reactions, centralized forums authorization and runtime moderation for topics, posts, watches, and explicit read-state updates, plus best-effort watch notification publishing for new posts and successful allowed child-topic and top-level challenge starter posts. The policy resolves inherited topic restrictions, challenge access, resource-role/copilot elevation, role matching, ownership, scoped M2M write access, and M2M on-behalf target-member visibility before writes, reads, or notification delivery. Runtime moderation enforces active member bans, trusted exact-IP bans for human request traffic, and locked-topic mutation rules. Topic reads are exposed under `read:forums-topics`. `GET /v6/forums/topics` returns visible non-challenge root topics; `GET /v6/forums/topics/challenges/:challengeId` checks base challenge visibility before returning visible challenge roots; `GET /v6/forums/topics/:topicId/children` requires parent visibility before filtering direct children; and `GET /v6/forums/topics/:topicId` returns topic detail with an embedded post tree. Active member bans and trusted exact-IP bans return 403 before read policy or query work. Topic summaries include `locked`, `lockedBy`, and `lockedAt`; `lockedBy` and `lockedAt` may be null for imported legacy locked topics. Locked topics remain readable for callers that otherwise pass moderation and visibility checks. Detail embeds posts under `read:forums-topics`, orders every top-level and nested sibling list chronologically from oldest to newest, keeps deleted post placeholders with null content, counts only non-deleted posts, derives unread state from `TopicReadState.lastReadAt`, and includes each post's current challenge-resource `authorIsCopilot` projection, shared `thumbsUpCount`/`thumbsDownCount`, and the authenticated member's nullable `viewerReaction`. `read:forums-posts` remains reserved for future post-specific read APIs in v1. @@ -46,9 +49,31 @@ Moderation management is exposed under `/v6/forums/moderation`. Human callers mu Successful `POST /v6/forums/topics/:topicId/posts` commands publish a best-effort watch notification after the post transaction commits. Successful -allowed child-topic `POST /v6/forums/topics` commands publish the same -notification for the created starter post. Top-level topic creation, updates, -deletes, watch mutations, and mark-read commands do not publish notifications. +allowed child-topic and top-level challenge `POST /v6/forums/topics` commands +publish the same notification for the created starter post. General top-level +topic creation, updates, deletes, watch mutations, and mark-read commands do not +publish notifications. + +Challenge forums are watched by default for members with a Submitter or Copilot +resource assignment. Defaults are resolved from current Resources data, so new +registrations and copilot assignments take effect without visiting the forum, +including before its first topic exists. The challenge watch routes return +`{ challengeId, memberId, watching }` for the authenticated human member; PUT and +DELETE persist explicit preferences. DELETE stores an opt-out so later reads or +resource changes cannot silently resubscribe the member. Topic list/detail reads +inherit this preference when there is no explicit topic choice. Topic PUT stores +`watching: true`; challenge-topic DELETE stores `watching: false` so a later forum +re-watch cannot resubscribe that topic. Explicit topic watches survive forum +opt-out, and explicit topic opt-outs suppress emails even while the forum is +watched. Announcements follow the same rules. New challenge topics, including +those created by the member, inherit the current forum preference. Challenge-wide watchers receive new topic and post emails, +subject to the same topic visibility, ban, author exclusion, and deduplication +rules as topic watchers. Deploy the `20261016000000_challenge_forum_watch` database +migration and `20261020000000_topic_watch_preferences` before the API and the +Opportunities watch control. The latter preserves existing topic watch rows as +opt-ins; historical rows have no provenance distinguishing manual from author +watches. New challenge-topic creation no longer seeds an author watch. General +forum author watches and unwatch behavior are unchanged. Recipient resolution expands watches from the created content topic through all ancestor topics using `TopicClosure`, dedupes by member id, excludes the @@ -64,10 +89,12 @@ the content write. For challenge-scoped notifications, the publisher fetches the effective challenge from `GET /v6/challenges/:challengeId` using the configured M2M credentials and -includes its `name` as `data.challengeTitle`. This lookup runs once per outgoing -notification, after recipient authorization, and uses a five-second HTTP timeout. -Lookup failures are logged and the email still publishes with `challengeId` but -without `challengeTitle`. +includes its name and environment-correct Opportunities link. The established +`challengeName` / `challengeURL` SendGrid keys and the `challengeTitle` / +`challengeUrl` compatibility aliases carry identical values. This lookup runs +once per outgoing notification, after recipient authorization, and uses a +five-second HTTP timeout. Name and URL failures are logged independently, and +the email still publishes with every field that could be resolved. The [forum notification HTML template](docs/email-templates/forum-notification.html) adapts the Topcoder support email design for these notifications. Use the subject @@ -81,7 +108,10 @@ Template variables come from the event payload's `data` object: | Field | Value | | --- | --- | | `challengeId` | Effective challenge ID; omitted when absent. The template hides this row when absent. | -| `challengeTitle` | Challenge API `name`; omitted for non-challenge topics or failed lookups. The template hides this row when absent. | +| `challengeName` | Challenge API `name`; omitted for non-challenge topics or failed lookups. The template hides this row when absent. | +| `challengeURL` | Public `/opportunities/challenge/:challengeId` URL. | +| `challengeTitle` | Compatibility alias of `challengeName`. | +| `challengeUrl` | Compatibility alias of `challengeURL`. | | `topicId` | Created content's topic ID. | | `topicTitle` | Topic title. | | `postContent` | Persisted post content, or an empty string when null. | @@ -90,13 +120,14 @@ Template variables come from the event payload's `data` object: Use the [sample template data](docs/email-templates/forum-notification.sample.json) in SendGrid's preview editor; it contains only the `data` fields, without the -event envelope. Remove `challengeId` and `challengeTitle` to preview a non-challenge notification. +event envelope. Remove all challenge fields to preview a non-challenge notification. User content uses escaped double-brace substitutions and is displayed as text, with line breaks preserved where the email client supports `white-space: pre-wrap`; Markdown and HTML are not rendered. See SendGrid's [Handlebars documentation](https://www.twilio.com/docs/sendgrid/for-developers/sending-email/using-handlebars) -for substitution and conditional syntax. The current payload has no discussion -URL, so the template currently displays the topic ID without a discussion link. +for substitution and conditional syntax. The challenge name links to the new +Opportunities challenge-details route; the topic ID remains unlinked because the +page does not yet expose a stable topic-detail URL. ## Environment @@ -116,6 +147,7 @@ SENDGRID_NOTIFICATION_TEMPLATE="sendgrid-template-id" BUSAPI_URL="https://api.topcoder-dev.com/v6" BUS_API_URL="https://api.topcoder-dev.com/v6/bus/events" TOPCODER_API_URL_BASE="https://api.topcoder-dev.com" +TOPCODER_URL="https://www.topcoder-dev.com" CHALLENGE_API_URL="https://api.topcoder-dev.com/v6/challenges" KAFKA_ERROR_TOPIC="common.error.reporting" AUTH0_URL="https://auth.topcoder-dev.com/" @@ -136,7 +168,7 @@ PORT=3000 `VANILLA_DB_URL` is used only by the standalone Vanilla import CLI for legacy MySQL reads. The runtime HTTP service does not connect to Vanilla. `AUTH_SECRET` is required; the service fails during startup when it is omitted. `SENDGRID_NOTIFICATION_TEMPLATE` enables forum watch notification emails. When omitted, notification publishing is skipped and content writes still succeed. -`CHALLENGE_API_URL` optionally configures the challenges collection endpoint for notification titles. When omitted, it defaults to `${TOPCODER_API_URL_BASE}/v6/challenges`. The lookup uses the same Auth0 configuration and `M2M_CLIENT_ID` / `M2M_CLIENT_SECRET` as outbound bus publishing; the M2M client must have challenge read access. +`CHALLENGE_API_URL` optionally configures the challenges collection endpoint for notification titles. When omitted, it defaults to `${TOPCODER_API_URL_BASE}/v6/challenges`. The lookup uses the same Auth0 configuration and `M2M_CLIENT_ID` / `M2M_CLIENT_SECRET` as outbound bus publishing; the M2M client must have challenge read access. `TOPCODER_URL` optionally configures the public web origin used by challenge links. When omitted, the service derives it from `TOPCODER_API_URL_BASE` by replacing the conventional `api.` hostname prefix with `www.`. `BUSAPI_URL` configures the shared Bus API v6 base for `external.action.email`; the backwards-compatible `BUS_API_URL` alias may contain either that base or the complete `/v6/bus/events` endpoint. Both values are normalized to the `/v6` base because `tc-bus-api-wrapper` appends `/bus/events`, and conflicting aliases or legacy `/eventBus` and `/v5` values are rejected. When neither alias is set, the service derives the v6 base from `TOPCODER_API_URL_BASE`. `KAFKA_ERROR_TOPIC`, `AUTH0_URL`, `AUTH0_AUDIENCE`, `TOKEN_CACHE_TIME`, `M2M_CLIENT_ID`, `M2M_CLIENT_SECRET`, and `AUTH0_PROXY_SERVER_URL` are passed to the standard bus wrapper for outbound authenticated publishing. `TRUST_FORWARDED_CLIENT_IP=true` enables forwarded client-IP moderation using the first exact IPv4/IPv6 host from trusted forwarding headers. When disabled, or when the forwarded value is missing, malformed, CIDR, wildcard, or otherwise non-exact, no client IP is resolved and IP-ban enforcement is skipped for that request. Do not enable this unless the service is behind infrastructure that strips or controls inbound forwarding headers. @@ -284,4 +316,4 @@ The forums Prisma schema defines `Topic`, `Post`, `PostReaction`, `TopicClosure` The production image includes the Prisma schema, migration history, configuration, and CLI. Its startup script runs `prisma migrate deploy` before starting NestJS, so a deployment cannot begin serving requests against an older forums schema. Migration failure stops the container instead of leaving feature-dependent reads or writes partially available. -Topic creation is transactional: it creates the topic, starter post, closure rows, and, for human authors, author watch and read-state rows together. M2M topic creation uses the system author and skips member watch/read-state side effects. Topic deletion is soft deletion, and post deletion preserves the post row while setting content to null for placeholder reads. Topic summary reads use side-effect-free raw queries for topic lock state, visible-post counts, nullable latest visible activity, and unread derivation, then apply centralized forums policy filtering before pagination. +Topic creation is transactional: it creates the topic, starter post, closure rows, and, for human authors, read-state rows together. General topics also seed an author watch; challenge topics inherit the forum preference. M2M topic creation uses the system author and skips member watch/read-state side effects. Topic deletion is soft deletion, and post deletion preserves the post row while setting content to null for placeholder reads. Topic summary reads use side-effect-free raw queries for topic lock state, visible-post counts, nullable latest visible activity, and unread derivation, then apply centralized forums policy filtering before pagination. diff --git a/docs/email-templates/forum-notification.html b/docs/email-templates/forum-notification.html index 2e3932b..c7b51a5 100644 --- a/docs/email-templates/forum-notification.html +++ b/docs/email-templates/forum-notification.html @@ -31,8 +31,8 @@

Post details

Topic: {{topicTitle}}

Topic ID: {{topicId}}

- {{#if challengeTitle}} -

Challenge: {{challengeTitle}}

+ {{#if challengeName}} +

Challenge: {{challengeName}}

{{/if}} {{#if challengeId}}

Challenge ID: {{challengeId}}

diff --git a/docs/email-templates/forum-notification.sample.json b/docs/email-templates/forum-notification.sample.json index 4b08282..87519a0 100644 --- a/docs/email-templates/forum-notification.sample.json +++ b/docs/email-templates/forum-notification.sample.json @@ -1,6 +1,9 @@ { "challengeId": "12345678-1234-4234-8234-123456789abc", + "challengeName": "Topcoder Forum Export Challenge", "challengeTitle": "Topcoder Forum Export Challenge", + "challengeURL": "https://www.topcoder-dev.com/opportunities/challenge/12345678-1234-4234-8234-123456789abc", + "challengeUrl": "https://www.topcoder-dev.com/opportunities/challenge/12345678-1234-4234-8234-123456789abc", "topicId": "87654321-4321-4321-8321-cba987654321", "topicTitle": "Challenge questions and clarifications", "postContent": "Hi everyone,\n\nThe requirements have been updated with an example for the export flow. Please review the discussion and share any questions.\n\nThanks!", diff --git a/packages/forums-prisma-client/edge.js b/packages/forums-prisma-client/edge.js index ddd73b2..5814b9c 100644 --- a/packages/forums-prisma-client/edge.js +++ b/packages/forums-prisma-client/edge.js @@ -159,9 +159,18 @@ exports.Prisma.TopicClosureScalarFieldEnum = { exports.Prisma.TopicWatchScalarFieldEnum = { topicId: 'topicId', memberId: 'memberId', + watching: 'watching', createdAt: 'createdAt' }; +exports.Prisma.ChallengeForumWatchScalarFieldEnum = { + challengeId: 'challengeId', + memberId: 'memberId', + watching: 'watching', + createdAt: 'createdAt', + updatedAt: 'updatedAt' +}; + exports.Prisma.TopicReadStateScalarFieldEnum = { topicId: 'topicId', memberId: 'memberId', @@ -233,6 +242,7 @@ exports.Prisma.ModelName = { PostReaction: 'PostReaction', TopicClosure: 'TopicClosure', TopicWatch: 'TopicWatch', + ChallengeForumWatch: 'ChallengeForumWatch', TopicReadState: 'TopicReadState', PublicForumCategory: 'PublicForumCategory', PublicForumRoleMember: 'PublicForumRoleMember', @@ -246,14 +256,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\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" + "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\n// Explicit topic choices override challenge-wide notification preferences.\nmodel TopicWatch {\n topicId String @db.VarChar(14)\n memberId String @db.VarChar(64)\n watching Boolean @default(true)\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\n// Explicit challenge-wide preferences override resource-based default watches.\nmodel ChallengeForumWatch {\n challengeId String @db.VarChar(64)\n memberId String @db.VarChar(64)\n watching Boolean\n createdAt DateTime @default(now())\n updatedAt DateTime @default(now()) @updatedAt\n\n @@id([challengeId, 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},\"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\":{}}") +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\":\"watching\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"topic\",\"kind\":\"object\",\"type\":\"Topic\",\"relationName\":\"TopicToTopicWatch\"}],\"dbName\":null},\"ChallengeForumWatch\":{\"fields\":[{\"name\":\"challengeId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"memberId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"watching\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"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\",\"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" + 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\",\"ChallengeForumWatch.findUnique\",\"ChallengeForumWatch.findUniqueOrThrow\",\"ChallengeForumWatch.findFirst\",\"ChallengeForumWatch.findFirstOrThrow\",\"ChallengeForumWatch.findMany\",\"ChallengeForumWatch.createOne\",\"ChallengeForumWatch.createMany\",\"ChallengeForumWatch.createManyAndReturn\",\"ChallengeForumWatch.updateOne\",\"ChallengeForumWatch.updateMany\",\"ChallengeForumWatch.updateManyAndReturn\",\"ChallengeForumWatch.upsertOne\",\"ChallengeForumWatch.deleteOne\",\"ChallengeForumWatch.deleteMany\",\"ChallengeForumWatch.groupBy\",\"ChallengeForumWatch.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\",\"challengeId\",\"watching\",\"challengeId_memberId\",\"ancestorTopicId\",\"descendantTopicId\",\"depth\",\"postId\",\"PostReactionType\",\"reaction\",\"id\",\"parentType\",\"parentId\",\"authorMemberId\",\"authorHandle\",\"content\",\"deletedAt\",\"deletedByMemberId\",\"ipAddress\",\"createdByMemberId\",\"removedAt\",\"removedByMemberId\",\"parentTopicId\",\"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: "3QRnwAEZAQAAgwMAIAQAAIQDACAJAACFAwAgDAAAhgMAIA0AAIYDACAOAACHAwAgDwAAiAMAINgBAACCAwAw2QEAAAMAENoBAACCAwAw4AFAAMgCACHuAQEA8AIAIYUCQADIAgAhhgIBAPACACGPAgEAAAABkgIBAMcCACGTAgEAxwIAIZUCQADxAgAhlgIBAPACACGbAgEA8AIAIZwCAQDHAgAhnQIgAN4CACGeAiAA3gIAIZ8CQADxAgAhoAIBAPACACEBAAAAAQAgGQEAAIMDACAEAACEAwAgCQAAhQMAIAwAAIYDACANAACGAwAgDgAAhwMAIA8AAIgDACDYAQAAggMAMNkBAAADABDaAQAAggMAMOABQADIAgAh7gEBAPACACGFAkAAyAIAIYYCAQDwAgAhjwIBAMcCACGSAgEAxwIAIZMCAQDHAgAhlQJAAPECACGWAgEA8AIAIZsCAQDwAgAhnAIBAMcCACGdAiAA3gIAIZ4CIADeAgAhnwJAAPECACGgAgEA8AIAIQEAAAADACAOAQAArAQAIAQAAK8EACAJAACwBAAgDAAAsQQAIA0AALEEACAOAACyBAAgDwAAswQAIO4BAAC6AwAghgIAALoDACCVAgAAugMAIJYCAAC6AwAgmwIAALoDACCfAgAAugMAIKACAAC6AwAgAwAAAAMAIAIAAAUAMAMAAAEAIBAFAAD3AgAgBwAAgQMAINgBAACAAwAw2QEAAAcAENoBAACAAwAw4AFAAMgCACHxAQEAxwIAIYUCQADIAgAhjwIBAMcCACGQAgEAxwIAIZECAQDHAgAhkgIBAMcCACGTAgEAxwIAIZQCAQDwAgAhlQJAAPECACGWAgEA8AIAIQUFAACsBAAgBwAArgQAIJQCAAC6AwAglQIAALoDACCWAgAAugMAIBAFAAD3AgAgBwAAgQMAINgBAACAAwAw2QEAAAcAENoBAACAAwAw4AFAAMgCACHxAQEAxwIAIYUCQADIAgAhjwIBAAAAAZACAQDHAgAhkQIBAMcCACGSAgEAxwIAIZMCAQDHAgAhlAIBAPACACGVAkAA8QIAIZYCAQDwAgAhAwAAAAcAIAIAAAgAMAMAAAkAIAkGAAD_AgAg2AEAAP0CADDZAQAACwAQ2gEAAP0CADDgAUAAyAIAIe8BAQDHAgAhhQJAAMgCACGMAgEAxwIAIY4CAAD-Ao4CIgEGAACtBAAgCgYAAP8CACDYAQAA_QIAMNkBAAALABDaAQAA_QIAMOABQADIAgAh7wEBAMcCACGFAkAAyAIAIYwCAQDHAgAhjgIAAP4CjgIiowIAAPwCACADAAAACwAgAgAADAAwAwAADQAgAQAAAAsAIAgKAAD3AgAgCwAA9wIAINgBAAD7AgAw2QEAABAAENoBAAD7AgAwiQIBAMcCACGKAgEAxwIAIYsCAgDWAgAhAgoAAKwEACALAACsBAAgCQoAAPcCACALAAD3AgAg2AEAAPsCADDZAQAAEAAQ2gEAAPsCADCJAgEAxwIAIYoCAQDHAgAhiwICANYCACGiAgAA-gIAIAMAAAAQACACAAARADADAAASACADAAAAEAAgAgAAEQAwAwAAEgAgCAUAAPcCACDYAQAA-QIAMNkBAAAVABDaAQAA-QIAMOABQADIAgAh7wEBAMcCACHxAQEAxwIAIYcCIADeAgAhAQUAAKwEACAJBQAA9wIAINgBAAD5AgAw2QEAABUAENoBAAD5AgAw4AFAAMgCACHvAQEAxwIAIfEBAQDHAgAhhwIgAN4CACGhAgAA-AIAIAMAAAAVACACAAAWADADAAAXACAIBQAA9wIAINgBAAD2AgAw2QEAABkAENoBAAD2AgAw7wEBAMcCACHxAQEAxwIAIYQCQADIAgAhhQJAAMgCACEBBQAArAQAIAkFAAD3AgAg2AEAAPYCADDZAQAAGQAQ2gEAAPYCADDvAQEAxwIAIfEBAQDHAgAhhAJAAMgCACGFAkAAyAIAIaECAAD1AgAgAwAAABkAIAIAABoAMAMAABsAIAEAAAADACABAAAABwAgAQAAABAAIAEAAAAQACABAAAAFQAgAQAAABkAIAEAAAABACADAAAAAwAgAgAABQAwAwAAAQAgAwAAAAMAIAIAAAUAMAMAAAEAIAMAAAADACACAAAFADADAAABACAWAQAAqwQAIAQAAKUEACAJAACmBAAgDAAApwQAIA0AAKgEACAOAACpBAAgDwAAqgQAIOABQAAAAAHuAQEAAAABhQJAAAAAAYYCAQAAAAGPAgEAAAABkgIBAAAAAZMCAQAAAAGVAkAAAAABlgIBAAAAAZsCAQAAAAGcAgEAAAABnQIgAAAAAZ4CIAAAAAGfAkAAAAABoAIBAAAAAQEVAAAnACAP4AFAAAAAAe4BAQAAAAGFAkAAAAABhgIBAAAAAY8CAQAAAAGSAgEAAAABkwIBAAAAAZUCQAAAAAGWAgEAAAABmwIBAAAAAZwCAQAAAAGdAiAAAAABngIgAAAAAZ8CQAAAAAGgAgEAAAABARUAACkAMAEVAAApADABAAAAAwAgFgEAANkDACAEAADaAwAgCQAA2wMAIAwAANwDACANAADdAwAgDgAA3gMAIA8AAN8DACDgAUAAjQMAIe4BAQC-AwAhhQJAAI0DACGGAgEAvgMAIY8CAQCMAwAhkgIBAIwDACGTAgEAjAMAIZUCQAC_AwAhlgIBAL4DACGbAgEAvgMAIZwCAQCMAwAhnQIgAKUDACGeAiAApQMAIZ8CQAC_AwAhoAIBAL4DACECAAAAAQAgFQAALQAgD-ABQACNAwAh7gEBAL4DACGFAkAAjQMAIYYCAQC-AwAhjwIBAIwDACGSAgEAjAMAIZMCAQCMAwAhlQJAAL8DACGWAgEAvgMAIZsCAQC-AwAhnAIBAIwDACGdAiAApQMAIZ4CIAClAwAhnwJAAL8DACGgAgEAvgMAIQIAAAADACAVAAAvACACAAAAAwAgFQAALwAgAQAAAAMAIAMAAAABACAcAAAnACAdAAAtACABAAAAAQAgAQAAAAMAIAoIAADWAwAgIgAA2AMAICMAANcDACDuAQAAugMAIIYCAAC6AwAglQIAALoDACCWAgAAugMAIJsCAAC6AwAgnwIAALoDACCgAgAAugMAIBLYAQAA9AIAMNkBAAA3ABDaAQAA9AIAMOABQADAAgAh7gEBAOcCACGFAkAAwAIAIYYCAQDnAgAhjwIBAL8CACGSAgEAvwIAIZMCAQC_AgAhlQJAAOgCACGWAgEA5wIAIZsCAQDnAgAhnAIBAL8CACGdAiAA2gIAIZ4CIADaAgAhnwJAAOgCACGgAgEA5wIAIQMAAAADACACAAA2ADAhAAA3ACADAAAAAwAgAgAABQAwAwAAAQAgCdgBAADzAgAw2QEAAD0AENoBAADzAgAw4AFAAMgCACHvAQEAxwIAIY8CAQAAAAGYAgEA8AIAIZkCQADxAgAhmgIBAPACACEBAAAAOgAgAQAAADoAIAnYAQAA8wIAMNkBAAA9ABDaAQAA8wIAMOABQADIAgAh7wEBAMcCACGPAgEAxwIAIZgCAQDwAgAhmQJAAPECACGaAgEA8AIAIQOYAgAAugMAIJkCAAC6AwAgmgIAALoDACADAAAAPQAgAgAAPgAwAwAAOgAgAwAAAD0AIAIAAD4AMAMAADoAIAMAAAA9ACACAAA-ADADAAA6ACAG4AFAAAAAAe8BAQAAAAGPAgEAAAABmAIBAAAAAZkCQAAAAAGaAgEAAAABARUAAEIAIAbgAUAAAAAB7wEBAAAAAY8CAQAAAAGYAgEAAAABmQJAAAAAAZoCAQAAAAEBFQAARAAwARUAAEQAMAbgAUAAjQMAIe8BAQCMAwAhjwIBAIwDACGYAgEAvgMAIZkCQAC_AwAhmgIBAL4DACECAAAAOgAgFQAARwAgBuABQACNAwAh7wEBAIwDACGPAgEAjAMAIZgCAQC-AwAhmQJAAL8DACGaAgEAvgMAIQIAAAA9ACAVAABJACACAAAAPQAgFQAASQAgAwAAADoAIBwAAEIAIB0AAEcAIAEAAAA6ACABAAAAPQAgBggAANMDACAiAADVAwAgIwAA1AMAIJgCAAC6AwAgmQIAALoDACCaAgAAugMAIAnYAQAA8gIAMNkBAABQABDaAQAA8gIAMOABQADAAgAh7wEBAL8CACGPAgEAvwIAIZgCAQDnAgAhmQJAAOgCACGaAgEA5wIAIQMAAAA9ACACAABPADAhAABQACADAAAAPQAgAgAAPgAwAwAAOgAgCdgBAADvAgAw2QEAAFYAENoBAADvAgAw4AFAAMgCACGPAgEAAAABlwIBAMcCACGYAgEA8AIAIZkCQADxAgAhmgIBAPACACEBAAAAUwAgAQAAAFMAIAnYAQAA7wIAMNkBAABWABDaAQAA7wIAMOABQADIAgAhjwIBAMcCACGXAgEAxwIAIZgCAQDwAgAhmQJAAPECACGaAgEA8AIAIQOYAgAAugMAIJkCAAC6AwAgmgIAALoDACADAAAAVgAgAgAAVwAwAwAAUwAgAwAAAFYAIAIAAFcAMAMAAFMAIAMAAABWACACAABXADADAABTACAG4AFAAAAAAY8CAQAAAAGXAgEAAAABmAIBAAAAAZkCQAAAAAGaAgEAAAABARUAAFsAIAbgAUAAAAABjwIBAAAAAZcCAQAAAAGYAgEAAAABmQJAAAAAAZoCAQAAAAEBFQAAXQAwARUAAF0AMAbgAUAAjQMAIY8CAQCMAwAhlwIBAIwDACGYAgEAvgMAIZkCQAC_AwAhmgIBAL4DACECAAAAUwAgFQAAYAAgBuABQACNAwAhjwIBAIwDACGXAgEAjAMAIZgCAQC-AwAhmQJAAL8DACGaAgEAvgMAIQIAAABWACAVAABiACACAAAAVgAgFQAAYgAgAwAAAFMAIBwAAFsAIB0AAGAAIAEAAABTACABAAAAVgAgBggAANADACAiAADSAwAgIwAA0QMAIJgCAAC6AwAgmQIAALoDACCaAgAAugMAIAnYAQAA7gIAMNkBAABpABDaAQAA7gIAMOABQADAAgAhjwIBAL8CACGXAgEAvwIAIZgCAQDnAgAhmQJAAOgCACGaAgEA5wIAIQMAAABWACACAABoADAhAABpACADAAAAVgAgAgAAVwAwAwAAUwAgAQAAAAkAIAEAAAAJACADAAAABwAgAgAACAAwAwAACQAgAwAAAAcAIAIAAAgAMAMAAAkAIAMAAAAHACACAAAIADADAAAJACANBQAAzgMAIAcAAM8DACDgAUAAAAAB8QEBAAAAAYUCQAAAAAGPAgEAAAABkAIBAAAAAZECAQAAAAGSAgEAAAABkwIBAAAAAZQCAQAAAAGVAkAAAAABlgIBAAAAAQEVAABxACAL4AFAAAAAAfEBAQAAAAGFAkAAAAABjwIBAAAAAZACAQAAAAGRAgEAAAABkgIBAAAAAZMCAQAAAAGUAgEAAAABlQJAAAAAAZYCAQAAAAEBFQAAcwAwARUAAHMAMA0FAADAAwAgBwAAwQMAIOABQACNAwAh8QEBAIwDACGFAkAAjQMAIY8CAQCMAwAhkAIBAIwDACGRAgEAjAMAIZICAQCMAwAhkwIBAIwDACGUAgEAvgMAIZUCQAC_AwAhlgIBAL4DACECAAAACQAgFQAAdgAgC-ABQACNAwAh8QEBAIwDACGFAkAAjQMAIY8CAQCMAwAhkAIBAIwDACGRAgEAjAMAIZICAQCMAwAhkwIBAIwDACGUAgEAvgMAIZUCQAC_AwAhlgIBAL4DACECAAAABwAgFQAAeAAgAgAAAAcAIBUAAHgAIAMAAAAJACAcAABxACAdAAB2ACABAAAACQAgAQAAAAcAIAYIAAC7AwAgIgAAvQMAICMAALwDACCUAgAAugMAIJUCAAC6AwAglgIAALoDACAO2AEAAOYCADDZAQAAfwAQ2gEAAOYCADDgAUAAwAIAIfEBAQC_AgAhhQJAAMACACGPAgEAvwIAIZACAQC_AgAhkQIBAL8CACGSAgEAvwIAIZMCAQC_AgAhlAIBAOcCACGVAkAA6AIAIZYCAQDnAgAhAwAAAAcAIAIAAH4AMCEAAH8AIAMAAAAHACACAAAIADADAAAJACABAAAADQAgAQAAAA0AIAMAAAALACACAAAMADADAAANACADAAAACwAgAgAADAAwAwAADQAgAwAAAAsAIAIAAAwAMAMAAA0AIAYGAAC5AwAg4AFAAAAAAe8BAQAAAAGFAkAAAAABjAIBAAAAAY4CAAAAjgICARUAAIcBACAF4AFAAAAAAe8BAQAAAAGFAkAAAAABjAIBAAAAAY4CAAAAjgICARUAAIkBADABFQAAiQEAMAYGAAC4AwAg4AFAAI0DACHvAQEAjAMAIYUCQACNAwAhjAIBAIwDACGOAgAAtwOOAiICAAAADQAgFQAAjAEAIAXgAUAAjQMAIe8BAQCMAwAhhQJAAI0DACGMAgEAjAMAIY4CAAC3A44CIgIAAAALACAVAACOAQAgAgAAAAsAIBUAAI4BACADAAAADQAgHAAAhwEAIB0AAIwBACABAAAADQAgAQAAAAsAIAMIAAC0AwAgIgAAtgMAICMAALUDACAI2AEAAOICADDZAQAAlQEAENoBAADiAgAw4AFAAMACACHvAQEAvwIAIYUCQADAAgAhjAIBAL8CACGOAgAA4wKOAiIDAAAACwAgAgAAlAEAMCEAAJUBACADAAAACwAgAgAADAAwAwAADQAgAQAAABIAIAEAAAASACADAAAAEAAgAgAAEQAwAwAAEgAgAwAAABAAIAIAABEAMAMAABIAIAMAAAAQACACAAARADADAAASACAFCgAAsgMAIAsAALMDACCJAgEAAAABigIBAAAAAYsCAgAAAAEBFQAAnQEAIAOJAgEAAAABigIBAAAAAYsCAgAAAAEBFQAAnwEAMAEVAACfAQAwBQoAALADACALAACxAwAgiQIBAIwDACGKAgEAjAMAIYsCAgCWAwAhAgAAABIAIBUAAKIBACADiQIBAIwDACGKAgEAjAMAIYsCAgCWAwAhAgAAABAAIBUAAKQBACACAAAAEAAgFQAApAEAIAMAAAASACAcAACdAQAgHQAAogEAIAEAAAASACABAAAAEAAgBQgAAKsDACAiAACuAwAgIwAArQMAIHQAAKwDACB1AACvAwAgBtgBAADhAgAw2QEAAKsBABDaAQAA4QIAMIkCAQC_AgAhigIBAL8CACGLAgIAzwIAIQMAAAAQACACAACqAQAwIQAAqwEAIAMAAAAQACACAAARADADAAASACABAAAAFwAgAQAAABcAIAMAAAAVACACAAAWADADAAAXACADAAAAFQAgAgAAFgAwAwAAFwAgAwAAABUAIAIAABYAMAMAABcAIAUFAACqAwAg4AFAAAAAAe8BAQAAAAHxAQEAAAABhwIgAAAAAQEVAACzAQAgBOABQAAAAAHvAQEAAAAB8QEBAAAAAYcCIAAAAAEBFQAAtQEAMAEVAAC1AQAwBQUAAKkDACDgAUAAjQMAIe8BAQCMAwAh8QEBAIwDACGHAiAApQMAIQIAAAAXACAVAAC4AQAgBOABQACNAwAh7wEBAIwDACHxAQEAjAMAIYcCIAClAwAhAgAAABUAIBUAALoBACACAAAAFQAgFQAAugEAIAMAAAAXACAcAACzAQAgHQAAuAEAIAEAAAAXACABAAAAFQAgAwgAAKYDACAiAACoAwAgIwAApwMAIAfYAQAA4AIAMNkBAADBAQAQ2gEAAOACADDgAUAAwAIAIe8BAQC_AgAh8QEBAL8CACGHAiAA2gIAIQMAAAAVACACAADAAQAwIQAAwQEAIAMAAAAVACACAAAWADADAAAXACAJ2AEAAN0CADDZAQAAxwEAENoBAADdAgAw4AFAAMgCACHvAQEAxwIAIYUCQADIAgAhhgIBAMcCACGHAiAA3gIAIYgCAADfAgAgAQAAAMQBACABAAAAxAEAIAjYAQAA3QIAMNkBAADHAQAQ2gEAAN0CADDgAUAAyAIAIe8BAQDHAgAhhQJAAMgCACGGAgEAxwIAIYcCIADeAgAhAAMAAADHAQAgAgAAyAEAMAMAAMQBACADAAAAxwEAIAIAAMgBADADAADEAQAgAwAAAMcBACACAADIAQAwAwAAxAEAIAXgAUAAAAAB7wEBAAAAAYUCQAAAAAGGAgEAAAABhwIgAAAAAQEVAADMAQAgBeABQAAAAAHvAQEAAAABhQJAAAAAAYYCAQAAAAGHAiAAAAABARUAAM4BADABFQAAzgEAMAXgAUAAjQMAIe8BAQCMAwAhhQJAAI0DACGGAgEAjAMAIYcCIAClAwAhAgAAAMQBACAVAADRAQAgBeABQACNAwAh7wEBAIwDACGFAkAAjQMAIYYCAQCMAwAhhwIgAKUDACECAAAAxwEAIBUAANMBACACAAAAxwEAIBUAANMBACADAAAAxAEAIBwAAMwBACAdAADRAQAgAQAAAMQBACABAAAAxwEAIAMIAACiAwAgIgAApAMAICMAAKMDACAI2AEAANkCADDZAQAA2gEAENoBAADZAgAw4AFAAMACACHvAQEAvwIAIYUCQADAAgAhhgIBAL8CACGHAiAA2gIAIQMAAADHAQAgAgAA2QEAMCEAANoBACADAAAAxwEAIAIAAMgBADADAADEAQAgAQAAABsAIAEAAAAbACADAAAAGQAgAgAAGgAwAwAAGwAgAwAAABkAIAIAABoAMAMAABsAIAMAAAAZACACAAAaADADAAAbACAFBQAAoQMAIO8BAQAAAAHxAQEAAAABhAJAAAAAAYUCQAAAAAEBFQAA4gEAIATvAQEAAAAB8QEBAAAAAYQCQAAAAAGFAkAAAAABARUAAOQBADABFQAA5AEAMAUFAACgAwAg7wEBAIwDACHxAQEAjAMAIYQCQACNAwAhhQJAAI0DACECAAAAGwAgFQAA5wEAIATvAQEAjAMAIfEBAQCMAwAhhAJAAI0DACGFAkAAjQMAIQIAAAAZACAVAADpAQAgAgAAABkAIBUAAOkBACADAAAAGwAgHAAA4gEAIB0AAOcBACABAAAAGwAgAQAAABkAIAMIAACdAwAgIgAAnwMAICMAAJ4DACAH2AEAANgCADDZAQAA8AEAENoBAADYAgAw7wEBAL8CACHxAQEAvwIAIYQCQADAAgAhhQJAAMACACEDAAAAGQAgAgAA7wEAMCEAAPABACADAAAAGQAgAgAAGgAwAwAAGwAgDdgBAADVAgAw2QEAAPYBABDaAQAA1QIAMPEBAQAAAAHyAQEAxwIAIfMBAQDHAgAh9AECANYCACH1AQAA0AIAIPYBAADQAgAg9wEAANACACD4AQIAAAAB-QEBAMcCACH6AQAA1wIAIAEAAADzAQAgAQAAAPMBACAN2AEAANUCADDZAQAA9gEAENoBAADVAgAw8QEBAMcCACHyAQEAxwIAIfMBAQDHAgAh9AECANYCACH1AQAA0AIAIPYBAADQAgAg9wEAANACACD4AQIA1gIAIfkBAQDHAgAh-gEAANcCACAAAwAAAPYBACACAAD3AQAwAwAA8wEAIAMAAAD2AQAgAgAA9wEAMAMAAPMBACADAAAA9gEAIAIAAPcBADADAADzAQAgCvEBAQAAAAHyAQEAAAAB8wEBAAAAAfQBAgAAAAH1AQAAmgMAIPYBAACbAwAg9wEAAJwDACD4AQIAAAAB-QEBAAAAAfoBgAAAAAEBFQAA-wEAIArxAQEAAAAB8gEBAAAAAfMBAQAAAAH0AQIAAAAB9QEAAJoDACD2AQAAmwMAIPcBAACcAwAg-AECAAAAAfkBAQAAAAH6AYAAAAABARUAAP0BADABFQAA_QEAMArxAQEAjAMAIfIBAQCMAwAh8wEBAIwDACH0AQIAlgMAIfUBAACXAwAg9gEAAJgDACD3AQAAmQMAIPgBAgCWAwAh-QEBAIwDACH6AYAAAAABAgAAAPMBACAVAACAAgAgCvEBAQCMAwAh8gEBAIwDACHzAQEAjAMAIfQBAgCWAwAh9QEAAJcDACD2AQAAmAMAIPcBAACZAwAg-AECAJYDACH5AQEAjAMAIfoBgAAAAAECAAAA9gEAIBUAAIICACACAAAA9gEAIBUAAIICACADAAAA8wEAIBwAAPsBACAdAACAAgAgAQAAAPMBACABAAAA9gEAIAUIAACRAwAgIgAAlAMAICMAAJMDACB0AACSAwAgdQAAlQMAIA3YAQAAzgIAMNkBAACJAgAQ2gEAAM4CADDxAQEAvwIAIfIBAQC_AgAh8wEBAL8CACH0AQIAzwIAIfUBAADQAgAg9gEAANACACD3AQAA0AIAIPgBAgDPAgAh-QEBAL8CACH6AQAA0QIAIAMAAAD2AQAgAgAAiAIAMCEAAIkCACADAAAA9gEAIAIAAPcBADADAADzAQAgBtgBAADMAgAw2QEAAI8CABDaAQAAzAIAMO4BAQDHAgAh7wEBAMcCACHwAQAAzQIAIAEAAACMAgAgAQAAAIwCACAF2AEAAMwCADDZAQAAjwIAENoBAADMAgAw7gEBAMcCACHvAQEAxwIAIQADAAAAjwIAIAIAAJACADADAACMAgAgAwAAAI8CACACAACQAgAwAwAAjAIAIAMAAACPAgAgAgAAkAIAMAMAAIwCACAC7gEBAAAAAe8BAQAAAAEBFQAAlAIAIALuAQEAAAAB7wEBAAAAAQEVAACWAgAwARUAAJYCADAC7gEBAIwDACHvAQEAjAMAIQIAAACMAgAgFQAAmQIAIALuAQEAjAMAIe8BAQCMAwAhAgAAAI8CACAVAACbAgAgAgAAAI8CACAVAACbAgAgAwAAAIwCACAcAACUAgAgHQAAmQIAIAEAAACMAgAgAQAAAI8CACADCAAAjgMAICIAAJADACAjAACPAwAgBdgBAADLAgAw2QEAAKICABDaAQAAywIAMO4BAQC_AgAh7wEBAL8CACEDAAAAjwIAIAIAAKECADAhAACiAgAgAwAAAI8CACACAACQAgAwAwAAjAIAIAvYAQAAxgIAMNkBAACoAgAQ2gEAAMYCADDbAQEAxwIAIdwBAQDHAgAh3QEBAMcCACHeAQEAxwIAId8BAQDHAgAh4AFAAMgCACHsAQAAyQIAIO0BAADKAgAgAQAAAKUCACABAAAApQIAIAnYAQAAxgIAMNkBAACoAgAQ2gEAAMYCADDbAQEAxwIAIdwBAQDHAgAh3QEBAMcCACHeAQEAxwIAId8BAQDHAgAh4AFAAMgCACEAAwAAAKgCACACAACpAgAwAwAApQIAIAMAAACoAgAgAgAAqQIAMAMAAKUCACADAAAAqAIAIAIAAKkCADADAAClAgAgBtsBAQAAAAHcAQEAAAAB3QEBAAAAAd4BAQAAAAHfAQEAAAAB4AFAAAAAAQEVAACtAgAgBtsBAQAAAAHcAQEAAAAB3QEBAAAAAd4BAQAAAAHfAQEAAAAB4AFAAAAAAQEVAACvAgAwARUAAK8CADAG2wEBAIwDACHcAQEAjAMAId0BAQCMAwAh3gEBAIwDACHfAQEAjAMAIeABQACNAwAhAgAAAKUCACAVAACyAgAgBtsBAQCMAwAh3AEBAIwDACHdAQEAjAMAId4BAQCMAwAh3wEBAIwDACHgAUAAjQMAIQIAAACoAgAgFQAAtAIAIAIAAACoAgAgFQAAtAIAIAMAAAClAgAgHAAArQIAIB0AALICACABAAAApQIAIAEAAACoAgAgAwgAAIkDACAiAACLAwAgIwAAigMAIAnYAQAAvgIAMNkBAAC7AgAQ2gEAAL4CADDbAQEAvwIAIdwBAQC_AgAh3QEBAL8CACHeAQEAvwIAId8BAQC_AgAh4AFAAMACACEDAAAAqAIAIAIAALoCADAhAAC7AgAgAwAAAKgCACACAACpAgAwAwAApQIAIAnYAQAAvgIAMNkBAAC7AgAQ2gEAAL4CADDbAQEAvwIAIdwBAQC_AgAh3QEBAL8CACHeAQEAvwIAId8BAQC_AgAh4AFAAMACACEOCAAAwgIAICIAAMUCACAjAADFAgAg4QEBAAAAAeIBAQAAAATjAQEAAAAE5AEBAAAAAeUBAQAAAAHmAQEAAAAB5wEBAAAAAegBAQDEAgAh6QEBAAAAAeoBAQAAAAHrAQEAAAABCwgAAMICACAiAADDAgAgIwAAwwIAIOEBQAAAAAHiAUAAAAAE4wFAAAAABOQBQAAAAAHlAUAAAAAB5gFAAAAAAecBQAAAAAHoAUAAwQIAIQsIAADCAgAgIgAAwwIAICMAAMMCACDhAUAAAAAB4gFAAAAABOMBQAAAAATkAUAAAAAB5QFAAAAAAeYBQAAAAAHnAUAAAAAB6AFAAMECACEI4QECAAAAAeIBAgAAAATjAQIAAAAE5AECAAAAAeUBAgAAAAHmAQIAAAAB5wECAAAAAegBAgDCAgAhCOEBQAAAAAHiAUAAAAAE4wFAAAAABOQBQAAAAAHlAUAAAAAB5gFAAAAAAecBQAAAAAHoAUAAwwIAIQ4IAADCAgAgIgAAxQIAICMAAMUCACDhAQEAAAAB4gEBAAAABOMBAQAAAATkAQEAAAAB5QEBAAAAAeYBAQAAAAHnAQEAAAAB6AEBAMQCACHpAQEAAAAB6gEBAAAAAesBAQAAAAEL4QEBAAAAAeIBAQAAAATjAQEAAAAE5AEBAAAAAeUBAQAAAAHmAQEAAAAB5wEBAAAAAegBAQDFAgAh6QEBAAAAAeoBAQAAAAHrAQEAAAABCdgBAADGAgAw2QEAAKgCABDaAQAAxgIAMNsBAQDHAgAh3AEBAMcCACHdAQEAxwIAId4BAQDHAgAh3wEBAMcCACHgAUAAyAIAIQvhAQEAAAAB4gEBAAAABOMBAQAAAATkAQEAAAAB5QEBAAAAAeYBAQAAAAHnAQEAAAAB6AEBAMUCACHpAQEAAAAB6gEBAAAAAesBAQAAAAEI4QFAAAAAAeIBQAAAAATjAUAAAAAE5AFAAAAAAeUBQAAAAAHmAUAAAAAB5wFAAAAAAegBQADDAgAhAtsBAQAAAAHdAQEAAAABAtsBAQAAAAHcAQEAAAABBdgBAADLAgAw2QEAAKICABDaAQAAywIAMO4BAQC_AgAh7wEBAL8CACEF2AEAAMwCADDZAQAAjwIAENoBAADMAgAw7gEBAMcCACHvAQEAxwIAIQLuAQEAAAAB7wEBAAAAAQ3YAQAAzgIAMNkBAACJAgAQ2gEAAM4CADDxAQEAvwIAIfIBAQC_AgAh8wEBAL8CACH0AQIAzwIAIfUBAADQAgAg9gEAANACACD3AQAA0AIAIPgBAgDPAgAh-QEBAL8CACH6AQAA0QIAIA0IAADCAgAgIgAAwgIAICMAAMICACB0AADUAgAgdQAAwgIAIOEBAgAAAAHiAQIAAAAE4wECAAAABOQBAgAAAAHlAQIAAAAB5gECAAAAAecBAgAAAAHoAQIA0wIAIQThAQEAAAAFgQIBAAAAAYICAQAAAASDAgEAAAAEDwgAAMICACAiAADSAgAgIwAA0gIAIOEBgAAAAAHkAYAAAAAB5QGAAAAAAeYBgAAAAAHnAYAAAAAB6AGAAAAAAfsBAQAAAAH8AQEAAAAB_QEBAAAAAf4BgAAAAAH_AYAAAAABgAKAAAAAAQzhAYAAAAAB5AGAAAAAAeUBgAAAAAHmAYAAAAAB5wGAAAAAAegBgAAAAAH7AQEAAAAB_AEBAAAAAf0BAQAAAAH-AYAAAAAB_wGAAAAAAYACgAAAAAENCAAAwgIAICIAAMICACAjAADCAgAgdAAA1AIAIHUAAMICACDhAQIAAAAB4gECAAAABOMBAgAAAATkAQIAAAAB5QECAAAAAeYBAgAAAAHnAQIAAAAB6AECANMCACEI4QEIAAAAAeIBCAAAAATjAQgAAAAE5AEIAAAAAeUBCAAAAAHmAQgAAAAB5wEIAAAAAegBCADUAgAhDdgBAADVAgAw2QEAAPYBABDaAQAA1QIAMPEBAQDHAgAh8gEBAMcCACHzAQEAxwIAIfQBAgDWAgAh9QEAANACACD2AQAA0AIAIPcBAADQAgAg-AECANYCACH5AQEAxwIAIfoBAADXAgAgCOEBAgAAAAHiAQIAAAAE4wECAAAABOQBAgAAAAHlAQIAAAAB5gECAAAAAecBAgAAAAHoAQIAwgIAIQzhAYAAAAAB5AGAAAAAAeUBgAAAAAHmAYAAAAAB5wGAAAAAAegBgAAAAAH7AQEAAAAB_AEBAAAAAf0BAQAAAAH-AYAAAAAB_wGAAAAAAYACgAAAAAEH2AEAANgCADDZAQAA8AEAENoBAADYAgAw7wEBAL8CACHxAQEAvwIAIYQCQADAAgAhhQJAAMACACEI2AEAANkCADDZAQAA2gEAENoBAADZAgAw4AFAAMACACHvAQEAvwIAIYUCQADAAgAhhgIBAL8CACGHAiAA2gIAIQUIAADCAgAgIgAA3AIAICMAANwCACDhASAAAAAB6AEgANsCACEFCAAAwgIAICIAANwCACAjAADcAgAg4QEgAAAAAegBIADbAgAhAuEBIAAAAAHoASAA3AIAIQjYAQAA3QIAMNkBAADHAQAQ2gEAAN0CADDgAUAAyAIAIe8BAQDHAgAhhQJAAMgCACGGAgEAxwIAIYcCIADeAgAhAuEBIAAAAAHoASAA3AIAIQLvAQEAAAABhgIBAAAAAQfYAQAA4AIAMNkBAADBAQAQ2gEAAOACADDgAUAAwAIAIe8BAQC_AgAh8QEBAL8CACGHAiAA2gIAIQbYAQAA4QIAMNkBAACrAQAQ2gEAAOECADCJAgEAvwIAIYoCAQC_AgAhiwICAM8CACEI2AEAAOICADDZAQAAlQEAENoBAADiAgAw4AFAAMACACHvAQEAvwIAIYUCQADAAgAhjAIBAL8CACGOAgAA4wKOAiIHCAAAwgIAICIAAOUCACAjAADlAgAg4QEAAACOAgLiAQAAAI4CCOMBAAAAjgII6AEAAOQCjgIiBwgAAMICACAiAADlAgAgIwAA5QIAIOEBAAAAjgIC4gEAAACOAgjjAQAAAI4CCOgBAADkAo4CIgThAQAAAI4CAuIBAAAAjgII4wEAAACOAgjoAQAA5QKOAiIO2AEAAOYCADDZAQAAfwAQ2gEAAOYCADDgAUAAwAIAIfEBAQC_AgAhhQJAAMACACGPAgEAvwIAIZACAQC_AgAhkQIBAL8CACGSAgEAvwIAIZMCAQC_AgAhlAIBAOcCACGVAkAA6AIAIZYCAQDnAgAhDggAAOoCACAiAADtAgAgIwAA7QIAIOEBAQAAAAHiAQEAAAAF4wEBAAAABeQBAQAAAAHlAQEAAAAB5gEBAAAAAecBAQAAAAHoAQEA7AIAIekBAQAAAAHqAQEAAAAB6wEBAAAAAQsIAADqAgAgIgAA6wIAICMAAOsCACDhAUAAAAAB4gFAAAAABeMBQAAAAAXkAUAAAAAB5QFAAAAAAeYBQAAAAAHnAUAAAAAB6AFAAOkCACELCAAA6gIAICIAAOsCACAjAADrAgAg4QFAAAAAAeIBQAAAAAXjAUAAAAAF5AFAAAAAAeUBQAAAAAHmAUAAAAAB5wFAAAAAAegBQADpAgAhCOEBAgAAAAHiAQIAAAAF4wECAAAABeQBAgAAAAHlAQIAAAAB5gECAAAAAecBAgAAAAHoAQIA6gIAIQjhAUAAAAAB4gFAAAAABeMBQAAAAAXkAUAAAAAB5QFAAAAAAeYBQAAAAAHnAUAAAAAB6AFAAOsCACEOCAAA6gIAICIAAO0CACAjAADtAgAg4QEBAAAAAeIBAQAAAAXjAQEAAAAF5AEBAAAAAeUBAQAAAAHmAQEAAAAB5wEBAAAAAegBAQDsAgAh6QEBAAAAAeoBAQAAAAHrAQEAAAABC-EBAQAAAAHiAQEAAAAF4wEBAAAABeQBAQAAAAHlAQEAAAAB5gEBAAAAAecBAQAAAAHoAQEA7QIAIekBAQAAAAHqAQEAAAAB6wEBAAAAAQnYAQAA7gIAMNkBAABpABDaAQAA7gIAMOABQADAAgAhjwIBAL8CACGXAgEAvwIAIZgCAQDnAgAhmQJAAOgCACGaAgEA5wIAIQnYAQAA7wIAMNkBAABWABDaAQAA7wIAMOABQADIAgAhjwIBAMcCACGXAgEAxwIAIZgCAQDwAgAhmQJAAPECACGaAgEA8AIAIQvhAQEAAAAB4gEBAAAABeMBAQAAAAXkAQEAAAAB5QEBAAAAAeYBAQAAAAHnAQEAAAAB6AEBAO0CACHpAQEAAAAB6gEBAAAAAesBAQAAAAEI4QFAAAAAAeIBQAAAAAXjAUAAAAAF5AFAAAAAAeUBQAAAAAHmAUAAAAAB5wFAAAAAAegBQADrAgAhCdgBAADyAgAw2QEAAFAAENoBAADyAgAw4AFAAMACACHvAQEAvwIAIY8CAQC_AgAhmAIBAOcCACGZAkAA6AIAIZoCAQDnAgAhCdgBAADzAgAw2QEAAD0AENoBAADzAgAw4AFAAMgCACHvAQEAxwIAIY8CAQDHAgAhmAIBAPACACGZAkAA8QIAIZoCAQDwAgAhEtgBAAD0AgAw2QEAADcAENoBAAD0AgAw4AFAAMACACHuAQEA5wIAIYUCQADAAgAhhgIBAOcCACGPAgEAvwIAIZICAQC_AgAhkwIBAL8CACGVAkAA6AIAIZYCAQDnAgAhmwIBAOcCACGcAgEAvwIAIZ0CIADaAgAhngIgANoCACGfAkAA6AIAIaACAQDnAgAhAu8BAQAAAAHxAQEAAAABCAUAAPcCACDYAQAA9gIAMNkBAAAZABDaAQAA9gIAMO8BAQDHAgAh8QEBAMcCACGEAkAAyAIAIYUCQADIAgAhGwEAAIMDACAEAACEAwAgCQAAhQMAIAwAAIYDACANAACGAwAgDgAAhwMAIA8AAIgDACDYAQAAggMAMNkBAAADABDaAQAAggMAMOABQADIAgAh7gEBAPACACGFAkAAyAIAIYYCAQDwAgAhjwIBAMcCACGSAgEAxwIAIZMCAQDHAgAhlQJAAPECACGWAgEA8AIAIZsCAQDwAgAhnAIBAMcCACGdAiAA3gIAIZ4CIADeAgAhnwJAAPECACGgAgEA8AIAIacCAAADACCoAgAAAwAgAu8BAQAAAAHxAQEAAAABCAUAAPcCACDYAQAA-QIAMNkBAAAVABDaAQAA-QIAMOABQADIAgAh7wEBAMcCACHxAQEAxwIAIYcCIADeAgAhAokCAQAAAAGKAgEAAAABCAoAAPcCACALAAD3AgAg2AEAAPsCADDZAQAAEAAQ2gEAAPsCADCJAgEAxwIAIYoCAQDHAgAhiwICANYCACEC7wEBAAAAAYwCAQAAAAEJBgAA_wIAINgBAAD9AgAw2QEAAAsAENoBAAD9AgAw4AFAAMgCACHvAQEAxwIAIYUCQADIAgAhjAIBAMcCACGOAgAA_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_wIAINgBAAD9AgAw2QEAAAsAENoBAAD9AgAw4AFAAMgCACHvAQEAxwIAIYUCQADIAgAhjAIBAMcCACGOAgAA_gKOAiKjAgAA_AIAIAIAAAANACAVAADMAwAgAgAAAMoDACAVAADLAwAgCNgBAADJAwAw2QEAAMoDABDaAQAAyQMAMOABQADIAgAh7wEBAMcCACGFAkAAyAIAIYwCAQDHAgAhjgIAAP4CjgIiCNgBAADJAwAw2QEAAMoDABDaAQAAyQMAMOABQADIAgAh7wEBAMcCACGFAkAAyAIAIYwCAQDHAgAhjgIAAP4CjgIiBOABQACNAwAh7wEBAIwDACGFAkAAjQMAIY4CAAC3A44CIgTgAUAAjQMAIe8BAQCMAwAhhQJAAI0DACGOAgAAtwOOAiIE4AFAAAAAAe8BAQAAAAGFAkAAAAABjgIAAACOAgIDHAAAvwQAIKkCAADABAAgrwIAAAEAIAQcAADCAwAwqQIAAMMDADCrAgAAxQMAIK8CAADGAwAwAAAAAAAAAAAABxwAALQEACAdAAC9BAAgqQIAALUEACCqAgAAvAQAIK0CAAADACCuAgAAAwAgrwIAAAEAIAscAACZBAAwHQAAngQAMKkCAACaBAAwqgIAAJsEADCrAgAAnAQAIKwCAACdBAAwrQIAAJ0EADCuAgAAnQQAMK8CAACdBAAwsAIAAJ8EADCxAgAAoAQAMAscAACNBAAwHQAAkgQAMKkCAACOBAAwqgIAAI8EADCrAgAAkAQAIKwCAACRBAAwrQIAAJEEADCuAgAAkQQAMK8CAACRBAAwsAIAAJMEADCxAgAAlAQAMAscAACEBAAwHQAAiAQAMKkCAACFBAAwqgIAAIYEADCrAgAAhwQAIKwCAAD8AwAwrQIAAPwDADCuAgAA_AMAMK8CAAD8AwAwsAIAAIkEADCxAgAA_wMAMAscAAD4AwAwHQAA_QMAMKkCAAD5AwAwqgIAAPoDADCrAgAA-wMAIKwCAAD8AwAwrQIAAPwDADCuAgAA_AMAMK8CAAD8AwAwsAIAAP4DADCxAgAA_wMAMAscAADsAwAwHQAA8QMAMKkCAADtAwAwqgIAAO4DADCrAgAA7wMAIKwCAADwAwAwrQIAAPADADCuAgAA8AMAMK8CAADwAwAwsAIAAPIDADCxAgAA8wMAMAscAADgAwAwHQAA5QMAMKkCAADhAwAwqgIAAOIDADCrAgAA4wMAIKwCAADkAwAwrQIAAOQDADCuAgAA5AMAMK8CAADkAwAwsAIAAOYDADCxAgAA5wMAMAPvAQEAAAABhAJAAAAAAYUCQAAAAAECAAAAGwAgHAAA6wMAIAMAAAAbACAcAADrAwAgHQAA6gMAIAEVAAC7BAAwCQUAAPcCACDYAQAA9gIAMNkBAAAZABDaAQAA9gIAMO8BAQDHAgAh8QEBAMcCACGEAkAAyAIAIYUCQADIAgAhoQIAAPUCACACAAAAGwAgFQAA6gMAIAIAAADoAwAgFQAA6QMAIAfYAQAA5wMAMNkBAADoAwAQ2gEAAOcDADDvAQEAxwIAIfEBAQDHAgAhhAJAAMgCACGFAkAAyAIAIQfYAQAA5wMAMNkBAADoAwAQ2gEAAOcDADDvAQEAxwIAIfEBAQDHAgAhhAJAAMgCACGFAkAAyAIAIQPvAQEAjAMAIYQCQACNAwAhhQJAAI0DACED7wEBAIwDACGEAkAAjQMAIYUCQACNAwAhA-8BAQAAAAGEAkAAAAABhQJAAAAAAQPgAUAAAAAB7wEBAAAAAYcCIAAAAAECAAAAFwAgHAAA9wMAIAMAAAAXACAcAAD3AwAgHQAA9gMAIAEVAAC6BAAwCQUAAPcCACDYAQAA-QIAMNkBAAAVABDaAQAA-QIAMOABQADIAgAh7wEBAMcCACHxAQEAxwIAIYcCIADeAgAhoQIAAPgCACACAAAAFwAgFQAA9gMAIAIAAAD0AwAgFQAA9QMAIAfYAQAA8wMAMNkBAAD0AwAQ2gEAAPMDADDgAUAAyAIAIe8BAQDHAgAh8QEBAMcCACGHAiAA3gIAIQfYAQAA8wMAMNkBAAD0AwAQ2gEAAPMDADDgAUAAyAIAIe8BAQDHAgAh8QEBAMcCACGHAiAA3gIAIQPgAUAAjQMAIe8BAQCMAwAhhwIgAKUDACED4AFAAI0DACHvAQEAjAMAIYcCIAClAwAhA-ABQAAAAAHvAQEAAAABhwIgAAAAAQMKAACyAwAgiQIBAAAAAYsCAgAAAAECAAAAEgAgHAAAgwQAIAMAAAASACAcAACDBAAgHQAAggQAIAEVAAC5BAAwCQoAAPcCACALAAD3AgAg2AEAAPsCADDZAQAAEAAQ2gEAAPsCADCJAgEAxwIAIYoCAQDHAgAhiwICANYCACGiAgAA-gIAIAIAAAASACAVAACCBAAgAgAAAIAEACAVAACBBAAgBtgBAAD_AwAw2QEAAIAEABDaAQAA_wMAMIkCAQDHAgAhigIBAMcCACGLAgIA1gIAIQbYAQAA_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_AwAhoAIBAL4DACEUBAAA2gMAIAkAANsDACAMAADcAwAgDQAA3QMAIA4AAN4DACAPAADfAwAg4AFAAI0DACHuAQEAvgMAIYUCQACNAwAhhgIBAL4DACGPAgEAjAMAIZICAQCMAwAhkwIBAIwDACGVAkAAvwMAIZYCAQC-AwAhnAIBAIwDACGdAiAApQMAIZ4CIAClAwAhnwJAAL8DACGgAgEAvgMAIRQEAAClBAAgCQAApgQAIAwAAKcEACANAACoBAAgDgAAqQQAIA8AAKoEACDgAUAAAAAB7gEBAAAAAYUCQAAAAAGGAgEAAAABjwIBAAAAAZICAQAAAAGTAgEAAAABlQJAAAAAAZYCAQAAAAGcAgEAAAABnQIgAAAAAZ4CIAAAAAGfAkAAAAABoAIBAAAAAQQcAACZBAAwqQIAAJoEADCrAgAAnAQAIK8CAACdBAAwBBwAAI0EADCpAgAAjgQAMKsCAACQBAAgrwIAAJEEADAEHAAAhAQAMKkCAACFBAAwqwIAAIcEACCvAgAA_AMAMAQcAAD4AwAwqQIAAPkDADCrAgAA-wMAIK8CAAD8AwAwBBwAAOwDADCpAgAA7QMAMKsCAADvAwAgrwIAAPADADAEHAAA4AMAMKkCAADhAwAwqwIAAOMDACCvAgAA5AMAMAMcAAC0BAAgqQIAALUEACCvAgAAAQAgDgEAAKwEACAEAACvBAAgCQAAsAQAIAwAALEEACANAACxBAAgDgAAsgQAIA8AALMEACDuAQAAugMAIIYCAAC6AwAglQIAALoDACCWAgAAugMAIJsCAAC6AwAgnwIAALoDACCgAgAAugMAIAUFAACsBAAgBwAArgQAIJQCAAC6AwAglQIAALoDACCWAgAAugMAIAAAAAAAABUBAACrBAAgCQAApgQAIAwAAKcEACANAACoBAAgDgAAqQQAIA8AAKoEACDgAUAAAAAB7gEBAAAAAYUCQAAAAAGGAgEAAAABjwIBAAAAAZICAQAAAAGTAgEAAAABlQJAAAAAAZYCAQAAAAGbAgEAAAABnAIBAAAAAZ0CIAAAAAGeAiAAAAABnwJAAAAAAaACAQAAAAECAAAAAQAgHAAAtAQAIA7gAUAAAAAB7gEBAAAAAYUCQAAAAAGGAgEAAAABjwIBAAAAAZICAQAAAAGTAgEAAAABlQJAAAAAAZYCAQAAAAGcAgEAAAABnQIgAAAAAZ4CIAAAAAGfAkAAAAABoAIBAAAAAQrgAUAAAAABhQJAAAAAAY8CAQAAAAGQAgEAAAABkQIBAAAAAZICAQAAAAGTAgEAAAABlAIBAAAAAZUCQAAAAAGWAgEAAAABAooCAQAAAAGLAgIAAAABAokCAQAAAAGLAgIAAAABA-ABQAAAAAHvAQEAAAABhwIgAAAAAQPvAQEAAAABhAJAAAAAAYUCQAAAAAEDAAAAAwAgHAAAtAQAIB0AAL4EACAXAAAAAwAgAQAA2QMAIAkAANsDACAMAADcAwAgDQAA3QMAIA4AAN4DACAPAADfAwAgFQAAvgQAIOABQACNAwAh7gEBAL4DACGFAkAAjQMAIYYCAQC-AwAhjwIBAIwDACGSAgEAjAMAIZMCAQCMAwAhlQJAAL8DACGWAgEAvgMAIZsCAQC-AwAhnAIBAIwDACGdAiAApQMAIZ4CIAClAwAhnwJAAL8DACGgAgEAvgMAIRUBAADZAwAgCQAA2wMAIAwAANwDACANAADdAwAgDgAA3gMAIA8AAN8DACDgAUAAjQMAIe4BAQC-AwAhhQJAAI0DACGGAgEAvgMAIY8CAQCMAwAhkgIBAIwDACGTAgEAjAMAIZUCQAC_AwAhlgIBAL4DACGbAgEAvgMAIZwCAQCMAwAhnQIgAKUDACGeAiAApQMAIZ8CQAC_AwAhoAIBAL4DACEVAQAAqwQAIAQAAKUEACAMAACnBAAgDQAAqAQAIA4AAKkEACAPAACqBAAg4AFAAAAAAe4BAQAAAAGFAkAAAAABhgIBAAAAAY8CAQAAAAGSAgEAAAABkwIBAAAAAZUCQAAAAAGWAgEAAAABmwIBAAAAAZwCAQAAAAGdAiAAAAABngIgAAAAAZ8CQAAAAAGgAgEAAAABAgAAAAEAIBwAAL8EACAE4AFAAAAAAe8BAQAAAAGFAkAAAAABjgIAAACOAgIDAAAAAwAgHAAAvwQAIB0AAMQEACAXAAAAAwAgAQAA2QMAIAQAANoDACAMAADcAwAgDQAA3QMAIA4AAN4DACAPAADfAwAgFQAAxAQAIOABQACNAwAh7gEBAL4DACGFAkAAjQMAIYYCAQC-AwAhjwIBAIwDACGSAgEAjAMAIZMCAQCMAwAhlQJAAL8DACGWAgEAvgMAIZsCAQC-AwAhnAIBAIwDACGdAiAApQMAIZ4CIAClAwAhnwJAAL8DACGgAgEAvgMAIRUBAADZAwAgBAAA2gMAIAwAANwDACANAADdAwAgDgAA3gMAIA8AAN8DACDgAUAAjQMAIe4BAQC-AwAhhQJAAI0DACGGAgEAvgMAIY8CAQCMAwAhkgIBAIwDACGTAgEAjAMAIZUCQAC_AwAhlgIBAL4DACGbAgEAvgMAIZwCAQCMAwAhnQIgAKUDACGeAiAApQMAIZ8CQAC_AwAhoAIBAL4DACEMBQAAzgMAIOABQAAAAAHxAQEAAAABhQJAAAAAAY8CAQAAAAGQAgEAAAABkQIBAAAAAZICAQAAAAGTAgEAAAABlAIBAAAAAZUCQAAAAAGWAgEAAAABAgAAAAkAIBwAAMUEACADAAAABwAgHAAAxQQAIB0AAMkEACAOAAAABwAgBQAAwAMAIBUAAMkEACDgAUAAjQMAIfEBAQCMAwAhhQJAAI0DACGPAgEAjAMAIZACAQCMAwAhkQIBAIwDACGSAgEAjAMAIZMCAQCMAwAhlAIBAL4DACGVAkAAvwMAIZYCAQC-AwAhDAUAAMADACDgAUAAjQMAIfEBAQCMAwAhhQJAAI0DACGPAgEAjAMAIZACAQCMAwAhkQIBAIwDACGSAgEAjAMAIZMCAQCMAwAhlAIBAL4DACGVAkAAvwMAIZYCAQC-AwAhFQEAAKsEACAEAAClBAAgCQAApgQAIAwAAKcEACAOAACpBAAgDwAAqgQAIOABQAAAAAHuAQEAAAABhQJAAAAAAYYCAQAAAAGPAgEAAAABkgIBAAAAAZMCAQAAAAGVAkAAAAABlgIBAAAAAZsCAQAAAAGcAgEAAAABnQIgAAAAAZ4CIAAAAAGfAkAAAAABoAIBAAAAAQIAAAABACAcAADKBAAgFQEAAKsEACAEAAClBAAgCQAApgQAIA0AAKgEACAOAACpBAAgDwAAqgQAIOABQAAAAAHuAQEAAAABhQJAAAAAAYYCAQAAAAGPAgEAAAABkgIBAAAAAZMCAQAAAAGVAkAAAAABlgIBAAAAAZsCAQAAAAGcAgEAAAABnQIgAAAAAZ4CIAAAAAGfAkAAAAABoAIBAAAAAQIAAAABACAcAADMBAAgAwAAAAMAIBwAAMoEACAdAADQBAAgFwAAAAMAIAEAANkDACAEAADaAwAgCQAA2wMAIAwAANwDACAOAADeAwAgDwAA3wMAIBUAANAEACDgAUAAjQMAIe4BAQC-AwAhhQJAAI0DACGGAgEAvgMAIY8CAQCMAwAhkgIBAIwDACGTAgEAjAMAIZUCQAC_AwAhlgIBAL4DACGbAgEAvgMAIZwCAQCMAwAhnQIgAKUDACGeAiAApQMAIZ8CQAC_AwAhoAIBAL4DACEVAQAA2QMAIAQAANoDACAJAADbAwAgDAAA3AMAIA4AAN4DACAPAADfAwAg4AFAAI0DACHuAQEAvgMAIYUCQACNAwAhhgIBAL4DACGPAgEAjAMAIZICAQCMAwAhkwIBAIwDACGVAkAAvwMAIZYCAQC-AwAhmwIBAL4DACGcAgEAjAMAIZ0CIAClAwAhngIgAKUDACGfAkAAvwMAIaACAQC-AwAhAwAAAAMAIBwAAMwEACAdAADTBAAgFwAAAAMAIAEAANkDACAEAADaAwAgCQAA2wMAIA0AAN0DACAOAADeAwAgDwAA3wMAIBUAANMEACDgAUAAjQMAIe4BAQC-AwAhhQJAAI0DACGGAgEAvgMAIY8CAQCMAwAhkgIBAIwDACGTAgEAjAMAIZUCQAC_AwAhlgIBAL4DACGbAgEAvgMAIZwCAQCMAwAhnQIgAKUDACGeAiAApQMAIZ8CQAC_AwAhoAIBAL4DACEVAQAA2QMAIAQAANoDACAJAADbAwAgDQAA3QMAIA4AAN4DACAPAADfAwAg4AFAAI0DACHuAQEAvgMAIYUCQACNAwAhhgIBAL4DACGPAgEAjAMAIZICAQCMAwAhkwIBAIwDACGVAkAAvwMAIZYCAQC-AwAhmwIBAL4DACGcAgEAjAMAIZ0CIAClAwAhngIgAKUDACGfAkAAvwMAIaACAQC-AwAhFQEAAKsEACAEAAClBAAgCQAApgQAIAwAAKcEACANAACoBAAgDwAAqgQAIOABQAAAAAHuAQEAAAABhQJAAAAAAYYCAQAAAAGPAgEAAAABkgIBAAAAAZMCAQAAAAGVAkAAAAABlgIBAAAAAZsCAQAAAAGcAgEAAAABnQIgAAAAAZ4CIAAAAAGfAkAAAAABoAIBAAAAAQIAAAABACAcAADUBAAgAwAAAAMAIBwAANQEACAdAADYBAAgFwAAAAMAIAEAANkDACAEAADaAwAgCQAA2wMAIAwAANwDACANAADdAwAgDwAA3wMAIBUAANgEACDgAUAAjQMAIe4BAQC-AwAhhQJAAI0DACGGAgEAvgMAIY8CAQCMAwAhkgIBAIwDACGTAgEAjAMAIZUCQAC_AwAhlgIBAL4DACGbAgEAvgMAIZwCAQCMAwAhnQIgAKUDACGeAiAApQMAIZ8CQAC_AwAhoAIBAL4DACEVAQAA2QMAIAQAANoDACAJAADbAwAgDAAA3AMAIA0AAN0DACAPAADfAwAg4AFAAI0DACHuAQEAvgMAIYUCQACNAwAhhgIBAL4DACGPAgEAjAMAIZICAQCMAwAhkwIBAIwDACGVAkAAvwMAIZYCAQC-AwAhmwIBAL4DACGcAgEAjAMAIZ0CIAClAwAhngIgAKUDACGfAkAAvwMAIaACAQC-AwAhFQEAAKsEACAEAAClBAAgCQAApgQAIAwAAKcEACANAACoBAAgDgAAqQQAIOABQAAAAAHuAQEAAAABhQJAAAAAAYYCAQAAAAGPAgEAAAABkgIBAAAAAZMCAQAAAAGVAkAAAAABlgIBAAAAAZsCAQAAAAGcAgEAAAABnQIgAAAAAZ4CIAAAAAGfAkAAAAABoAIBAAAAAQIAAAABACAcAADZBAAgAwAAAAMAIBwAANkEACAdAADdBAAgFwAAAAMAIAEAANkDACAEAADaAwAgCQAA2wMAIAwAANwDACANAADdAwAgDgAA3gMAIBUAAN0EACDgAUAAjQMAIe4BAQC-AwAhhQJAAI0DACGGAgEAvgMAIY8CAQCMAwAhkgIBAIwDACGTAgEAjAMAIZUCQAC_AwAhlgIBAL4DACGbAgEAvgMAIZwCAQCMAwAhnQIgAKUDACGeAiAApQMAIZ8CQAC_AwAhoAIBAL4DACEVAQAA2QMAIAQAANoDACAJAADbAwAgDAAA3AMAIA0AAN0DACAOAADeAwAg4AFAAI0DACHuAQEAvgMAIYUCQACNAwAhhgIBAL4DACGPAgEAjAMAIZICAQCMAwAhkwIBAIwDACGVAkAAvwMAIZYCAQC-AwAhmwIBAL4DACGcAgEAjAMAIZ0CIAClAwAhngIgAKUDACGfAkAAvwMAIaACAQC-AwAhCAEEAQQGAQgACAkKAgwTBQ0UBQ4YBg8cBwMFAAEHDgMIAAQBBgACAQcPAAIKAAELAAEBBQABAQUAAQYEHQAJHgAMHwANIAAOIQAPIgAAAQEsAQEBMgEDCAANIgAOIwAPAAAAAwgADSIADiMADwAAAAMIABUiABYjABcAAAADCAAVIgAWIwAXAAAAAwgAHSIAHiMAHwAAAAMIAB0iAB4jAB8BBQABAQUAAQMIACQiACUjACYAAAADCAAkIgAlIwAmAQYAAgEGAAIDCAArIgAsIwAtAAAAAwgAKyIALCMALQIKAAELAAECCgABCwABBQgAMiIANSMANnQAM3UANAAAAAAABQgAMiIANSMANnQAM3UANAEFAAEBBQABAwgAOyIAPCMAPQAAAAMIADsiADwjAD0AAAADCABDIgBEIwBFAAAAAwgAQyIARCMARQEFAAEBBQABAwgASiIASyMATAAAAAMIAEoiAEsjAEwAAAAFCABSIgBVIwBWdABTdQBUAAAAAAAFCABSIgBVIwBWdABTdQBUAAAAAwgAXCIAXSMAXgAAAAMIAFwiAF0jAF4AAAADCABkIgBlIwBmAAAAAwgAZCIAZSMAZhACAREjARIkARMlARQmARYoARcqCRgrChkuARowCRsxCx4zAR80ASA1CSQ4DCU5ECY7ESc8ESg_ESlAESpBEStDESxFCS1GEi5IES9KCTBLEzFMETJNETNOCTRRFDVSGDZUGTdVGThYGTlZGTpaGTtcGTxeCT1fGj5hGT9jCUBkG0FlGUJmGUNnCURqHEVrIEZsAkdtAkhuAklvAkpwAktyAkx0CU11IU53Ak95CVB6IlF7AlJ8AlN9CVSAASNVgQEnVoIBA1eDAQNYhAEDWYUBA1qGAQNbiAEDXIoBCV2LAShejQEDX48BCWCQASlhkQEDYpIBA2OTAQlklgEqZZcBLmaYAQVnmQEFaJoBBWmbAQVqnAEFa54BBWygAQltoQEvbqMBBW-lAQlwpgEwcacBBXKoAQVzqQEJdqwBMXetATd4rgEGea8BBnqwAQZ7sQEGfLIBBn20AQZ-tgEJf7cBOIABuQEGgQG7AQmCAbwBOYMBvQEGhAG-AQaFAb8BCYYBwgE6hwHDAT6IAcUBP4kBxgE_igHJAT-LAcoBP4wBywE_jQHNAT-OAc8BCY8B0AFAkAHSAT-RAdQBCZIB1QFBkwHWAT-UAdcBP5UB2AEJlgHbAUKXAdwBRpgB3QEHmQHeAQeaAd8BB5sB4AEHnAHhAQedAeMBB54B5QEJnwHmAUegAegBB6EB6gEJogHrAUijAewBB6QB7QEHpQHuAQmmAfEBSacB8gFNqAH0AU6pAfUBTqoB-AFOqwH5AU6sAfoBTq0B_AFOrgH-AQmvAf8BT7ABgQJOsQGDAgmyAYQCULMBhQJOtAGGAk61AYcCCbYBigJRtwGLAle4AY0CWLkBjgJYugGRAli7AZICWLwBkwJYvQGVAli-AZcCCb8BmAJZwAGaAljBAZwCCcIBnQJawwGeAljEAZ8CWMUBoAIJxgGjAlvHAaQCX8gBpgJgyQGnAmDKAaoCYMsBqwJgzAGsAmDNAa4CYM4BsAIJzwGxAmHQAbMCYNEBtQIJ0gG2AmLTAbcCYNQBuAJg1QG5AgnWAbwCY9cBvQJn" } 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 3338bd5..4ff19d0 100644 --- a/packages/forums-prisma-client/index-browser.js +++ b/packages/forums-prisma-client/index-browser.js @@ -187,9 +187,18 @@ exports.Prisma.TopicClosureScalarFieldEnum = { exports.Prisma.TopicWatchScalarFieldEnum = { topicId: 'topicId', memberId: 'memberId', + watching: 'watching', createdAt: 'createdAt' }; +exports.Prisma.ChallengeForumWatchScalarFieldEnum = { + challengeId: 'challengeId', + memberId: 'memberId', + watching: 'watching', + createdAt: 'createdAt', + updatedAt: 'updatedAt' +}; + exports.Prisma.TopicReadStateScalarFieldEnum = { topicId: 'topicId', memberId: 'memberId', @@ -261,6 +270,7 @@ exports.Prisma.ModelName = { PostReaction: 'PostReaction', TopicClosure: 'TopicClosure', TopicWatch: 'TopicWatch', + ChallengeForumWatch: 'ChallengeForumWatch', TopicReadState: 'TopicReadState', PublicForumCategory: 'PublicForumCategory', PublicForumRoleMember: 'PublicForumRoleMember', diff --git a/packages/forums-prisma-client/index.d.ts b/packages/forums-prisma-client/index.d.ts index 6bbdd0e..c3d5c75 100644 --- a/packages/forums-prisma-client/index.d.ts +++ b/packages/forums-prisma-client/index.d.ts @@ -48,6 +48,11 @@ export type TopicClosure = $Result.DefaultSelection * */ export type TopicWatch = $Result.DefaultSelection +/** + * Model ChallengeForumWatch + * + */ +export type ChallengeForumWatch = $Result.DefaultSelection /** * Model TopicReadState * @@ -277,6 +282,16 @@ export class PrismaClient< */ get topicWatch(): Prisma.TopicWatchDelegate; + /** + * `prisma.challengeForumWatch`: Exposes CRUD operations for the **ChallengeForumWatch** model. + * Example usage: + * ```ts + * // Fetch zero or more ChallengeForumWatches + * const challengeForumWatches = await prisma.challengeForumWatch.findMany() + * ``` + */ + get challengeForumWatch(): Prisma.ChallengeForumWatchDelegate; + /** * `prisma.topicReadState`: Exposes CRUD operations for the **TopicReadState** model. * Example usage: @@ -770,6 +785,7 @@ export namespace Prisma { PostReaction: 'PostReaction', TopicClosure: 'TopicClosure', TopicWatch: 'TopicWatch', + ChallengeForumWatch: 'ChallengeForumWatch', TopicReadState: 'TopicReadState', PublicForumCategory: 'PublicForumCategory', PublicForumRoleMember: 'PublicForumRoleMember', @@ -789,7 +805,7 @@ export namespace Prisma { omit: GlobalOmitOptions } meta: { - modelProps: "topic" | "memberBan" | "ipBan" | "post" | "postReaction" | "topicClosure" | "topicWatch" | "topicReadState" | "publicForumCategory" | "publicForumRoleMember" | "publicForumImportRecord" + modelProps: "topic" | "memberBan" | "ipBan" | "post" | "postReaction" | "topicClosure" | "topicWatch" | "challengeForumWatch" | "topicReadState" | "publicForumCategory" | "publicForumRoleMember" | "publicForumImportRecord" txIsolationLevel: Prisma.TransactionIsolationLevel } model: { @@ -1311,6 +1327,80 @@ export namespace Prisma { } } } + ChallengeForumWatch: { + payload: Prisma.$ChallengeForumWatchPayload + fields: Prisma.ChallengeForumWatchFieldRefs + operations: { + findUnique: { + args: Prisma.ChallengeForumWatchFindUniqueArgs + result: $Utils.PayloadToResult | null + } + findUniqueOrThrow: { + args: Prisma.ChallengeForumWatchFindUniqueOrThrowArgs + result: $Utils.PayloadToResult + } + findFirst: { + args: Prisma.ChallengeForumWatchFindFirstArgs + result: $Utils.PayloadToResult | null + } + findFirstOrThrow: { + args: Prisma.ChallengeForumWatchFindFirstOrThrowArgs + result: $Utils.PayloadToResult + } + findMany: { + args: Prisma.ChallengeForumWatchFindManyArgs + result: $Utils.PayloadToResult[] + } + create: { + args: Prisma.ChallengeForumWatchCreateArgs + result: $Utils.PayloadToResult + } + createMany: { + args: Prisma.ChallengeForumWatchCreateManyArgs + result: BatchPayload + } + createManyAndReturn: { + args: Prisma.ChallengeForumWatchCreateManyAndReturnArgs + result: $Utils.PayloadToResult[] + } + delete: { + args: Prisma.ChallengeForumWatchDeleteArgs + result: $Utils.PayloadToResult + } + update: { + args: Prisma.ChallengeForumWatchUpdateArgs + result: $Utils.PayloadToResult + } + deleteMany: { + args: Prisma.ChallengeForumWatchDeleteManyArgs + result: BatchPayload + } + updateMany: { + args: Prisma.ChallengeForumWatchUpdateManyArgs + result: BatchPayload + } + updateManyAndReturn: { + args: Prisma.ChallengeForumWatchUpdateManyAndReturnArgs + result: $Utils.PayloadToResult[] + } + upsert: { + args: Prisma.ChallengeForumWatchUpsertArgs + result: $Utils.PayloadToResult + } + aggregate: { + args: Prisma.ChallengeForumWatchAggregateArgs + result: $Utils.Optional + } + groupBy: { + args: Prisma.ChallengeForumWatchGroupByArgs + result: $Utils.Optional[] + } + count: { + args: Prisma.ChallengeForumWatchCountArgs + result: $Utils.Optional | number + } + } + } TopicReadState: { payload: Prisma.$TopicReadStatePayload fields: Prisma.TopicReadStateFieldRefs @@ -1737,6 +1827,7 @@ export namespace Prisma { postReaction?: PostReactionOmit topicClosure?: TopicClosureOmit topicWatch?: TopicWatchOmit + challengeForumWatch?: ChallengeForumWatchOmit topicReadState?: TopicReadStateOmit publicForumCategory?: PublicForumCategoryOmit publicForumRoleMember?: PublicForumRoleMemberOmit @@ -8661,18 +8752,21 @@ export namespace Prisma { export type TopicWatchMinAggregateOutputType = { topicId: string | null memberId: string | null + watching: boolean | null createdAt: Date | null } export type TopicWatchMaxAggregateOutputType = { topicId: string | null memberId: string | null + watching: boolean | null createdAt: Date | null } export type TopicWatchCountAggregateOutputType = { topicId: number memberId: number + watching: number createdAt: number _all: number } @@ -8681,18 +8775,21 @@ export namespace Prisma { export type TopicWatchMinAggregateInputType = { topicId?: true memberId?: true + watching?: true createdAt?: true } export type TopicWatchMaxAggregateInputType = { topicId?: true memberId?: true + watching?: true createdAt?: true } export type TopicWatchCountAggregateInputType = { topicId?: true memberId?: true + watching?: true createdAt?: true _all?: true } @@ -8772,6 +8869,7 @@ export namespace Prisma { export type TopicWatchGroupByOutputType = { topicId: string memberId: string + watching: boolean createdAt: Date _count: TopicWatchCountAggregateOutputType | null _min: TopicWatchMinAggregateOutputType | null @@ -8795,6 +8893,7 @@ export namespace Prisma { export type TopicWatchSelect = $Extensions.GetSelect<{ topicId?: boolean memberId?: boolean + watching?: boolean createdAt?: boolean topic?: boolean | TopicDefaultArgs }, ExtArgs["result"]["topicWatch"]> @@ -8802,6 +8901,7 @@ export namespace Prisma { export type TopicWatchSelectCreateManyAndReturn = $Extensions.GetSelect<{ topicId?: boolean memberId?: boolean + watching?: boolean createdAt?: boolean topic?: boolean | TopicDefaultArgs }, ExtArgs["result"]["topicWatch"]> @@ -8809,6 +8909,7 @@ export namespace Prisma { export type TopicWatchSelectUpdateManyAndReturn = $Extensions.GetSelect<{ topicId?: boolean memberId?: boolean + watching?: boolean createdAt?: boolean topic?: boolean | TopicDefaultArgs }, ExtArgs["result"]["topicWatch"]> @@ -8816,10 +8917,11 @@ export namespace Prisma { export type TopicWatchSelectScalar = { topicId?: boolean memberId?: boolean + watching?: boolean createdAt?: boolean } - export type TopicWatchOmit = $Extensions.GetOmit<"topicId" | "memberId" | "createdAt", ExtArgs["result"]["topicWatch"]> + export type TopicWatchOmit = $Extensions.GetOmit<"topicId" | "memberId" | "watching" | "createdAt", ExtArgs["result"]["topicWatch"]> export type TopicWatchInclude = { topic?: boolean | TopicDefaultArgs } @@ -8838,6 +8940,7 @@ export namespace Prisma { scalars: $Extensions.GetPayloadResult<{ topicId: string memberId: string + watching: boolean createdAt: Date }, ExtArgs["result"]["topicWatch"]> composites: {} @@ -9265,6 +9368,7 @@ export namespace Prisma { interface TopicWatchFieldRefs { readonly topicId: FieldRef<"TopicWatch", 'String'> readonly memberId: FieldRef<"TopicWatch", 'String'> + readonly watching: FieldRef<"TopicWatch", 'Boolean'> readonly createdAt: FieldRef<"TopicWatch", 'DateTime'> } @@ -9686,339 +9790,337 @@ export namespace Prisma { /** - * Model TopicReadState + * Model ChallengeForumWatch */ - export type AggregateTopicReadState = { - _count: TopicReadStateCountAggregateOutputType | null - _min: TopicReadStateMinAggregateOutputType | null - _max: TopicReadStateMaxAggregateOutputType | null + export type AggregateChallengeForumWatch = { + _count: ChallengeForumWatchCountAggregateOutputType | null + _min: ChallengeForumWatchMinAggregateOutputType | null + _max: ChallengeForumWatchMaxAggregateOutputType | null } - export type TopicReadStateMinAggregateOutputType = { - topicId: string | null + export type ChallengeForumWatchMinAggregateOutputType = { + challengeId: string | null memberId: string | null - lastReadAt: Date | null + watching: boolean | null + createdAt: Date | null updatedAt: Date | null } - export type TopicReadStateMaxAggregateOutputType = { - topicId: string | null + export type ChallengeForumWatchMaxAggregateOutputType = { + challengeId: string | null memberId: string | null - lastReadAt: Date | null + watching: boolean | null + createdAt: Date | null updatedAt: Date | null } - export type TopicReadStateCountAggregateOutputType = { - topicId: number + export type ChallengeForumWatchCountAggregateOutputType = { + challengeId: number memberId: number - lastReadAt: number + watching: number + createdAt: number updatedAt: number _all: number } - export type TopicReadStateMinAggregateInputType = { - topicId?: true + export type ChallengeForumWatchMinAggregateInputType = { + challengeId?: true memberId?: true - lastReadAt?: true + watching?: true + createdAt?: true updatedAt?: true } - export type TopicReadStateMaxAggregateInputType = { - topicId?: true + export type ChallengeForumWatchMaxAggregateInputType = { + challengeId?: true memberId?: true - lastReadAt?: true + watching?: true + createdAt?: true updatedAt?: true } - export type TopicReadStateCountAggregateInputType = { - topicId?: true + export type ChallengeForumWatchCountAggregateInputType = { + challengeId?: true memberId?: true - lastReadAt?: true + watching?: true + createdAt?: true updatedAt?: true _all?: true } - export type TopicReadStateAggregateArgs = { + export type ChallengeForumWatchAggregateArgs = { /** - * Filter which TopicReadState to aggregate. + * Filter which ChallengeForumWatch to aggregate. */ - where?: TopicReadStateWhereInput + where?: ChallengeForumWatchWhereInput /** * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} * - * Determine the order of TopicReadStates to fetch. + * Determine the order of ChallengeForumWatches to fetch. */ - orderBy?: TopicReadStateOrderByWithRelationInput | TopicReadStateOrderByWithRelationInput[] + orderBy?: ChallengeForumWatchOrderByWithRelationInput | ChallengeForumWatchOrderByWithRelationInput[] /** * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} * * Sets the start position */ - cursor?: TopicReadStateWhereUniqueInput + cursor?: ChallengeForumWatchWhereUniqueInput /** * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} * - * Take `±n` TopicReadStates from the position of the cursor. + * Take `±n` ChallengeForumWatches 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` TopicReadStates. + * Skip the first `n` ChallengeForumWatches. */ skip?: number /** * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} * - * Count returned TopicReadStates + * Count returned ChallengeForumWatches **/ - _count?: true | TopicReadStateCountAggregateInputType + _count?: true | ChallengeForumWatchCountAggregateInputType /** * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} * * Select which fields to find the minimum value **/ - _min?: TopicReadStateMinAggregateInputType + _min?: ChallengeForumWatchMinAggregateInputType /** * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} * * Select which fields to find the maximum value **/ - _max?: TopicReadStateMaxAggregateInputType + _max?: ChallengeForumWatchMaxAggregateInputType } - export type GetTopicReadStateAggregateType = { - [P in keyof T & keyof AggregateTopicReadState]: P extends '_count' | 'count' + export type GetChallengeForumWatchAggregateType = { + [P in keyof T & keyof AggregateChallengeForumWatch]: P extends '_count' | 'count' ? T[P] extends true ? number - : GetScalarType - : GetScalarType + : GetScalarType + : GetScalarType } - export type TopicReadStateGroupByArgs = { - where?: TopicReadStateWhereInput - orderBy?: TopicReadStateOrderByWithAggregationInput | TopicReadStateOrderByWithAggregationInput[] - by: TopicReadStateScalarFieldEnum[] | TopicReadStateScalarFieldEnum - having?: TopicReadStateScalarWhereWithAggregatesInput + export type ChallengeForumWatchGroupByArgs = { + where?: ChallengeForumWatchWhereInput + orderBy?: ChallengeForumWatchOrderByWithAggregationInput | ChallengeForumWatchOrderByWithAggregationInput[] + by: ChallengeForumWatchScalarFieldEnum[] | ChallengeForumWatchScalarFieldEnum + having?: ChallengeForumWatchScalarWhereWithAggregatesInput take?: number skip?: number - _count?: TopicReadStateCountAggregateInputType | true - _min?: TopicReadStateMinAggregateInputType - _max?: TopicReadStateMaxAggregateInputType + _count?: ChallengeForumWatchCountAggregateInputType | true + _min?: ChallengeForumWatchMinAggregateInputType + _max?: ChallengeForumWatchMaxAggregateInputType } - export type TopicReadStateGroupByOutputType = { - topicId: string + export type ChallengeForumWatchGroupByOutputType = { + challengeId: string memberId: string - lastReadAt: Date + watching: boolean + createdAt: Date updatedAt: Date - _count: TopicReadStateCountAggregateOutputType | null - _min: TopicReadStateMinAggregateOutputType | null - _max: TopicReadStateMaxAggregateOutputType | null + _count: ChallengeForumWatchCountAggregateOutputType | null + _min: ChallengeForumWatchMinAggregateOutputType | null + _max: ChallengeForumWatchMaxAggregateOutputType | null } - type GetTopicReadStateGroupByPayload = Prisma.PrismaPromise< + type GetChallengeForumWatchGroupByPayload = Prisma.PrismaPromise< Array< - PickEnumerable & + PickEnumerable & { - [P in ((keyof T) & (keyof TopicReadStateGroupByOutputType))]: P extends '_count' + [P in ((keyof T) & (keyof ChallengeForumWatchGroupByOutputType))]: P extends '_count' ? T[P] extends boolean ? number - : GetScalarType - : GetScalarType + : GetScalarType + : GetScalarType } > > - export type TopicReadStateSelect = $Extensions.GetSelect<{ - topicId?: boolean + export type ChallengeForumWatchSelect = $Extensions.GetSelect<{ + challengeId?: boolean memberId?: boolean - lastReadAt?: boolean + watching?: boolean + createdAt?: boolean updatedAt?: boolean - topic?: boolean | TopicDefaultArgs - }, ExtArgs["result"]["topicReadState"]> + }, ExtArgs["result"]["challengeForumWatch"]> - export type TopicReadStateSelectCreateManyAndReturn = $Extensions.GetSelect<{ - topicId?: boolean + export type ChallengeForumWatchSelectCreateManyAndReturn = $Extensions.GetSelect<{ + challengeId?: boolean memberId?: boolean - lastReadAt?: boolean + watching?: boolean + createdAt?: boolean updatedAt?: boolean - topic?: boolean | TopicDefaultArgs - }, ExtArgs["result"]["topicReadState"]> + }, ExtArgs["result"]["challengeForumWatch"]> - export type TopicReadStateSelectUpdateManyAndReturn = $Extensions.GetSelect<{ - topicId?: boolean + export type ChallengeForumWatchSelectUpdateManyAndReturn = $Extensions.GetSelect<{ + challengeId?: boolean memberId?: boolean - lastReadAt?: boolean + watching?: boolean + createdAt?: boolean updatedAt?: boolean - topic?: boolean | TopicDefaultArgs - }, ExtArgs["result"]["topicReadState"]> + }, ExtArgs["result"]["challengeForumWatch"]> - export type TopicReadStateSelectScalar = { - topicId?: boolean + export type ChallengeForumWatchSelectScalar = { + challengeId?: boolean memberId?: boolean - lastReadAt?: boolean + watching?: boolean + createdAt?: boolean updatedAt?: boolean } - export type TopicReadStateOmit = $Extensions.GetOmit<"topicId" | "memberId" | "lastReadAt" | "updatedAt", ExtArgs["result"]["topicReadState"]> - export type TopicReadStateInclude = { - topic?: boolean | TopicDefaultArgs - } - export type TopicReadStateIncludeCreateManyAndReturn = { - topic?: boolean | TopicDefaultArgs - } - export type TopicReadStateIncludeUpdateManyAndReturn = { - topic?: boolean | TopicDefaultArgs - } + export type ChallengeForumWatchOmit = $Extensions.GetOmit<"challengeId" | "memberId" | "watching" | "createdAt" | "updatedAt", ExtArgs["result"]["challengeForumWatch"]> - export type $TopicReadStatePayload = { - name: "TopicReadState" - objects: { - topic: Prisma.$TopicPayload - } + export type $ChallengeForumWatchPayload = { + name: "ChallengeForumWatch" + objects: {} scalars: $Extensions.GetPayloadResult<{ - topicId: string + challengeId: string memberId: string - lastReadAt: Date + watching: boolean + createdAt: Date updatedAt: Date - }, ExtArgs["result"]["topicReadState"]> + }, ExtArgs["result"]["challengeForumWatch"]> composites: {} } - type TopicReadStateGetPayload = $Result.GetResult + type ChallengeForumWatchGetPayload = $Result.GetResult - type TopicReadStateCountArgs = - Omit & { - select?: TopicReadStateCountAggregateInputType | true + type ChallengeForumWatchCountArgs = + Omit & { + select?: ChallengeForumWatchCountAggregateInputType | true } - export interface TopicReadStateDelegate { - [K: symbol]: { types: Prisma.TypeMap['model']['TopicReadState'], meta: { name: 'TopicReadState' } } + export interface ChallengeForumWatchDelegate { + [K: symbol]: { types: Prisma.TypeMap['model']['ChallengeForumWatch'], meta: { name: 'ChallengeForumWatch' } } /** - * Find zero or one TopicReadState that matches the filter. - * @param {TopicReadStateFindUniqueArgs} args - Arguments to find a TopicReadState + * Find zero or one ChallengeForumWatch that matches the filter. + * @param {ChallengeForumWatchFindUniqueArgs} args - Arguments to find a ChallengeForumWatch * @example - * // Get one TopicReadState - * const topicReadState = await prisma.topicReadState.findUnique({ + * // Get one ChallengeForumWatch + * const challengeForumWatch = await prisma.challengeForumWatch.findUnique({ * where: { * // ... provide filter here * } * }) */ - findUnique(args: SelectSubset>): Prisma__TopicReadStateClient<$Result.GetResult, T, "findUnique", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> + findUnique(args: SelectSubset>): Prisma__ChallengeForumWatchClient<$Result.GetResult, T, "findUnique", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> /** - * Find one TopicReadState that matches the filter or throw an error with `error.code='P2025'` + * Find one ChallengeForumWatch that matches the filter or throw an error with `error.code='P2025'` * if no matches were found. - * @param {TopicReadStateFindUniqueOrThrowArgs} args - Arguments to find a TopicReadState + * @param {ChallengeForumWatchFindUniqueOrThrowArgs} args - Arguments to find a ChallengeForumWatch * @example - * // Get one TopicReadState - * const topicReadState = await prisma.topicReadState.findUniqueOrThrow({ + * // Get one ChallengeForumWatch + * const challengeForumWatch = await prisma.challengeForumWatch.findUniqueOrThrow({ * where: { * // ... provide filter here * } * }) */ - findUniqueOrThrow(args: SelectSubset>): Prisma__TopicReadStateClient<$Result.GetResult, T, "findUniqueOrThrow", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + findUniqueOrThrow(args: SelectSubset>): Prisma__ChallengeForumWatchClient<$Result.GetResult, T, "findUniqueOrThrow", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> /** - * Find the first TopicReadState that matches the filter. + * Find the first ChallengeForumWatch 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 {TopicReadStateFindFirstArgs} args - Arguments to find a TopicReadState + * @param {ChallengeForumWatchFindFirstArgs} args - Arguments to find a ChallengeForumWatch * @example - * // Get one TopicReadState - * const topicReadState = await prisma.topicReadState.findFirst({ + * // Get one ChallengeForumWatch + * const challengeForumWatch = await prisma.challengeForumWatch.findFirst({ * where: { * // ... provide filter here * } * }) */ - findFirst(args?: SelectSubset>): Prisma__TopicReadStateClient<$Result.GetResult, T, "findFirst", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> + findFirst(args?: SelectSubset>): Prisma__ChallengeForumWatchClient<$Result.GetResult, T, "findFirst", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> /** - * Find the first TopicReadState that matches the filter or + * Find the first ChallengeForumWatch 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 {TopicReadStateFindFirstOrThrowArgs} args - Arguments to find a TopicReadState + * @param {ChallengeForumWatchFindFirstOrThrowArgs} args - Arguments to find a ChallengeForumWatch * @example - * // Get one TopicReadState - * const topicReadState = await prisma.topicReadState.findFirstOrThrow({ + * // Get one ChallengeForumWatch + * const challengeForumWatch = await prisma.challengeForumWatch.findFirstOrThrow({ * where: { * // ... provide filter here * } * }) */ - findFirstOrThrow(args?: SelectSubset>): Prisma__TopicReadStateClient<$Result.GetResult, T, "findFirstOrThrow", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + findFirstOrThrow(args?: SelectSubset>): Prisma__ChallengeForumWatchClient<$Result.GetResult, T, "findFirstOrThrow", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> /** - * Find zero or more TopicReadStates that matches the filter. + * Find zero or more ChallengeForumWatches 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 {TopicReadStateFindManyArgs} args - Arguments to filter and select certain fields only. + * @param {ChallengeForumWatchFindManyArgs} args - Arguments to filter and select certain fields only. * @example - * // Get all TopicReadStates - * const topicReadStates = await prisma.topicReadState.findMany() + * // Get all ChallengeForumWatches + * const challengeForumWatches = await prisma.challengeForumWatch.findMany() * - * // Get first 10 TopicReadStates - * const topicReadStates = await prisma.topicReadState.findMany({ take: 10 }) + * // Get first 10 ChallengeForumWatches + * const challengeForumWatches = await prisma.challengeForumWatch.findMany({ take: 10 }) * - * // Only select the `topicId` - * const topicReadStateWithTopicIdOnly = await prisma.topicReadState.findMany({ select: { topicId: true } }) + * // Only select the `challengeId` + * const challengeForumWatchWithChallengeIdOnly = await prisma.challengeForumWatch.findMany({ select: { challengeId: true } }) * */ - findMany(args?: SelectSubset>): Prisma.PrismaPromise<$Result.GetResult, T, "findMany", GlobalOmitOptions>> + findMany(args?: SelectSubset>): Prisma.PrismaPromise<$Result.GetResult, T, "findMany", GlobalOmitOptions>> /** - * Create a TopicReadState. - * @param {TopicReadStateCreateArgs} args - Arguments to create a TopicReadState. + * Create a ChallengeForumWatch. + * @param {ChallengeForumWatchCreateArgs} args - Arguments to create a ChallengeForumWatch. * @example - * // Create one TopicReadState - * const TopicReadState = await prisma.topicReadState.create({ + * // Create one ChallengeForumWatch + * const ChallengeForumWatch = await prisma.challengeForumWatch.create({ * data: { - * // ... data to create a TopicReadState + * // ... data to create a ChallengeForumWatch * } * }) * */ - create(args: SelectSubset>): Prisma__TopicReadStateClient<$Result.GetResult, T, "create", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + create(args: SelectSubset>): Prisma__ChallengeForumWatchClient<$Result.GetResult, T, "create", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> /** - * Create many TopicReadStates. - * @param {TopicReadStateCreateManyArgs} args - Arguments to create many TopicReadStates. + * Create many ChallengeForumWatches. + * @param {ChallengeForumWatchCreateManyArgs} args - Arguments to create many ChallengeForumWatches. * @example - * // Create many TopicReadStates - * const topicReadState = await prisma.topicReadState.createMany({ + * // Create many ChallengeForumWatches + * const challengeForumWatch = await prisma.challengeForumWatch.createMany({ * data: [ * // ... provide data here * ] * }) * */ - createMany(args?: SelectSubset>): Prisma.PrismaPromise + createMany(args?: SelectSubset>): Prisma.PrismaPromise /** - * Create many TopicReadStates and returns the data saved in the database. - * @param {TopicReadStateCreateManyAndReturnArgs} args - Arguments to create many TopicReadStates. + * Create many ChallengeForumWatches and returns the data saved in the database. + * @param {ChallengeForumWatchCreateManyAndReturnArgs} args - Arguments to create many ChallengeForumWatches. * @example - * // Create many TopicReadStates - * const topicReadState = await prisma.topicReadState.createManyAndReturn({ + * // Create many ChallengeForumWatches + * const challengeForumWatch = await prisma.challengeForumWatch.createManyAndReturn({ * data: [ * // ... provide data here * ] * }) * - * // Create many TopicReadStates and only return the `topicId` - * const topicReadStateWithTopicIdOnly = await prisma.topicReadState.createManyAndReturn({ - * select: { topicId: true }, + * // Create many ChallengeForumWatches and only return the `challengeId` + * const challengeForumWatchWithChallengeIdOnly = await prisma.challengeForumWatch.createManyAndReturn({ + * select: { challengeId: true }, * data: [ * // ... provide data here * ] @@ -10027,28 +10129,28 @@ export namespace Prisma { * Read more here: https://pris.ly/d/null-undefined * */ - createManyAndReturn(args?: SelectSubset>): Prisma.PrismaPromise<$Result.GetResult, T, "createManyAndReturn", GlobalOmitOptions>> + createManyAndReturn(args?: SelectSubset>): Prisma.PrismaPromise<$Result.GetResult, T, "createManyAndReturn", GlobalOmitOptions>> /** - * Delete a TopicReadState. - * @param {TopicReadStateDeleteArgs} args - Arguments to delete one TopicReadState. + * Delete a ChallengeForumWatch. + * @param {ChallengeForumWatchDeleteArgs} args - Arguments to delete one ChallengeForumWatch. * @example - * // Delete one TopicReadState - * const TopicReadState = await prisma.topicReadState.delete({ + * // Delete one ChallengeForumWatch + * const ChallengeForumWatch = await prisma.challengeForumWatch.delete({ * where: { - * // ... filter to delete one TopicReadState + * // ... filter to delete one ChallengeForumWatch * } * }) * */ - delete(args: SelectSubset>): Prisma__TopicReadStateClient<$Result.GetResult, T, "delete", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + delete(args: SelectSubset>): Prisma__ChallengeForumWatchClient<$Result.GetResult, T, "delete", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> /** - * Update one TopicReadState. - * @param {TopicReadStateUpdateArgs} args - Arguments to update one TopicReadState. + * Update one ChallengeForumWatch. + * @param {ChallengeForumWatchUpdateArgs} args - Arguments to update one ChallengeForumWatch. * @example - * // Update one TopicReadState - * const topicReadState = await prisma.topicReadState.update({ + * // Update one ChallengeForumWatch + * const challengeForumWatch = await prisma.challengeForumWatch.update({ * where: { * // ... provide filter here * }, @@ -10058,30 +10160,30 @@ export namespace Prisma { * }) * */ - update(args: SelectSubset>): Prisma__TopicReadStateClient<$Result.GetResult, T, "update", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + update(args: SelectSubset>): Prisma__ChallengeForumWatchClient<$Result.GetResult, T, "update", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> /** - * Delete zero or more TopicReadStates. - * @param {TopicReadStateDeleteManyArgs} args - Arguments to filter TopicReadStates to delete. + * Delete zero or more ChallengeForumWatches. + * @param {ChallengeForumWatchDeleteManyArgs} args - Arguments to filter ChallengeForumWatches to delete. * @example - * // Delete a few TopicReadStates - * const { count } = await prisma.topicReadState.deleteMany({ + * // Delete a few ChallengeForumWatches + * const { count } = await prisma.challengeForumWatch.deleteMany({ * where: { * // ... provide filter here * } * }) * */ - deleteMany(args?: SelectSubset>): Prisma.PrismaPromise + deleteMany(args?: SelectSubset>): Prisma.PrismaPromise /** - * Update zero or more TopicReadStates. + * Update zero or more ChallengeForumWatches. * Note, that providing `undefined` is treated as the value not being there. * Read more here: https://pris.ly/d/null-undefined - * @param {TopicReadStateUpdateManyArgs} args - Arguments to update one or more rows. + * @param {ChallengeForumWatchUpdateManyArgs} args - Arguments to update one or more rows. * @example - * // Update many TopicReadStates - * const topicReadState = await prisma.topicReadState.updateMany({ + * // Update many ChallengeForumWatches + * const challengeForumWatch = await prisma.challengeForumWatch.updateMany({ * where: { * // ... provide filter here * }, @@ -10091,14 +10193,14 @@ export namespace Prisma { * }) * */ - updateMany(args: SelectSubset>): Prisma.PrismaPromise + updateMany(args: SelectSubset>): Prisma.PrismaPromise /** - * Update zero or more TopicReadStates and returns the data updated in the database. - * @param {TopicReadStateUpdateManyAndReturnArgs} args - Arguments to update many TopicReadStates. + * Update zero or more ChallengeForumWatches and returns the data updated in the database. + * @param {ChallengeForumWatchUpdateManyAndReturnArgs} args - Arguments to update many ChallengeForumWatches. * @example - * // Update many TopicReadStates - * const topicReadState = await prisma.topicReadState.updateManyAndReturn({ + * // Update many ChallengeForumWatches + * const challengeForumWatch = await prisma.challengeForumWatch.updateManyAndReturn({ * where: { * // ... provide filter here * }, @@ -10107,9 +10209,9 @@ export namespace Prisma { * ] * }) * - * // Update zero or more TopicReadStates and only return the `topicId` - * const topicReadStateWithTopicIdOnly = await prisma.topicReadState.updateManyAndReturn({ - * select: { topicId: true }, + * // Update zero or more ChallengeForumWatches and only return the `challengeId` + * const challengeForumWatchWithChallengeIdOnly = await prisma.challengeForumWatch.updateManyAndReturn({ + * select: { challengeId: true }, * where: { * // ... provide filter here * }, @@ -10121,26 +10223,1028 @@ export namespace Prisma { * Read more here: https://pris.ly/d/null-undefined * */ - updateManyAndReturn(args: SelectSubset>): Prisma.PrismaPromise<$Result.GetResult, T, "updateManyAndReturn", GlobalOmitOptions>> + updateManyAndReturn(args: SelectSubset>): Prisma.PrismaPromise<$Result.GetResult, T, "updateManyAndReturn", GlobalOmitOptions>> /** - * Create or update one TopicReadState. - * @param {TopicReadStateUpsertArgs} args - Arguments to update or create a TopicReadState. + * Create or update one ChallengeForumWatch. + * @param {ChallengeForumWatchUpsertArgs} args - Arguments to update or create a ChallengeForumWatch. * @example - * // Update or create a TopicReadState - * const topicReadState = await prisma.topicReadState.upsert({ + * // Update or create a ChallengeForumWatch + * const challengeForumWatch = await prisma.challengeForumWatch.upsert({ * create: { - * // ... data to create a TopicReadState + * // ... data to create a ChallengeForumWatch * }, * update: { * // ... in case it already exists, update * }, * where: { - * // ... the filter for the TopicReadState we want to update + * // ... the filter for the ChallengeForumWatch we want to update * } * }) */ - upsert(args: SelectSubset>): Prisma__TopicReadStateClient<$Result.GetResult, T, "upsert", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + upsert(args: SelectSubset>): Prisma__ChallengeForumWatchClient<$Result.GetResult, T, "upsert", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + + /** + * Count the number of ChallengeForumWatches. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {ChallengeForumWatchCountArgs} args - Arguments to filter ChallengeForumWatches to count. + * @example + * // Count the number of ChallengeForumWatches + * const count = await prisma.challengeForumWatch.count({ + * where: { + * // ... the filter for the ChallengeForumWatches 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 ChallengeForumWatch. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {ChallengeForumWatchAggregateArgs} 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 ChallengeForumWatch. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {ChallengeForumWatchGroupByArgs} 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 ChallengeForumWatchGroupByArgs, + HasSelectOrTake extends Or< + Extends<'skip', Keys>, + Extends<'take', Keys> + >, + OrderByArg extends True extends HasSelectOrTake + ? { orderBy: ChallengeForumWatchGroupByArgs['orderBy'] } + : { orderBy?: ChallengeForumWatchGroupByArgs['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 ? GetChallengeForumWatchGroupByPayload : Prisma.PrismaPromise + /** + * Fields of the ChallengeForumWatch model + */ + readonly fields: ChallengeForumWatchFieldRefs; + } + + /** + * The delegate class that acts as a "Promise-like" for ChallengeForumWatch. + * 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__ChallengeForumWatchClient 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 ChallengeForumWatch model + */ + interface ChallengeForumWatchFieldRefs { + readonly challengeId: FieldRef<"ChallengeForumWatch", 'String'> + readonly memberId: FieldRef<"ChallengeForumWatch", 'String'> + readonly watching: FieldRef<"ChallengeForumWatch", 'Boolean'> + readonly createdAt: FieldRef<"ChallengeForumWatch", 'DateTime'> + readonly updatedAt: FieldRef<"ChallengeForumWatch", 'DateTime'> + } + + + // Custom InputTypes + /** + * ChallengeForumWatch findUnique + */ + export type ChallengeForumWatchFindUniqueArgs = { + /** + * Select specific fields to fetch from the ChallengeForumWatch + */ + select?: ChallengeForumWatchSelect | null + /** + * Omit specific fields from the ChallengeForumWatch + */ + omit?: ChallengeForumWatchOmit | null + /** + * Filter, which ChallengeForumWatch to fetch. + */ + where: ChallengeForumWatchWhereUniqueInput + } + + /** + * ChallengeForumWatch findUniqueOrThrow + */ + export type ChallengeForumWatchFindUniqueOrThrowArgs = { + /** + * Select specific fields to fetch from the ChallengeForumWatch + */ + select?: ChallengeForumWatchSelect | null + /** + * Omit specific fields from the ChallengeForumWatch + */ + omit?: ChallengeForumWatchOmit | null + /** + * Filter, which ChallengeForumWatch to fetch. + */ + where: ChallengeForumWatchWhereUniqueInput + } + + /** + * ChallengeForumWatch findFirst + */ + export type ChallengeForumWatchFindFirstArgs = { + /** + * Select specific fields to fetch from the ChallengeForumWatch + */ + select?: ChallengeForumWatchSelect | null + /** + * Omit specific fields from the ChallengeForumWatch + */ + omit?: ChallengeForumWatchOmit | null + /** + * Filter, which ChallengeForumWatch to fetch. + */ + where?: ChallengeForumWatchWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of ChallengeForumWatches to fetch. + */ + orderBy?: ChallengeForumWatchOrderByWithRelationInput | ChallengeForumWatchOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for searching for ChallengeForumWatches. + */ + cursor?: ChallengeForumWatchWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` ChallengeForumWatches 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` ChallengeForumWatches. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of ChallengeForumWatches. + */ + distinct?: ChallengeForumWatchScalarFieldEnum | ChallengeForumWatchScalarFieldEnum[] + } + + /** + * ChallengeForumWatch findFirstOrThrow + */ + export type ChallengeForumWatchFindFirstOrThrowArgs = { + /** + * Select specific fields to fetch from the ChallengeForumWatch + */ + select?: ChallengeForumWatchSelect | null + /** + * Omit specific fields from the ChallengeForumWatch + */ + omit?: ChallengeForumWatchOmit | null + /** + * Filter, which ChallengeForumWatch to fetch. + */ + where?: ChallengeForumWatchWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of ChallengeForumWatches to fetch. + */ + orderBy?: ChallengeForumWatchOrderByWithRelationInput | ChallengeForumWatchOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for searching for ChallengeForumWatches. + */ + cursor?: ChallengeForumWatchWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` ChallengeForumWatches 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` ChallengeForumWatches. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of ChallengeForumWatches. + */ + distinct?: ChallengeForumWatchScalarFieldEnum | ChallengeForumWatchScalarFieldEnum[] + } + + /** + * ChallengeForumWatch findMany + */ + export type ChallengeForumWatchFindManyArgs = { + /** + * Select specific fields to fetch from the ChallengeForumWatch + */ + select?: ChallengeForumWatchSelect | null + /** + * Omit specific fields from the ChallengeForumWatch + */ + omit?: ChallengeForumWatchOmit | null + /** + * Filter, which ChallengeForumWatches to fetch. + */ + where?: ChallengeForumWatchWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of ChallengeForumWatches to fetch. + */ + orderBy?: ChallengeForumWatchOrderByWithRelationInput | ChallengeForumWatchOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for listing ChallengeForumWatches. + */ + cursor?: ChallengeForumWatchWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` ChallengeForumWatches 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` ChallengeForumWatches. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of ChallengeForumWatches. + */ + distinct?: ChallengeForumWatchScalarFieldEnum | ChallengeForumWatchScalarFieldEnum[] + } + + /** + * ChallengeForumWatch create + */ + export type ChallengeForumWatchCreateArgs = { + /** + * Select specific fields to fetch from the ChallengeForumWatch + */ + select?: ChallengeForumWatchSelect | null + /** + * Omit specific fields from the ChallengeForumWatch + */ + omit?: ChallengeForumWatchOmit | null + /** + * The data needed to create a ChallengeForumWatch. + */ + data: XOR + } + + /** + * ChallengeForumWatch createMany + */ + export type ChallengeForumWatchCreateManyArgs = { + /** + * The data used to create many ChallengeForumWatches. + */ + data: ChallengeForumWatchCreateManyInput | ChallengeForumWatchCreateManyInput[] + skipDuplicates?: boolean + } + + /** + * ChallengeForumWatch createManyAndReturn + */ + export type ChallengeForumWatchCreateManyAndReturnArgs = { + /** + * Select specific fields to fetch from the ChallengeForumWatch + */ + select?: ChallengeForumWatchSelectCreateManyAndReturn | null + /** + * Omit specific fields from the ChallengeForumWatch + */ + omit?: ChallengeForumWatchOmit | null + /** + * The data used to create many ChallengeForumWatches. + */ + data: ChallengeForumWatchCreateManyInput | ChallengeForumWatchCreateManyInput[] + skipDuplicates?: boolean + } + + /** + * ChallengeForumWatch update + */ + export type ChallengeForumWatchUpdateArgs = { + /** + * Select specific fields to fetch from the ChallengeForumWatch + */ + select?: ChallengeForumWatchSelect | null + /** + * Omit specific fields from the ChallengeForumWatch + */ + omit?: ChallengeForumWatchOmit | null + /** + * The data needed to update a ChallengeForumWatch. + */ + data: XOR + /** + * Choose, which ChallengeForumWatch to update. + */ + where: ChallengeForumWatchWhereUniqueInput + } + + /** + * ChallengeForumWatch updateMany + */ + export type ChallengeForumWatchUpdateManyArgs = { + /** + * The data used to update ChallengeForumWatches. + */ + data: XOR + /** + * Filter which ChallengeForumWatches to update + */ + where?: ChallengeForumWatchWhereInput + /** + * Limit how many ChallengeForumWatches to update. + */ + limit?: number + } + + /** + * ChallengeForumWatch updateManyAndReturn + */ + export type ChallengeForumWatchUpdateManyAndReturnArgs = { + /** + * Select specific fields to fetch from the ChallengeForumWatch + */ + select?: ChallengeForumWatchSelectUpdateManyAndReturn | null + /** + * Omit specific fields from the ChallengeForumWatch + */ + omit?: ChallengeForumWatchOmit | null + /** + * The data used to update ChallengeForumWatches. + */ + data: XOR + /** + * Filter which ChallengeForumWatches to update + */ + where?: ChallengeForumWatchWhereInput + /** + * Limit how many ChallengeForumWatches to update. + */ + limit?: number + } + + /** + * ChallengeForumWatch upsert + */ + export type ChallengeForumWatchUpsertArgs = { + /** + * Select specific fields to fetch from the ChallengeForumWatch + */ + select?: ChallengeForumWatchSelect | null + /** + * Omit specific fields from the ChallengeForumWatch + */ + omit?: ChallengeForumWatchOmit | null + /** + * The filter to search for the ChallengeForumWatch to update in case it exists. + */ + where: ChallengeForumWatchWhereUniqueInput + /** + * In case the ChallengeForumWatch found by the `where` argument doesn't exist, create a new ChallengeForumWatch with this data. + */ + create: XOR + /** + * In case the ChallengeForumWatch was found with the provided `where` argument, update it with this data. + */ + update: XOR + } + + /** + * ChallengeForumWatch delete + */ + export type ChallengeForumWatchDeleteArgs = { + /** + * Select specific fields to fetch from the ChallengeForumWatch + */ + select?: ChallengeForumWatchSelect | null + /** + * Omit specific fields from the ChallengeForumWatch + */ + omit?: ChallengeForumWatchOmit | null + /** + * Filter which ChallengeForumWatch to delete. + */ + where: ChallengeForumWatchWhereUniqueInput + } + + /** + * ChallengeForumWatch deleteMany + */ + export type ChallengeForumWatchDeleteManyArgs = { + /** + * Filter which ChallengeForumWatches to delete + */ + where?: ChallengeForumWatchWhereInput + /** + * Limit how many ChallengeForumWatches to delete. + */ + limit?: number + } + + /** + * ChallengeForumWatch without action + */ + export type ChallengeForumWatchDefaultArgs = { + /** + * Select specific fields to fetch from the ChallengeForumWatch + */ + select?: ChallengeForumWatchSelect | null + /** + * Omit specific fields from the ChallengeForumWatch + */ + omit?: ChallengeForumWatchOmit | null + } + + + /** + * Model TopicReadState + */ + + export type AggregateTopicReadState = { + _count: TopicReadStateCountAggregateOutputType | null + _min: TopicReadStateMinAggregateOutputType | null + _max: TopicReadStateMaxAggregateOutputType | null + } + + export type TopicReadStateMinAggregateOutputType = { + topicId: string | null + memberId: string | null + lastReadAt: Date | null + updatedAt: Date | null + } + + export type TopicReadStateMaxAggregateOutputType = { + topicId: string | null + memberId: string | null + lastReadAt: Date | null + updatedAt: Date | null + } + + export type TopicReadStateCountAggregateOutputType = { + topicId: number + memberId: number + lastReadAt: number + updatedAt: number + _all: number + } + + + export type TopicReadStateMinAggregateInputType = { + topicId?: true + memberId?: true + lastReadAt?: true + updatedAt?: true + } + + export type TopicReadStateMaxAggregateInputType = { + topicId?: true + memberId?: true + lastReadAt?: true + updatedAt?: true + } + + export type TopicReadStateCountAggregateInputType = { + topicId?: true + memberId?: true + lastReadAt?: true + updatedAt?: true + _all?: true + } + + export type TopicReadStateAggregateArgs = { + /** + * Filter which TopicReadState to aggregate. + */ + where?: TopicReadStateWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of TopicReadStates to fetch. + */ + orderBy?: TopicReadStateOrderByWithRelationInput | TopicReadStateOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the start position + */ + cursor?: TopicReadStateWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` TopicReadStates 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` TopicReadStates. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Count returned TopicReadStates + **/ + _count?: true | TopicReadStateCountAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to find the minimum value + **/ + _min?: TopicReadStateMinAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to find the maximum value + **/ + _max?: TopicReadStateMaxAggregateInputType + } + + export type GetTopicReadStateAggregateType = { + [P in keyof T & keyof AggregateTopicReadState]: P extends '_count' | 'count' + ? T[P] extends true + ? number + : GetScalarType + : GetScalarType + } + + + + + export type TopicReadStateGroupByArgs = { + where?: TopicReadStateWhereInput + orderBy?: TopicReadStateOrderByWithAggregationInput | TopicReadStateOrderByWithAggregationInput[] + by: TopicReadStateScalarFieldEnum[] | TopicReadStateScalarFieldEnum + having?: TopicReadStateScalarWhereWithAggregatesInput + take?: number + skip?: number + _count?: TopicReadStateCountAggregateInputType | true + _min?: TopicReadStateMinAggregateInputType + _max?: TopicReadStateMaxAggregateInputType + } + + export type TopicReadStateGroupByOutputType = { + topicId: string + memberId: string + lastReadAt: Date + updatedAt: Date + _count: TopicReadStateCountAggregateOutputType | null + _min: TopicReadStateMinAggregateOutputType | null + _max: TopicReadStateMaxAggregateOutputType | null + } + + type GetTopicReadStateGroupByPayload = Prisma.PrismaPromise< + Array< + PickEnumerable & + { + [P in ((keyof T) & (keyof TopicReadStateGroupByOutputType))]: P extends '_count' + ? T[P] extends boolean + ? number + : GetScalarType + : GetScalarType + } + > + > + + + export type TopicReadStateSelect = $Extensions.GetSelect<{ + topicId?: boolean + memberId?: boolean + lastReadAt?: boolean + updatedAt?: boolean + topic?: boolean | TopicDefaultArgs + }, ExtArgs["result"]["topicReadState"]> + + export type TopicReadStateSelectCreateManyAndReturn = $Extensions.GetSelect<{ + topicId?: boolean + memberId?: boolean + lastReadAt?: boolean + updatedAt?: boolean + topic?: boolean | TopicDefaultArgs + }, ExtArgs["result"]["topicReadState"]> + + export type TopicReadStateSelectUpdateManyAndReturn = $Extensions.GetSelect<{ + topicId?: boolean + memberId?: boolean + lastReadAt?: boolean + updatedAt?: boolean + topic?: boolean | TopicDefaultArgs + }, ExtArgs["result"]["topicReadState"]> + + export type TopicReadStateSelectScalar = { + topicId?: boolean + memberId?: boolean + lastReadAt?: boolean + updatedAt?: boolean + } + + export type TopicReadStateOmit = $Extensions.GetOmit<"topicId" | "memberId" | "lastReadAt" | "updatedAt", ExtArgs["result"]["topicReadState"]> + export type TopicReadStateInclude = { + topic?: boolean | TopicDefaultArgs + } + export type TopicReadStateIncludeCreateManyAndReturn = { + topic?: boolean | TopicDefaultArgs + } + export type TopicReadStateIncludeUpdateManyAndReturn = { + topic?: boolean | TopicDefaultArgs + } + + export type $TopicReadStatePayload = { + name: "TopicReadState" + objects: { + topic: Prisma.$TopicPayload + } + scalars: $Extensions.GetPayloadResult<{ + topicId: string + memberId: string + lastReadAt: Date + updatedAt: Date + }, ExtArgs["result"]["topicReadState"]> + composites: {} + } + + type TopicReadStateGetPayload = $Result.GetResult + + type TopicReadStateCountArgs = + Omit & { + select?: TopicReadStateCountAggregateInputType | true + } + + export interface TopicReadStateDelegate { + [K: symbol]: { types: Prisma.TypeMap['model']['TopicReadState'], meta: { name: 'TopicReadState' } } + /** + * Find zero or one TopicReadState that matches the filter. + * @param {TopicReadStateFindUniqueArgs} args - Arguments to find a TopicReadState + * @example + * // Get one TopicReadState + * const topicReadState = await prisma.topicReadState.findUnique({ + * where: { + * // ... provide filter here + * } + * }) + */ + findUnique(args: SelectSubset>): Prisma__TopicReadStateClient<$Result.GetResult, T, "findUnique", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> + + /** + * Find one TopicReadState that matches the filter or throw an error with `error.code='P2025'` + * if no matches were found. + * @param {TopicReadStateFindUniqueOrThrowArgs} args - Arguments to find a TopicReadState + * @example + * // Get one TopicReadState + * const topicReadState = await prisma.topicReadState.findUniqueOrThrow({ + * where: { + * // ... provide filter here + * } + * }) + */ + findUniqueOrThrow(args: SelectSubset>): Prisma__TopicReadStateClient<$Result.GetResult, T, "findUniqueOrThrow", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Find the first TopicReadState 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 {TopicReadStateFindFirstArgs} args - Arguments to find a TopicReadState + * @example + * // Get one TopicReadState + * const topicReadState = await prisma.topicReadState.findFirst({ + * where: { + * // ... provide filter here + * } + * }) + */ + findFirst(args?: SelectSubset>): Prisma__TopicReadStateClient<$Result.GetResult, T, "findFirst", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> + + /** + * Find the first TopicReadState 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 {TopicReadStateFindFirstOrThrowArgs} args - Arguments to find a TopicReadState + * @example + * // Get one TopicReadState + * const topicReadState = await prisma.topicReadState.findFirstOrThrow({ + * where: { + * // ... provide filter here + * } + * }) + */ + findFirstOrThrow(args?: SelectSubset>): Prisma__TopicReadStateClient<$Result.GetResult, T, "findFirstOrThrow", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Find zero or more TopicReadStates 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 {TopicReadStateFindManyArgs} args - Arguments to filter and select certain fields only. + * @example + * // Get all TopicReadStates + * const topicReadStates = await prisma.topicReadState.findMany() + * + * // Get first 10 TopicReadStates + * const topicReadStates = await prisma.topicReadState.findMany({ take: 10 }) + * + * // Only select the `topicId` + * const topicReadStateWithTopicIdOnly = await prisma.topicReadState.findMany({ select: { topicId: true } }) + * + */ + findMany(args?: SelectSubset>): Prisma.PrismaPromise<$Result.GetResult, T, "findMany", GlobalOmitOptions>> + + /** + * Create a TopicReadState. + * @param {TopicReadStateCreateArgs} args - Arguments to create a TopicReadState. + * @example + * // Create one TopicReadState + * const TopicReadState = await prisma.topicReadState.create({ + * data: { + * // ... data to create a TopicReadState + * } + * }) + * + */ + create(args: SelectSubset>): Prisma__TopicReadStateClient<$Result.GetResult, T, "create", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Create many TopicReadStates. + * @param {TopicReadStateCreateManyArgs} args - Arguments to create many TopicReadStates. + * @example + * // Create many TopicReadStates + * const topicReadState = await prisma.topicReadState.createMany({ + * data: [ + * // ... provide data here + * ] + * }) + * + */ + createMany(args?: SelectSubset>): Prisma.PrismaPromise + + /** + * Create many TopicReadStates and returns the data saved in the database. + * @param {TopicReadStateCreateManyAndReturnArgs} args - Arguments to create many TopicReadStates. + * @example + * // Create many TopicReadStates + * const topicReadState = await prisma.topicReadState.createManyAndReturn({ + * data: [ + * // ... provide data here + * ] + * }) + * + * // Create many TopicReadStates and only return the `topicId` + * const topicReadStateWithTopicIdOnly = await prisma.topicReadState.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 TopicReadState. + * @param {TopicReadStateDeleteArgs} args - Arguments to delete one TopicReadState. + * @example + * // Delete one TopicReadState + * const TopicReadState = await prisma.topicReadState.delete({ + * where: { + * // ... filter to delete one TopicReadState + * } + * }) + * + */ + delete(args: SelectSubset>): Prisma__TopicReadStateClient<$Result.GetResult, T, "delete", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Update one TopicReadState. + * @param {TopicReadStateUpdateArgs} args - Arguments to update one TopicReadState. + * @example + * // Update one TopicReadState + * const topicReadState = await prisma.topicReadState.update({ + * where: { + * // ... provide filter here + * }, + * data: { + * // ... provide data here + * } + * }) + * + */ + update(args: SelectSubset>): Prisma__TopicReadStateClient<$Result.GetResult, T, "update", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Delete zero or more TopicReadStates. + * @param {TopicReadStateDeleteManyArgs} args - Arguments to filter TopicReadStates to delete. + * @example + * // Delete a few TopicReadStates + * const { count } = await prisma.topicReadState.deleteMany({ + * where: { + * // ... provide filter here + * } + * }) + * + */ + deleteMany(args?: SelectSubset>): Prisma.PrismaPromise + + /** + * Update zero or more TopicReadStates. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {TopicReadStateUpdateManyArgs} args - Arguments to update one or more rows. + * @example + * // Update many TopicReadStates + * const topicReadState = await prisma.topicReadState.updateMany({ + * where: { + * // ... provide filter here + * }, + * data: { + * // ... provide data here + * } + * }) + * + */ + updateMany(args: SelectSubset>): Prisma.PrismaPromise + + /** + * Update zero or more TopicReadStates and returns the data updated in the database. + * @param {TopicReadStateUpdateManyAndReturnArgs} args - Arguments to update many TopicReadStates. + * @example + * // Update many TopicReadStates + * const topicReadState = await prisma.topicReadState.updateManyAndReturn({ + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * + * // Update zero or more TopicReadStates and only return the `topicId` + * const topicReadStateWithTopicIdOnly = await prisma.topicReadState.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 TopicReadState. + * @param {TopicReadStateUpsertArgs} args - Arguments to update or create a TopicReadState. + * @example + * // Update or create a TopicReadState + * const topicReadState = await prisma.topicReadState.upsert({ + * create: { + * // ... data to create a TopicReadState + * }, + * update: { + * // ... in case it already exists, update + * }, + * where: { + * // ... the filter for the TopicReadState we want to update + * } + * }) + */ + upsert(args: SelectSubset>): Prisma__TopicReadStateClient<$Result.GetResult, T, "upsert", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> /** @@ -13895,12 +14999,24 @@ export namespace Prisma { export const TopicWatchScalarFieldEnum: { topicId: 'topicId', memberId: 'memberId', + watching: 'watching', createdAt: 'createdAt' }; export type TopicWatchScalarFieldEnum = (typeof TopicWatchScalarFieldEnum)[keyof typeof TopicWatchScalarFieldEnum] + export const ChallengeForumWatchScalarFieldEnum: { + challengeId: 'challengeId', + memberId: 'memberId', + watching: 'watching', + createdAt: 'createdAt', + updatedAt: 'updatedAt' + }; + + export type ChallengeForumWatchScalarFieldEnum = (typeof ChallengeForumWatchScalarFieldEnum)[keyof typeof ChallengeForumWatchScalarFieldEnum] + + export const TopicReadStateScalarFieldEnum: { topicId: 'topicId', memberId: 'memberId', @@ -14524,6 +15640,7 @@ export namespace Prisma { NOT?: TopicWatchWhereInput | TopicWatchWhereInput[] topicId?: StringFilter<"TopicWatch"> | string memberId?: StringFilter<"TopicWatch"> | string + watching?: BoolFilter<"TopicWatch"> | boolean createdAt?: DateTimeFilter<"TopicWatch"> | Date | string topic?: XOR } @@ -14531,6 +15648,7 @@ export namespace Prisma { export type TopicWatchOrderByWithRelationInput = { topicId?: SortOrder memberId?: SortOrder + watching?: SortOrder createdAt?: SortOrder topic?: TopicOrderByWithRelationInput } @@ -14542,6 +15660,7 @@ export namespace Prisma { NOT?: TopicWatchWhereInput | TopicWatchWhereInput[] topicId?: StringFilter<"TopicWatch"> | string memberId?: StringFilter<"TopicWatch"> | string + watching?: BoolFilter<"TopicWatch"> | boolean createdAt?: DateTimeFilter<"TopicWatch"> | Date | string topic?: XOR }, "topicId_memberId"> @@ -14549,6 +15668,7 @@ export namespace Prisma { export type TopicWatchOrderByWithAggregationInput = { topicId?: SortOrder memberId?: SortOrder + watching?: SortOrder createdAt?: SortOrder _count?: TopicWatchCountOrderByAggregateInput _max?: TopicWatchMaxOrderByAggregateInput @@ -14561,9 +15681,63 @@ export namespace Prisma { NOT?: TopicWatchScalarWhereWithAggregatesInput | TopicWatchScalarWhereWithAggregatesInput[] topicId?: StringWithAggregatesFilter<"TopicWatch"> | string memberId?: StringWithAggregatesFilter<"TopicWatch"> | string + watching?: BoolWithAggregatesFilter<"TopicWatch"> | boolean createdAt?: DateTimeWithAggregatesFilter<"TopicWatch"> | Date | string } + export type ChallengeForumWatchWhereInput = { + AND?: ChallengeForumWatchWhereInput | ChallengeForumWatchWhereInput[] + OR?: ChallengeForumWatchWhereInput[] + NOT?: ChallengeForumWatchWhereInput | ChallengeForumWatchWhereInput[] + challengeId?: StringFilter<"ChallengeForumWatch"> | string + memberId?: StringFilter<"ChallengeForumWatch"> | string + watching?: BoolFilter<"ChallengeForumWatch"> | boolean + createdAt?: DateTimeFilter<"ChallengeForumWatch"> | Date | string + updatedAt?: DateTimeFilter<"ChallengeForumWatch"> | Date | string + } + + export type ChallengeForumWatchOrderByWithRelationInput = { + challengeId?: SortOrder + memberId?: SortOrder + watching?: SortOrder + createdAt?: SortOrder + updatedAt?: SortOrder + } + + export type ChallengeForumWatchWhereUniqueInput = Prisma.AtLeast<{ + challengeId_memberId?: ChallengeForumWatchChallengeIdMemberIdCompoundUniqueInput + AND?: ChallengeForumWatchWhereInput | ChallengeForumWatchWhereInput[] + OR?: ChallengeForumWatchWhereInput[] + NOT?: ChallengeForumWatchWhereInput | ChallengeForumWatchWhereInput[] + challengeId?: StringFilter<"ChallengeForumWatch"> | string + memberId?: StringFilter<"ChallengeForumWatch"> | string + watching?: BoolFilter<"ChallengeForumWatch"> | boolean + createdAt?: DateTimeFilter<"ChallengeForumWatch"> | Date | string + updatedAt?: DateTimeFilter<"ChallengeForumWatch"> | Date | string + }, "challengeId_memberId"> + + export type ChallengeForumWatchOrderByWithAggregationInput = { + challengeId?: SortOrder + memberId?: SortOrder + watching?: SortOrder + createdAt?: SortOrder + updatedAt?: SortOrder + _count?: ChallengeForumWatchCountOrderByAggregateInput + _max?: ChallengeForumWatchMaxOrderByAggregateInput + _min?: ChallengeForumWatchMinOrderByAggregateInput + } + + export type ChallengeForumWatchScalarWhereWithAggregatesInput = { + AND?: ChallengeForumWatchScalarWhereWithAggregatesInput | ChallengeForumWatchScalarWhereWithAggregatesInput[] + OR?: ChallengeForumWatchScalarWhereWithAggregatesInput[] + NOT?: ChallengeForumWatchScalarWhereWithAggregatesInput | ChallengeForumWatchScalarWhereWithAggregatesInput[] + challengeId?: StringWithAggregatesFilter<"ChallengeForumWatch"> | string + memberId?: StringWithAggregatesFilter<"ChallengeForumWatch"> | string + watching?: BoolWithAggregatesFilter<"ChallengeForumWatch"> | boolean + createdAt?: DateTimeWithAggregatesFilter<"ChallengeForumWatch"> | Date | string + updatedAt?: DateTimeWithAggregatesFilter<"ChallengeForumWatch"> | Date | string + } + export type TopicReadStateWhereInput = { AND?: TopicReadStateWhereInput | TopicReadStateWhereInput[] OR?: TopicReadStateWhereInput[] @@ -15264,6 +16438,7 @@ export namespace Prisma { export type TopicWatchCreateInput = { memberId: string + watching?: boolean createdAt?: Date | string topic: TopicCreateNestedOneWithoutWatchesInput } @@ -15271,11 +16446,13 @@ export namespace Prisma { export type TopicWatchUncheckedCreateInput = { topicId: string memberId: string + watching?: boolean createdAt?: Date | string } export type TopicWatchUpdateInput = { memberId?: StringFieldUpdateOperationsInput | string + watching?: BoolFieldUpdateOperationsInput | boolean createdAt?: DateTimeFieldUpdateOperationsInput | Date | string topic?: TopicUpdateOneRequiredWithoutWatchesNestedInput } @@ -15283,24 +16460,84 @@ export namespace Prisma { export type TopicWatchUncheckedUpdateInput = { topicId?: StringFieldUpdateOperationsInput | string memberId?: StringFieldUpdateOperationsInput | string + watching?: BoolFieldUpdateOperationsInput | boolean createdAt?: DateTimeFieldUpdateOperationsInput | Date | string } export type TopicWatchCreateManyInput = { topicId: string memberId: string + watching?: boolean createdAt?: Date | string } export type TopicWatchUpdateManyMutationInput = { memberId?: StringFieldUpdateOperationsInput | string + watching?: BoolFieldUpdateOperationsInput | boolean createdAt?: DateTimeFieldUpdateOperationsInput | Date | string } export type TopicWatchUncheckedUpdateManyInput = { topicId?: StringFieldUpdateOperationsInput | string memberId?: StringFieldUpdateOperationsInput | string + watching?: BoolFieldUpdateOperationsInput | boolean + createdAt?: DateTimeFieldUpdateOperationsInput | Date | string + } + + export type ChallengeForumWatchCreateInput = { + challengeId: string + memberId: string + watching: boolean + createdAt?: Date | string + updatedAt?: Date | string + } + + export type ChallengeForumWatchUncheckedCreateInput = { + challengeId: string + memberId: string + watching: boolean + createdAt?: Date | string + updatedAt?: Date | string + } + + export type ChallengeForumWatchUpdateInput = { + challengeId?: StringFieldUpdateOperationsInput | string + memberId?: StringFieldUpdateOperationsInput | string + watching?: BoolFieldUpdateOperationsInput | boolean + createdAt?: DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string + } + + export type ChallengeForumWatchUncheckedUpdateInput = { + challengeId?: StringFieldUpdateOperationsInput | string + memberId?: StringFieldUpdateOperationsInput | string + watching?: BoolFieldUpdateOperationsInput | boolean + createdAt?: DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string + } + + export type ChallengeForumWatchCreateManyInput = { + challengeId: string + memberId: string + watching: boolean + createdAt?: Date | string + updatedAt?: Date | string + } + + export type ChallengeForumWatchUpdateManyMutationInput = { + challengeId?: StringFieldUpdateOperationsInput | string + memberId?: StringFieldUpdateOperationsInput | string + watching?: BoolFieldUpdateOperationsInput | boolean + createdAt?: DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string + } + + export type ChallengeForumWatchUncheckedUpdateManyInput = { + challengeId?: StringFieldUpdateOperationsInput | string + memberId?: StringFieldUpdateOperationsInput | string + watching?: BoolFieldUpdateOperationsInput | boolean createdAt?: DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string } export type TopicReadStateCreateInput = { @@ -16011,19 +17248,51 @@ export namespace Prisma { export type TopicWatchCountOrderByAggregateInput = { topicId?: SortOrder memberId?: SortOrder + watching?: SortOrder createdAt?: SortOrder } export type TopicWatchMaxOrderByAggregateInput = { topicId?: SortOrder memberId?: SortOrder + watching?: SortOrder createdAt?: SortOrder } export type TopicWatchMinOrderByAggregateInput = { topicId?: SortOrder memberId?: SortOrder + watching?: SortOrder + createdAt?: SortOrder + } + + export type ChallengeForumWatchChallengeIdMemberIdCompoundUniqueInput = { + challengeId: string + memberId: string + } + + export type ChallengeForumWatchCountOrderByAggregateInput = { + challengeId?: SortOrder + memberId?: SortOrder + watching?: SortOrder + createdAt?: SortOrder + updatedAt?: SortOrder + } + + export type ChallengeForumWatchMaxOrderByAggregateInput = { + challengeId?: SortOrder + memberId?: SortOrder + watching?: SortOrder + createdAt?: SortOrder + updatedAt?: SortOrder + } + + export type ChallengeForumWatchMinOrderByAggregateInput = { + challengeId?: SortOrder + memberId?: SortOrder + watching?: SortOrder createdAt?: SortOrder + updatedAt?: SortOrder } export type TopicReadStateTopicIdMemberIdCompoundUniqueInput = { @@ -17061,11 +18330,13 @@ export namespace Prisma { export type TopicWatchCreateWithoutTopicInput = { memberId: string + watching?: boolean createdAt?: Date | string } export type TopicWatchUncheckedCreateWithoutTopicInput = { memberId: string + watching?: boolean createdAt?: Date | string } @@ -17291,6 +18562,7 @@ export namespace Prisma { NOT?: TopicWatchScalarWhereInput | TopicWatchScalarWhereInput[] topicId?: StringFilter<"TopicWatch"> | string memberId?: StringFilter<"TopicWatch"> | string + watching?: BoolFilter<"TopicWatch"> | boolean createdAt?: DateTimeFilter<"TopicWatch"> | Date | string } @@ -18025,6 +19297,7 @@ export namespace Prisma { export type TopicWatchCreateManyTopicInput = { memberId: string + watching?: boolean createdAt?: Date | string } @@ -18170,16 +19443,19 @@ export namespace Prisma { export type TopicWatchUpdateWithoutTopicInput = { memberId?: StringFieldUpdateOperationsInput | string + watching?: BoolFieldUpdateOperationsInput | boolean createdAt?: DateTimeFieldUpdateOperationsInput | Date | string } export type TopicWatchUncheckedUpdateWithoutTopicInput = { memberId?: StringFieldUpdateOperationsInput | string + watching?: BoolFieldUpdateOperationsInput | boolean createdAt?: DateTimeFieldUpdateOperationsInput | Date | string } export type TopicWatchUncheckedUpdateManyWithoutTopicInput = { memberId?: StringFieldUpdateOperationsInput | string + watching?: BoolFieldUpdateOperationsInput | boolean createdAt?: DateTimeFieldUpdateOperationsInput | Date | string } diff --git a/packages/forums-prisma-client/index.js b/packages/forums-prisma-client/index.js index 6c4f777..a070789 100644 --- a/packages/forums-prisma-client/index.js +++ b/packages/forums-prisma-client/index.js @@ -160,9 +160,18 @@ exports.Prisma.TopicClosureScalarFieldEnum = { exports.Prisma.TopicWatchScalarFieldEnum = { topicId: 'topicId', memberId: 'memberId', + watching: 'watching', createdAt: 'createdAt' }; +exports.Prisma.ChallengeForumWatchScalarFieldEnum = { + challengeId: 'challengeId', + memberId: 'memberId', + watching: 'watching', + createdAt: 'createdAt', + updatedAt: 'updatedAt' +}; + exports.Prisma.TopicReadStateScalarFieldEnum = { topicId: 'topicId', memberId: 'memberId', @@ -234,6 +243,7 @@ exports.Prisma.ModelName = { PostReaction: 'PostReaction', TopicClosure: 'TopicClosure', TopicWatch: 'TopicWatch', + ChallengeForumWatch: 'ChallengeForumWatch', TopicReadState: 'TopicReadState', PublicForumCategory: 'PublicForumCategory', PublicForumRoleMember: 'PublicForumRoleMember', @@ -247,14 +257,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\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" + "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\n// Explicit topic choices override challenge-wide notification preferences.\nmodel TopicWatch {\n topicId String @db.VarChar(14)\n memberId String @db.VarChar(64)\n watching Boolean @default(true)\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\n// Explicit challenge-wide preferences override resource-based default watches.\nmodel ChallengeForumWatch {\n challengeId String @db.VarChar(64)\n memberId String @db.VarChar(64)\n watching Boolean\n createdAt DateTime @default(now())\n updatedAt DateTime @default(now()) @updatedAt\n\n @@id([challengeId, 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},\"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\":{}}") +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\":\"watching\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"topic\",\"kind\":\"object\",\"type\":\"Topic\",\"relationName\":\"TopicToTopicWatch\"}],\"dbName\":null},\"ChallengeForumWatch\":{\"fields\":[{\"name\":\"challengeId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"memberId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"watching\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"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\",\"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-AECAAAAAQEVAACfAQAwARUAAJ8BADAFCgAAkAMAIAsAAJEDACD2AQEA8AIAIfcBAQDwAgAh-AECAPoCACECAAAAEgAgFQAAogEAIAP2AQEA8AIAIfcBAQDwAgAh-AECAPoCACECAAAAEAAgFQAApAEAIAIAAAAQACAVAACkAQAgAwAAABIAIBwAAJ0BACAdAACiAQAgAQAAABIAIAEAAAAQACAFCAAAiwMAICIAAI4DACAjAACNAwAgdAAAjAMAIHUAAI8DACAGyAEAAMECADDJAQAAqwEAEMoBAADBAgAw9gEBAKYCACH3AQEApgIAIfgBAgC2AgAhAwAAABAAIAIAAKoBADAhAACrAQAgAwAAABAAIAIAABEAMAMAABIAIAEAAAAXACABAAAAFwAgAwAAABUAIAIAABYAMAMAABcAIAMAAAAVACACAAAWADADAAAXACADAAAAFQAgAgAAFgAwAwAAFwAgBAUAAIoDACDQAUAAAAAB3wEBAAAAAeEBAQAAAAEBFQAAswEAIAPQAUAAAAAB3wEBAAAAAeEBAQAAAAEBFQAAtQEAMAEVAAC1AQAwBAUAAIkDACDQAUAA8QIAId8BAQDwAgAh4QEBAPACACECAAAAFwAgFQAAuAEAIAPQAUAA8QIAId8BAQDwAgAh4QEBAPACACECAAAAFQAgFQAAugEAIAIAAAAVACAVAAC6AQAgAwAAABcAIBwAALMBACAdAAC4AQAgAQAAABcAIAEAAAAVACADCAAAhgMAICIAAIgDACAjAACHAwAgBsgBAADAAgAwyQEAAMEBABDKAQAAwAIAMNABQACnAgAh3wEBAKYCACHhAQEApgIAIQMAAAAVACACAADAAQAwIQAAwQEAIAMAAAAVACACAAAWADADAAAXACABAAAAGwAgAQAAABsAIAMAAAAZACACAAAaADADAAAbACADAAAAGQAgAgAAGgAwAwAAGwAgAwAAABkAIAIAABoAMAMAABsAIAUFAACFAwAg3wEBAAAAAeEBAQAAAAH0AUAAAAAB9QFAAAAAAQEVAADJAQAgBN8BAQAAAAHhAQEAAAAB9AFAAAAAAfUBQAAAAAEBFQAAywEAMAEVAADLAQAwBQUAAIQDACDfAQEA8AIAIeEBAQDwAgAh9AFAAPECACH1AUAA8QIAIQIAAAAbACAVAADOAQAgBN8BAQDwAgAh4QEBAPACACH0AUAA8QIAIfUBQADxAgAhAgAAABkAIBUAANABACACAAAAGQAgFQAA0AEAIAMAAAAbACAcAADJAQAgHQAAzgEAIAEAAAAbACABAAAAGQAgAwgAAIEDACAiAACDAwAgIwAAggMAIAfIAQAAvwIAMMkBAADXAQAQygEAAL8CADDfAQEApgIAIeEBAQCmAgAh9AFAAKcCACH1AUAApwIAIQMAAAAZACACAADWAQAwIQAA1wEAIAMAAAAZACACAAAaADADAAAbACANyAEAALwCADDJAQAA3QEAEMoBAAC8AgAw4QEBAAAAAeIBAQCuAgAh4wEBAK4CACHkAQIAvQIAIeUBAAC3AgAg5gEAALcCACDnAQAAtwIAIOgBAgAAAAHpAQEArgIAIeoBAAC-AgAgAQAAANoBACABAAAA2gEAIA3IAQAAvAIAMMkBAADdAQAQygEAALwCADDhAQEArgIAIeIBAQCuAgAh4wEBAK4CACHkAQIAvQIAIeUBAAC3AgAg5gEAALcCACDnAQAAtwIAIOgBAgC9AgAh6QEBAK4CACHqAQAAvgIAIAADAAAA3QEAIAIAAN4BADADAADaAQAgAwAAAN0BACACAADeAQAwAwAA2gEAIAMAAADdAQAgAgAA3gEAMAMAANoBACAK4QEBAAAAAeIBAQAAAAHjAQEAAAAB5AECAAAAAeUBAAD-AgAg5gEAAP8CACDnAQAAgAMAIOgBAgAAAAHpAQEAAAAB6gGAAAAAAQEVAADiAQAgCuEBAQAAAAHiAQEAAAAB4wEBAAAAAeQBAgAAAAHlAQAA_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" + 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\",\"ChallengeForumWatch.findUnique\",\"ChallengeForumWatch.findUniqueOrThrow\",\"ChallengeForumWatch.findFirst\",\"ChallengeForumWatch.findFirstOrThrow\",\"ChallengeForumWatch.findMany\",\"ChallengeForumWatch.createOne\",\"ChallengeForumWatch.createMany\",\"ChallengeForumWatch.createManyAndReturn\",\"ChallengeForumWatch.updateOne\",\"ChallengeForumWatch.updateMany\",\"ChallengeForumWatch.updateManyAndReturn\",\"ChallengeForumWatch.upsertOne\",\"ChallengeForumWatch.deleteOne\",\"ChallengeForumWatch.deleteMany\",\"ChallengeForumWatch.groupBy\",\"ChallengeForumWatch.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\",\"challengeId\",\"watching\",\"challengeId_memberId\",\"ancestorTopicId\",\"descendantTopicId\",\"depth\",\"postId\",\"PostReactionType\",\"reaction\",\"id\",\"parentType\",\"parentId\",\"authorMemberId\",\"authorHandle\",\"content\",\"deletedAt\",\"deletedByMemberId\",\"ipAddress\",\"createdByMemberId\",\"removedAt\",\"removedByMemberId\",\"parentTopicId\",\"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: "3QRnwAEZAQAAgwMAIAQAAIQDACAJAACFAwAgDAAAhgMAIA0AAIYDACAOAACHAwAgDwAAiAMAINgBAACCAwAw2QEAAAMAENoBAACCAwAw4AFAAMgCACHuAQEA8AIAIYUCQADIAgAhhgIBAPACACGPAgEAAAABkgIBAMcCACGTAgEAxwIAIZUCQADxAgAhlgIBAPACACGbAgEA8AIAIZwCAQDHAgAhnQIgAN4CACGeAiAA3gIAIZ8CQADxAgAhoAIBAPACACEBAAAAAQAgGQEAAIMDACAEAACEAwAgCQAAhQMAIAwAAIYDACANAACGAwAgDgAAhwMAIA8AAIgDACDYAQAAggMAMNkBAAADABDaAQAAggMAMOABQADIAgAh7gEBAPACACGFAkAAyAIAIYYCAQDwAgAhjwIBAMcCACGSAgEAxwIAIZMCAQDHAgAhlQJAAPECACGWAgEA8AIAIZsCAQDwAgAhnAIBAMcCACGdAiAA3gIAIZ4CIADeAgAhnwJAAPECACGgAgEA8AIAIQEAAAADACAOAQAArAQAIAQAAK8EACAJAACwBAAgDAAAsQQAIA0AALEEACAOAACyBAAgDwAAswQAIO4BAAC6AwAghgIAALoDACCVAgAAugMAIJYCAAC6AwAgmwIAALoDACCfAgAAugMAIKACAAC6AwAgAwAAAAMAIAIAAAUAMAMAAAEAIBAFAAD3AgAgBwAAgQMAINgBAACAAwAw2QEAAAcAENoBAACAAwAw4AFAAMgCACHxAQEAxwIAIYUCQADIAgAhjwIBAMcCACGQAgEAxwIAIZECAQDHAgAhkgIBAMcCACGTAgEAxwIAIZQCAQDwAgAhlQJAAPECACGWAgEA8AIAIQUFAACsBAAgBwAArgQAIJQCAAC6AwAglQIAALoDACCWAgAAugMAIBAFAAD3AgAgBwAAgQMAINgBAACAAwAw2QEAAAcAENoBAACAAwAw4AFAAMgCACHxAQEAxwIAIYUCQADIAgAhjwIBAAAAAZACAQDHAgAhkQIBAMcCACGSAgEAxwIAIZMCAQDHAgAhlAIBAPACACGVAkAA8QIAIZYCAQDwAgAhAwAAAAcAIAIAAAgAMAMAAAkAIAkGAAD_AgAg2AEAAP0CADDZAQAACwAQ2gEAAP0CADDgAUAAyAIAIe8BAQDHAgAhhQJAAMgCACGMAgEAxwIAIY4CAAD-Ao4CIgEGAACtBAAgCgYAAP8CACDYAQAA_QIAMNkBAAALABDaAQAA_QIAMOABQADIAgAh7wEBAMcCACGFAkAAyAIAIYwCAQDHAgAhjgIAAP4CjgIiowIAAPwCACADAAAACwAgAgAADAAwAwAADQAgAQAAAAsAIAgKAAD3AgAgCwAA9wIAINgBAAD7AgAw2QEAABAAENoBAAD7AgAwiQIBAMcCACGKAgEAxwIAIYsCAgDWAgAhAgoAAKwEACALAACsBAAgCQoAAPcCACALAAD3AgAg2AEAAPsCADDZAQAAEAAQ2gEAAPsCADCJAgEAxwIAIYoCAQDHAgAhiwICANYCACGiAgAA-gIAIAMAAAAQACACAAARADADAAASACADAAAAEAAgAgAAEQAwAwAAEgAgCAUAAPcCACDYAQAA-QIAMNkBAAAVABDaAQAA-QIAMOABQADIAgAh7wEBAMcCACHxAQEAxwIAIYcCIADeAgAhAQUAAKwEACAJBQAA9wIAINgBAAD5AgAw2QEAABUAENoBAAD5AgAw4AFAAMgCACHvAQEAxwIAIfEBAQDHAgAhhwIgAN4CACGhAgAA-AIAIAMAAAAVACACAAAWADADAAAXACAIBQAA9wIAINgBAAD2AgAw2QEAABkAENoBAAD2AgAw7wEBAMcCACHxAQEAxwIAIYQCQADIAgAhhQJAAMgCACEBBQAArAQAIAkFAAD3AgAg2AEAAPYCADDZAQAAGQAQ2gEAAPYCADDvAQEAxwIAIfEBAQDHAgAhhAJAAMgCACGFAkAAyAIAIaECAAD1AgAgAwAAABkAIAIAABoAMAMAABsAIAEAAAADACABAAAABwAgAQAAABAAIAEAAAAQACABAAAAFQAgAQAAABkAIAEAAAABACADAAAAAwAgAgAABQAwAwAAAQAgAwAAAAMAIAIAAAUAMAMAAAEAIAMAAAADACACAAAFADADAAABACAWAQAAqwQAIAQAAKUEACAJAACmBAAgDAAApwQAIA0AAKgEACAOAACpBAAgDwAAqgQAIOABQAAAAAHuAQEAAAABhQJAAAAAAYYCAQAAAAGPAgEAAAABkgIBAAAAAZMCAQAAAAGVAkAAAAABlgIBAAAAAZsCAQAAAAGcAgEAAAABnQIgAAAAAZ4CIAAAAAGfAkAAAAABoAIBAAAAAQEVAAAnACAP4AFAAAAAAe4BAQAAAAGFAkAAAAABhgIBAAAAAY8CAQAAAAGSAgEAAAABkwIBAAAAAZUCQAAAAAGWAgEAAAABmwIBAAAAAZwCAQAAAAGdAiAAAAABngIgAAAAAZ8CQAAAAAGgAgEAAAABARUAACkAMAEVAAApADABAAAAAwAgFgEAANkDACAEAADaAwAgCQAA2wMAIAwAANwDACANAADdAwAgDgAA3gMAIA8AAN8DACDgAUAAjQMAIe4BAQC-AwAhhQJAAI0DACGGAgEAvgMAIY8CAQCMAwAhkgIBAIwDACGTAgEAjAMAIZUCQAC_AwAhlgIBAL4DACGbAgEAvgMAIZwCAQCMAwAhnQIgAKUDACGeAiAApQMAIZ8CQAC_AwAhoAIBAL4DACECAAAAAQAgFQAALQAgD-ABQACNAwAh7gEBAL4DACGFAkAAjQMAIYYCAQC-AwAhjwIBAIwDACGSAgEAjAMAIZMCAQCMAwAhlQJAAL8DACGWAgEAvgMAIZsCAQC-AwAhnAIBAIwDACGdAiAApQMAIZ4CIAClAwAhnwJAAL8DACGgAgEAvgMAIQIAAAADACAVAAAvACACAAAAAwAgFQAALwAgAQAAAAMAIAMAAAABACAcAAAnACAdAAAtACABAAAAAQAgAQAAAAMAIAoIAADWAwAgIgAA2AMAICMAANcDACDuAQAAugMAIIYCAAC6AwAglQIAALoDACCWAgAAugMAIJsCAAC6AwAgnwIAALoDACCgAgAAugMAIBLYAQAA9AIAMNkBAAA3ABDaAQAA9AIAMOABQADAAgAh7gEBAOcCACGFAkAAwAIAIYYCAQDnAgAhjwIBAL8CACGSAgEAvwIAIZMCAQC_AgAhlQJAAOgCACGWAgEA5wIAIZsCAQDnAgAhnAIBAL8CACGdAiAA2gIAIZ4CIADaAgAhnwJAAOgCACGgAgEA5wIAIQMAAAADACACAAA2ADAhAAA3ACADAAAAAwAgAgAABQAwAwAAAQAgCdgBAADzAgAw2QEAAD0AENoBAADzAgAw4AFAAMgCACHvAQEAxwIAIY8CAQAAAAGYAgEA8AIAIZkCQADxAgAhmgIBAPACACEBAAAAOgAgAQAAADoAIAnYAQAA8wIAMNkBAAA9ABDaAQAA8wIAMOABQADIAgAh7wEBAMcCACGPAgEAxwIAIZgCAQDwAgAhmQJAAPECACGaAgEA8AIAIQOYAgAAugMAIJkCAAC6AwAgmgIAALoDACADAAAAPQAgAgAAPgAwAwAAOgAgAwAAAD0AIAIAAD4AMAMAADoAIAMAAAA9ACACAAA-ADADAAA6ACAG4AFAAAAAAe8BAQAAAAGPAgEAAAABmAIBAAAAAZkCQAAAAAGaAgEAAAABARUAAEIAIAbgAUAAAAAB7wEBAAAAAY8CAQAAAAGYAgEAAAABmQJAAAAAAZoCAQAAAAEBFQAARAAwARUAAEQAMAbgAUAAjQMAIe8BAQCMAwAhjwIBAIwDACGYAgEAvgMAIZkCQAC_AwAhmgIBAL4DACECAAAAOgAgFQAARwAgBuABQACNAwAh7wEBAIwDACGPAgEAjAMAIZgCAQC-AwAhmQJAAL8DACGaAgEAvgMAIQIAAAA9ACAVAABJACACAAAAPQAgFQAASQAgAwAAADoAIBwAAEIAIB0AAEcAIAEAAAA6ACABAAAAPQAgBggAANMDACAiAADVAwAgIwAA1AMAIJgCAAC6AwAgmQIAALoDACCaAgAAugMAIAnYAQAA8gIAMNkBAABQABDaAQAA8gIAMOABQADAAgAh7wEBAL8CACGPAgEAvwIAIZgCAQDnAgAhmQJAAOgCACGaAgEA5wIAIQMAAAA9ACACAABPADAhAABQACADAAAAPQAgAgAAPgAwAwAAOgAgCdgBAADvAgAw2QEAAFYAENoBAADvAgAw4AFAAMgCACGPAgEAAAABlwIBAMcCACGYAgEA8AIAIZkCQADxAgAhmgIBAPACACEBAAAAUwAgAQAAAFMAIAnYAQAA7wIAMNkBAABWABDaAQAA7wIAMOABQADIAgAhjwIBAMcCACGXAgEAxwIAIZgCAQDwAgAhmQJAAPECACGaAgEA8AIAIQOYAgAAugMAIJkCAAC6AwAgmgIAALoDACADAAAAVgAgAgAAVwAwAwAAUwAgAwAAAFYAIAIAAFcAMAMAAFMAIAMAAABWACACAABXADADAABTACAG4AFAAAAAAY8CAQAAAAGXAgEAAAABmAIBAAAAAZkCQAAAAAGaAgEAAAABARUAAFsAIAbgAUAAAAABjwIBAAAAAZcCAQAAAAGYAgEAAAABmQJAAAAAAZoCAQAAAAEBFQAAXQAwARUAAF0AMAbgAUAAjQMAIY8CAQCMAwAhlwIBAIwDACGYAgEAvgMAIZkCQAC_AwAhmgIBAL4DACECAAAAUwAgFQAAYAAgBuABQACNAwAhjwIBAIwDACGXAgEAjAMAIZgCAQC-AwAhmQJAAL8DACGaAgEAvgMAIQIAAABWACAVAABiACACAAAAVgAgFQAAYgAgAwAAAFMAIBwAAFsAIB0AAGAAIAEAAABTACABAAAAVgAgBggAANADACAiAADSAwAgIwAA0QMAIJgCAAC6AwAgmQIAALoDACCaAgAAugMAIAnYAQAA7gIAMNkBAABpABDaAQAA7gIAMOABQADAAgAhjwIBAL8CACGXAgEAvwIAIZgCAQDnAgAhmQJAAOgCACGaAgEA5wIAIQMAAABWACACAABoADAhAABpACADAAAAVgAgAgAAVwAwAwAAUwAgAQAAAAkAIAEAAAAJACADAAAABwAgAgAACAAwAwAACQAgAwAAAAcAIAIAAAgAMAMAAAkAIAMAAAAHACACAAAIADADAAAJACANBQAAzgMAIAcAAM8DACDgAUAAAAAB8QEBAAAAAYUCQAAAAAGPAgEAAAABkAIBAAAAAZECAQAAAAGSAgEAAAABkwIBAAAAAZQCAQAAAAGVAkAAAAABlgIBAAAAAQEVAABxACAL4AFAAAAAAfEBAQAAAAGFAkAAAAABjwIBAAAAAZACAQAAAAGRAgEAAAABkgIBAAAAAZMCAQAAAAGUAgEAAAABlQJAAAAAAZYCAQAAAAEBFQAAcwAwARUAAHMAMA0FAADAAwAgBwAAwQMAIOABQACNAwAh8QEBAIwDACGFAkAAjQMAIY8CAQCMAwAhkAIBAIwDACGRAgEAjAMAIZICAQCMAwAhkwIBAIwDACGUAgEAvgMAIZUCQAC_AwAhlgIBAL4DACECAAAACQAgFQAAdgAgC-ABQACNAwAh8QEBAIwDACGFAkAAjQMAIY8CAQCMAwAhkAIBAIwDACGRAgEAjAMAIZICAQCMAwAhkwIBAIwDACGUAgEAvgMAIZUCQAC_AwAhlgIBAL4DACECAAAABwAgFQAAeAAgAgAAAAcAIBUAAHgAIAMAAAAJACAcAABxACAdAAB2ACABAAAACQAgAQAAAAcAIAYIAAC7AwAgIgAAvQMAICMAALwDACCUAgAAugMAIJUCAAC6AwAglgIAALoDACAO2AEAAOYCADDZAQAAfwAQ2gEAAOYCADDgAUAAwAIAIfEBAQC_AgAhhQJAAMACACGPAgEAvwIAIZACAQC_AgAhkQIBAL8CACGSAgEAvwIAIZMCAQC_AgAhlAIBAOcCACGVAkAA6AIAIZYCAQDnAgAhAwAAAAcAIAIAAH4AMCEAAH8AIAMAAAAHACACAAAIADADAAAJACABAAAADQAgAQAAAA0AIAMAAAALACACAAAMADADAAANACADAAAACwAgAgAADAAwAwAADQAgAwAAAAsAIAIAAAwAMAMAAA0AIAYGAAC5AwAg4AFAAAAAAe8BAQAAAAGFAkAAAAABjAIBAAAAAY4CAAAAjgICARUAAIcBACAF4AFAAAAAAe8BAQAAAAGFAkAAAAABjAIBAAAAAY4CAAAAjgICARUAAIkBADABFQAAiQEAMAYGAAC4AwAg4AFAAI0DACHvAQEAjAMAIYUCQACNAwAhjAIBAIwDACGOAgAAtwOOAiICAAAADQAgFQAAjAEAIAXgAUAAjQMAIe8BAQCMAwAhhQJAAI0DACGMAgEAjAMAIY4CAAC3A44CIgIAAAALACAVAACOAQAgAgAAAAsAIBUAAI4BACADAAAADQAgHAAAhwEAIB0AAIwBACABAAAADQAgAQAAAAsAIAMIAAC0AwAgIgAAtgMAICMAALUDACAI2AEAAOICADDZAQAAlQEAENoBAADiAgAw4AFAAMACACHvAQEAvwIAIYUCQADAAgAhjAIBAL8CACGOAgAA4wKOAiIDAAAACwAgAgAAlAEAMCEAAJUBACADAAAACwAgAgAADAAwAwAADQAgAQAAABIAIAEAAAASACADAAAAEAAgAgAAEQAwAwAAEgAgAwAAABAAIAIAABEAMAMAABIAIAMAAAAQACACAAARADADAAASACAFCgAAsgMAIAsAALMDACCJAgEAAAABigIBAAAAAYsCAgAAAAEBFQAAnQEAIAOJAgEAAAABigIBAAAAAYsCAgAAAAEBFQAAnwEAMAEVAACfAQAwBQoAALADACALAACxAwAgiQIBAIwDACGKAgEAjAMAIYsCAgCWAwAhAgAAABIAIBUAAKIBACADiQIBAIwDACGKAgEAjAMAIYsCAgCWAwAhAgAAABAAIBUAAKQBACACAAAAEAAgFQAApAEAIAMAAAASACAcAACdAQAgHQAAogEAIAEAAAASACABAAAAEAAgBQgAAKsDACAiAACuAwAgIwAArQMAIHQAAKwDACB1AACvAwAgBtgBAADhAgAw2QEAAKsBABDaAQAA4QIAMIkCAQC_AgAhigIBAL8CACGLAgIAzwIAIQMAAAAQACACAACqAQAwIQAAqwEAIAMAAAAQACACAAARADADAAASACABAAAAFwAgAQAAABcAIAMAAAAVACACAAAWADADAAAXACADAAAAFQAgAgAAFgAwAwAAFwAgAwAAABUAIAIAABYAMAMAABcAIAUFAACqAwAg4AFAAAAAAe8BAQAAAAHxAQEAAAABhwIgAAAAAQEVAACzAQAgBOABQAAAAAHvAQEAAAAB8QEBAAAAAYcCIAAAAAEBFQAAtQEAMAEVAAC1AQAwBQUAAKkDACDgAUAAjQMAIe8BAQCMAwAh8QEBAIwDACGHAiAApQMAIQIAAAAXACAVAAC4AQAgBOABQACNAwAh7wEBAIwDACHxAQEAjAMAIYcCIAClAwAhAgAAABUAIBUAALoBACACAAAAFQAgFQAAugEAIAMAAAAXACAcAACzAQAgHQAAuAEAIAEAAAAXACABAAAAFQAgAwgAAKYDACAiAACoAwAgIwAApwMAIAfYAQAA4AIAMNkBAADBAQAQ2gEAAOACADDgAUAAwAIAIe8BAQC_AgAh8QEBAL8CACGHAiAA2gIAIQMAAAAVACACAADAAQAwIQAAwQEAIAMAAAAVACACAAAWADADAAAXACAJ2AEAAN0CADDZAQAAxwEAENoBAADdAgAw4AFAAMgCACHvAQEAxwIAIYUCQADIAgAhhgIBAMcCACGHAiAA3gIAIYgCAADfAgAgAQAAAMQBACABAAAAxAEAIAjYAQAA3QIAMNkBAADHAQAQ2gEAAN0CADDgAUAAyAIAIe8BAQDHAgAhhQJAAMgCACGGAgEAxwIAIYcCIADeAgAhAAMAAADHAQAgAgAAyAEAMAMAAMQBACADAAAAxwEAIAIAAMgBADADAADEAQAgAwAAAMcBACACAADIAQAwAwAAxAEAIAXgAUAAAAAB7wEBAAAAAYUCQAAAAAGGAgEAAAABhwIgAAAAAQEVAADMAQAgBeABQAAAAAHvAQEAAAABhQJAAAAAAYYCAQAAAAGHAiAAAAABARUAAM4BADABFQAAzgEAMAXgAUAAjQMAIe8BAQCMAwAhhQJAAI0DACGGAgEAjAMAIYcCIAClAwAhAgAAAMQBACAVAADRAQAgBeABQACNAwAh7wEBAIwDACGFAkAAjQMAIYYCAQCMAwAhhwIgAKUDACECAAAAxwEAIBUAANMBACACAAAAxwEAIBUAANMBACADAAAAxAEAIBwAAMwBACAdAADRAQAgAQAAAMQBACABAAAAxwEAIAMIAACiAwAgIgAApAMAICMAAKMDACAI2AEAANkCADDZAQAA2gEAENoBAADZAgAw4AFAAMACACHvAQEAvwIAIYUCQADAAgAhhgIBAL8CACGHAiAA2gIAIQMAAADHAQAgAgAA2QEAMCEAANoBACADAAAAxwEAIAIAAMgBADADAADEAQAgAQAAABsAIAEAAAAbACADAAAAGQAgAgAAGgAwAwAAGwAgAwAAABkAIAIAABoAMAMAABsAIAMAAAAZACACAAAaADADAAAbACAFBQAAoQMAIO8BAQAAAAHxAQEAAAABhAJAAAAAAYUCQAAAAAEBFQAA4gEAIATvAQEAAAAB8QEBAAAAAYQCQAAAAAGFAkAAAAABARUAAOQBADABFQAA5AEAMAUFAACgAwAg7wEBAIwDACHxAQEAjAMAIYQCQACNAwAhhQJAAI0DACECAAAAGwAgFQAA5wEAIATvAQEAjAMAIfEBAQCMAwAhhAJAAI0DACGFAkAAjQMAIQIAAAAZACAVAADpAQAgAgAAABkAIBUAAOkBACADAAAAGwAgHAAA4gEAIB0AAOcBACABAAAAGwAgAQAAABkAIAMIAACdAwAgIgAAnwMAICMAAJ4DACAH2AEAANgCADDZAQAA8AEAENoBAADYAgAw7wEBAL8CACHxAQEAvwIAIYQCQADAAgAhhQJAAMACACEDAAAAGQAgAgAA7wEAMCEAAPABACADAAAAGQAgAgAAGgAwAwAAGwAgDdgBAADVAgAw2QEAAPYBABDaAQAA1QIAMPEBAQAAAAHyAQEAxwIAIfMBAQDHAgAh9AECANYCACH1AQAA0AIAIPYBAADQAgAg9wEAANACACD4AQIAAAAB-QEBAMcCACH6AQAA1wIAIAEAAADzAQAgAQAAAPMBACAN2AEAANUCADDZAQAA9gEAENoBAADVAgAw8QEBAMcCACHyAQEAxwIAIfMBAQDHAgAh9AECANYCACH1AQAA0AIAIPYBAADQAgAg9wEAANACACD4AQIA1gIAIfkBAQDHAgAh-gEAANcCACAAAwAAAPYBACACAAD3AQAwAwAA8wEAIAMAAAD2AQAgAgAA9wEAMAMAAPMBACADAAAA9gEAIAIAAPcBADADAADzAQAgCvEBAQAAAAHyAQEAAAAB8wEBAAAAAfQBAgAAAAH1AQAAmgMAIPYBAACbAwAg9wEAAJwDACD4AQIAAAAB-QEBAAAAAfoBgAAAAAEBFQAA-wEAIArxAQEAAAAB8gEBAAAAAfMBAQAAAAH0AQIAAAAB9QEAAJoDACD2AQAAmwMAIPcBAACcAwAg-AECAAAAAfkBAQAAAAH6AYAAAAABARUAAP0BADABFQAA_QEAMArxAQEAjAMAIfIBAQCMAwAh8wEBAIwDACH0AQIAlgMAIfUBAACXAwAg9gEAAJgDACD3AQAAmQMAIPgBAgCWAwAh-QEBAIwDACH6AYAAAAABAgAAAPMBACAVAACAAgAgCvEBAQCMAwAh8gEBAIwDACHzAQEAjAMAIfQBAgCWAwAh9QEAAJcDACD2AQAAmAMAIPcBAACZAwAg-AECAJYDACH5AQEAjAMAIfoBgAAAAAECAAAA9gEAIBUAAIICACACAAAA9gEAIBUAAIICACADAAAA8wEAIBwAAPsBACAdAACAAgAgAQAAAPMBACABAAAA9gEAIAUIAACRAwAgIgAAlAMAICMAAJMDACB0AACSAwAgdQAAlQMAIA3YAQAAzgIAMNkBAACJAgAQ2gEAAM4CADDxAQEAvwIAIfIBAQC_AgAh8wEBAL8CACH0AQIAzwIAIfUBAADQAgAg9gEAANACACD3AQAA0AIAIPgBAgDPAgAh-QEBAL8CACH6AQAA0QIAIAMAAAD2AQAgAgAAiAIAMCEAAIkCACADAAAA9gEAIAIAAPcBADADAADzAQAgBtgBAADMAgAw2QEAAI8CABDaAQAAzAIAMO4BAQDHAgAh7wEBAMcCACHwAQAAzQIAIAEAAACMAgAgAQAAAIwCACAF2AEAAMwCADDZAQAAjwIAENoBAADMAgAw7gEBAMcCACHvAQEAxwIAIQADAAAAjwIAIAIAAJACADADAACMAgAgAwAAAI8CACACAACQAgAwAwAAjAIAIAMAAACPAgAgAgAAkAIAMAMAAIwCACAC7gEBAAAAAe8BAQAAAAEBFQAAlAIAIALuAQEAAAAB7wEBAAAAAQEVAACWAgAwARUAAJYCADAC7gEBAIwDACHvAQEAjAMAIQIAAACMAgAgFQAAmQIAIALuAQEAjAMAIe8BAQCMAwAhAgAAAI8CACAVAACbAgAgAgAAAI8CACAVAACbAgAgAwAAAIwCACAcAACUAgAgHQAAmQIAIAEAAACMAgAgAQAAAI8CACADCAAAjgMAICIAAJADACAjAACPAwAgBdgBAADLAgAw2QEAAKICABDaAQAAywIAMO4BAQC_AgAh7wEBAL8CACEDAAAAjwIAIAIAAKECADAhAACiAgAgAwAAAI8CACACAACQAgAwAwAAjAIAIAvYAQAAxgIAMNkBAACoAgAQ2gEAAMYCADDbAQEAxwIAIdwBAQDHAgAh3QEBAMcCACHeAQEAxwIAId8BAQDHAgAh4AFAAMgCACHsAQAAyQIAIO0BAADKAgAgAQAAAKUCACABAAAApQIAIAnYAQAAxgIAMNkBAACoAgAQ2gEAAMYCADDbAQEAxwIAIdwBAQDHAgAh3QEBAMcCACHeAQEAxwIAId8BAQDHAgAh4AFAAMgCACEAAwAAAKgCACACAACpAgAwAwAApQIAIAMAAACoAgAgAgAAqQIAMAMAAKUCACADAAAAqAIAIAIAAKkCADADAAClAgAgBtsBAQAAAAHcAQEAAAAB3QEBAAAAAd4BAQAAAAHfAQEAAAAB4AFAAAAAAQEVAACtAgAgBtsBAQAAAAHcAQEAAAAB3QEBAAAAAd4BAQAAAAHfAQEAAAAB4AFAAAAAAQEVAACvAgAwARUAAK8CADAG2wEBAIwDACHcAQEAjAMAId0BAQCMAwAh3gEBAIwDACHfAQEAjAMAIeABQACNAwAhAgAAAKUCACAVAACyAgAgBtsBAQCMAwAh3AEBAIwDACHdAQEAjAMAId4BAQCMAwAh3wEBAIwDACHgAUAAjQMAIQIAAACoAgAgFQAAtAIAIAIAAACoAgAgFQAAtAIAIAMAAAClAgAgHAAArQIAIB0AALICACABAAAApQIAIAEAAACoAgAgAwgAAIkDACAiAACLAwAgIwAAigMAIAnYAQAAvgIAMNkBAAC7AgAQ2gEAAL4CADDbAQEAvwIAIdwBAQC_AgAh3QEBAL8CACHeAQEAvwIAId8BAQC_AgAh4AFAAMACACEDAAAAqAIAIAIAALoCADAhAAC7AgAgAwAAAKgCACACAACpAgAwAwAApQIAIAnYAQAAvgIAMNkBAAC7AgAQ2gEAAL4CADDbAQEAvwIAIdwBAQC_AgAh3QEBAL8CACHeAQEAvwIAId8BAQC_AgAh4AFAAMACACEOCAAAwgIAICIAAMUCACAjAADFAgAg4QEBAAAAAeIBAQAAAATjAQEAAAAE5AEBAAAAAeUBAQAAAAHmAQEAAAAB5wEBAAAAAegBAQDEAgAh6QEBAAAAAeoBAQAAAAHrAQEAAAABCwgAAMICACAiAADDAgAgIwAAwwIAIOEBQAAAAAHiAUAAAAAE4wFAAAAABOQBQAAAAAHlAUAAAAAB5gFAAAAAAecBQAAAAAHoAUAAwQIAIQsIAADCAgAgIgAAwwIAICMAAMMCACDhAUAAAAAB4gFAAAAABOMBQAAAAATkAUAAAAAB5QFAAAAAAeYBQAAAAAHnAUAAAAAB6AFAAMECACEI4QECAAAAAeIBAgAAAATjAQIAAAAE5AECAAAAAeUBAgAAAAHmAQIAAAAB5wECAAAAAegBAgDCAgAhCOEBQAAAAAHiAUAAAAAE4wFAAAAABOQBQAAAAAHlAUAAAAAB5gFAAAAAAecBQAAAAAHoAUAAwwIAIQ4IAADCAgAgIgAAxQIAICMAAMUCACDhAQEAAAAB4gEBAAAABOMBAQAAAATkAQEAAAAB5QEBAAAAAeYBAQAAAAHnAQEAAAAB6AEBAMQCACHpAQEAAAAB6gEBAAAAAesBAQAAAAEL4QEBAAAAAeIBAQAAAATjAQEAAAAE5AEBAAAAAeUBAQAAAAHmAQEAAAAB5wEBAAAAAegBAQDFAgAh6QEBAAAAAeoBAQAAAAHrAQEAAAABCdgBAADGAgAw2QEAAKgCABDaAQAAxgIAMNsBAQDHAgAh3AEBAMcCACHdAQEAxwIAId4BAQDHAgAh3wEBAMcCACHgAUAAyAIAIQvhAQEAAAAB4gEBAAAABOMBAQAAAATkAQEAAAAB5QEBAAAAAeYBAQAAAAHnAQEAAAAB6AEBAMUCACHpAQEAAAAB6gEBAAAAAesBAQAAAAEI4QFAAAAAAeIBQAAAAATjAUAAAAAE5AFAAAAAAeUBQAAAAAHmAUAAAAAB5wFAAAAAAegBQADDAgAhAtsBAQAAAAHdAQEAAAABAtsBAQAAAAHcAQEAAAABBdgBAADLAgAw2QEAAKICABDaAQAAywIAMO4BAQC_AgAh7wEBAL8CACEF2AEAAMwCADDZAQAAjwIAENoBAADMAgAw7gEBAMcCACHvAQEAxwIAIQLuAQEAAAAB7wEBAAAAAQ3YAQAAzgIAMNkBAACJAgAQ2gEAAM4CADDxAQEAvwIAIfIBAQC_AgAh8wEBAL8CACH0AQIAzwIAIfUBAADQAgAg9gEAANACACD3AQAA0AIAIPgBAgDPAgAh-QEBAL8CACH6AQAA0QIAIA0IAADCAgAgIgAAwgIAICMAAMICACB0AADUAgAgdQAAwgIAIOEBAgAAAAHiAQIAAAAE4wECAAAABOQBAgAAAAHlAQIAAAAB5gECAAAAAecBAgAAAAHoAQIA0wIAIQThAQEAAAAFgQIBAAAAAYICAQAAAASDAgEAAAAEDwgAAMICACAiAADSAgAgIwAA0gIAIOEBgAAAAAHkAYAAAAAB5QGAAAAAAeYBgAAAAAHnAYAAAAAB6AGAAAAAAfsBAQAAAAH8AQEAAAAB_QEBAAAAAf4BgAAAAAH_AYAAAAABgAKAAAAAAQzhAYAAAAAB5AGAAAAAAeUBgAAAAAHmAYAAAAAB5wGAAAAAAegBgAAAAAH7AQEAAAAB_AEBAAAAAf0BAQAAAAH-AYAAAAAB_wGAAAAAAYACgAAAAAENCAAAwgIAICIAAMICACAjAADCAgAgdAAA1AIAIHUAAMICACDhAQIAAAAB4gECAAAABOMBAgAAAATkAQIAAAAB5QECAAAAAeYBAgAAAAHnAQIAAAAB6AECANMCACEI4QEIAAAAAeIBCAAAAATjAQgAAAAE5AEIAAAAAeUBCAAAAAHmAQgAAAAB5wEIAAAAAegBCADUAgAhDdgBAADVAgAw2QEAAPYBABDaAQAA1QIAMPEBAQDHAgAh8gEBAMcCACHzAQEAxwIAIfQBAgDWAgAh9QEAANACACD2AQAA0AIAIPcBAADQAgAg-AECANYCACH5AQEAxwIAIfoBAADXAgAgCOEBAgAAAAHiAQIAAAAE4wECAAAABOQBAgAAAAHlAQIAAAAB5gECAAAAAecBAgAAAAHoAQIAwgIAIQzhAYAAAAAB5AGAAAAAAeUBgAAAAAHmAYAAAAAB5wGAAAAAAegBgAAAAAH7AQEAAAAB_AEBAAAAAf0BAQAAAAH-AYAAAAAB_wGAAAAAAYACgAAAAAEH2AEAANgCADDZAQAA8AEAENoBAADYAgAw7wEBAL8CACHxAQEAvwIAIYQCQADAAgAhhQJAAMACACEI2AEAANkCADDZAQAA2gEAENoBAADZAgAw4AFAAMACACHvAQEAvwIAIYUCQADAAgAhhgIBAL8CACGHAiAA2gIAIQUIAADCAgAgIgAA3AIAICMAANwCACDhASAAAAAB6AEgANsCACEFCAAAwgIAICIAANwCACAjAADcAgAg4QEgAAAAAegBIADbAgAhAuEBIAAAAAHoASAA3AIAIQjYAQAA3QIAMNkBAADHAQAQ2gEAAN0CADDgAUAAyAIAIe8BAQDHAgAhhQJAAMgCACGGAgEAxwIAIYcCIADeAgAhAuEBIAAAAAHoASAA3AIAIQLvAQEAAAABhgIBAAAAAQfYAQAA4AIAMNkBAADBAQAQ2gEAAOACADDgAUAAwAIAIe8BAQC_AgAh8QEBAL8CACGHAiAA2gIAIQbYAQAA4QIAMNkBAACrAQAQ2gEAAOECADCJAgEAvwIAIYoCAQC_AgAhiwICAM8CACEI2AEAAOICADDZAQAAlQEAENoBAADiAgAw4AFAAMACACHvAQEAvwIAIYUCQADAAgAhjAIBAL8CACGOAgAA4wKOAiIHCAAAwgIAICIAAOUCACAjAADlAgAg4QEAAACOAgLiAQAAAI4CCOMBAAAAjgII6AEAAOQCjgIiBwgAAMICACAiAADlAgAgIwAA5QIAIOEBAAAAjgIC4gEAAACOAgjjAQAAAI4CCOgBAADkAo4CIgThAQAAAI4CAuIBAAAAjgII4wEAAACOAgjoAQAA5QKOAiIO2AEAAOYCADDZAQAAfwAQ2gEAAOYCADDgAUAAwAIAIfEBAQC_AgAhhQJAAMACACGPAgEAvwIAIZACAQC_AgAhkQIBAL8CACGSAgEAvwIAIZMCAQC_AgAhlAIBAOcCACGVAkAA6AIAIZYCAQDnAgAhDggAAOoCACAiAADtAgAgIwAA7QIAIOEBAQAAAAHiAQEAAAAF4wEBAAAABeQBAQAAAAHlAQEAAAAB5gEBAAAAAecBAQAAAAHoAQEA7AIAIekBAQAAAAHqAQEAAAAB6wEBAAAAAQsIAADqAgAgIgAA6wIAICMAAOsCACDhAUAAAAAB4gFAAAAABeMBQAAAAAXkAUAAAAAB5QFAAAAAAeYBQAAAAAHnAUAAAAAB6AFAAOkCACELCAAA6gIAICIAAOsCACAjAADrAgAg4QFAAAAAAeIBQAAAAAXjAUAAAAAF5AFAAAAAAeUBQAAAAAHmAUAAAAAB5wFAAAAAAegBQADpAgAhCOEBAgAAAAHiAQIAAAAF4wECAAAABeQBAgAAAAHlAQIAAAAB5gECAAAAAecBAgAAAAHoAQIA6gIAIQjhAUAAAAAB4gFAAAAABeMBQAAAAAXkAUAAAAAB5QFAAAAAAeYBQAAAAAHnAUAAAAAB6AFAAOsCACEOCAAA6gIAICIAAO0CACAjAADtAgAg4QEBAAAAAeIBAQAAAAXjAQEAAAAF5AEBAAAAAeUBAQAAAAHmAQEAAAAB5wEBAAAAAegBAQDsAgAh6QEBAAAAAeoBAQAAAAHrAQEAAAABC-EBAQAAAAHiAQEAAAAF4wEBAAAABeQBAQAAAAHlAQEAAAAB5gEBAAAAAecBAQAAAAHoAQEA7QIAIekBAQAAAAHqAQEAAAAB6wEBAAAAAQnYAQAA7gIAMNkBAABpABDaAQAA7gIAMOABQADAAgAhjwIBAL8CACGXAgEAvwIAIZgCAQDnAgAhmQJAAOgCACGaAgEA5wIAIQnYAQAA7wIAMNkBAABWABDaAQAA7wIAMOABQADIAgAhjwIBAMcCACGXAgEAxwIAIZgCAQDwAgAhmQJAAPECACGaAgEA8AIAIQvhAQEAAAAB4gEBAAAABeMBAQAAAAXkAQEAAAAB5QEBAAAAAeYBAQAAAAHnAQEAAAAB6AEBAO0CACHpAQEAAAAB6gEBAAAAAesBAQAAAAEI4QFAAAAAAeIBQAAAAAXjAUAAAAAF5AFAAAAAAeUBQAAAAAHmAUAAAAAB5wFAAAAAAegBQADrAgAhCdgBAADyAgAw2QEAAFAAENoBAADyAgAw4AFAAMACACHvAQEAvwIAIY8CAQC_AgAhmAIBAOcCACGZAkAA6AIAIZoCAQDnAgAhCdgBAADzAgAw2QEAAD0AENoBAADzAgAw4AFAAMgCACHvAQEAxwIAIY8CAQDHAgAhmAIBAPACACGZAkAA8QIAIZoCAQDwAgAhEtgBAAD0AgAw2QEAADcAENoBAAD0AgAw4AFAAMACACHuAQEA5wIAIYUCQADAAgAhhgIBAOcCACGPAgEAvwIAIZICAQC_AgAhkwIBAL8CACGVAkAA6AIAIZYCAQDnAgAhmwIBAOcCACGcAgEAvwIAIZ0CIADaAgAhngIgANoCACGfAkAA6AIAIaACAQDnAgAhAu8BAQAAAAHxAQEAAAABCAUAAPcCACDYAQAA9gIAMNkBAAAZABDaAQAA9gIAMO8BAQDHAgAh8QEBAMcCACGEAkAAyAIAIYUCQADIAgAhGwEAAIMDACAEAACEAwAgCQAAhQMAIAwAAIYDACANAACGAwAgDgAAhwMAIA8AAIgDACDYAQAAggMAMNkBAAADABDaAQAAggMAMOABQADIAgAh7gEBAPACACGFAkAAyAIAIYYCAQDwAgAhjwIBAMcCACGSAgEAxwIAIZMCAQDHAgAhlQJAAPECACGWAgEA8AIAIZsCAQDwAgAhnAIBAMcCACGdAiAA3gIAIZ4CIADeAgAhnwJAAPECACGgAgEA8AIAIacCAAADACCoAgAAAwAgAu8BAQAAAAHxAQEAAAABCAUAAPcCACDYAQAA-QIAMNkBAAAVABDaAQAA-QIAMOABQADIAgAh7wEBAMcCACHxAQEAxwIAIYcCIADeAgAhAokCAQAAAAGKAgEAAAABCAoAAPcCACALAAD3AgAg2AEAAPsCADDZAQAAEAAQ2gEAAPsCADCJAgEAxwIAIYoCAQDHAgAhiwICANYCACEC7wEBAAAAAYwCAQAAAAEJBgAA_wIAINgBAAD9AgAw2QEAAAsAENoBAAD9AgAw4AFAAMgCACHvAQEAxwIAIYUCQADIAgAhjAIBAMcCACGOAgAA_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_wIAINgBAAD9AgAw2QEAAAsAENoBAAD9AgAw4AFAAMgCACHvAQEAxwIAIYUCQADIAgAhjAIBAMcCACGOAgAA_gKOAiKjAgAA_AIAIAIAAAANACAVAADMAwAgAgAAAMoDACAVAADLAwAgCNgBAADJAwAw2QEAAMoDABDaAQAAyQMAMOABQADIAgAh7wEBAMcCACGFAkAAyAIAIYwCAQDHAgAhjgIAAP4CjgIiCNgBAADJAwAw2QEAAMoDABDaAQAAyQMAMOABQADIAgAh7wEBAMcCACGFAkAAyAIAIYwCAQDHAgAhjgIAAP4CjgIiBOABQACNAwAh7wEBAIwDACGFAkAAjQMAIY4CAAC3A44CIgTgAUAAjQMAIe8BAQCMAwAhhQJAAI0DACGOAgAAtwOOAiIE4AFAAAAAAe8BAQAAAAGFAkAAAAABjgIAAACOAgIDHAAAvwQAIKkCAADABAAgrwIAAAEAIAQcAADCAwAwqQIAAMMDADCrAgAAxQMAIK8CAADGAwAwAAAAAAAAAAAABxwAALQEACAdAAC9BAAgqQIAALUEACCqAgAAvAQAIK0CAAADACCuAgAAAwAgrwIAAAEAIAscAACZBAAwHQAAngQAMKkCAACaBAAwqgIAAJsEADCrAgAAnAQAIKwCAACdBAAwrQIAAJ0EADCuAgAAnQQAMK8CAACdBAAwsAIAAJ8EADCxAgAAoAQAMAscAACNBAAwHQAAkgQAMKkCAACOBAAwqgIAAI8EADCrAgAAkAQAIKwCAACRBAAwrQIAAJEEADCuAgAAkQQAMK8CAACRBAAwsAIAAJMEADCxAgAAlAQAMAscAACEBAAwHQAAiAQAMKkCAACFBAAwqgIAAIYEADCrAgAAhwQAIKwCAAD8AwAwrQIAAPwDADCuAgAA_AMAMK8CAAD8AwAwsAIAAIkEADCxAgAA_wMAMAscAAD4AwAwHQAA_QMAMKkCAAD5AwAwqgIAAPoDADCrAgAA-wMAIKwCAAD8AwAwrQIAAPwDADCuAgAA_AMAMK8CAAD8AwAwsAIAAP4DADCxAgAA_wMAMAscAADsAwAwHQAA8QMAMKkCAADtAwAwqgIAAO4DADCrAgAA7wMAIKwCAADwAwAwrQIAAPADADCuAgAA8AMAMK8CAADwAwAwsAIAAPIDADCxAgAA8wMAMAscAADgAwAwHQAA5QMAMKkCAADhAwAwqgIAAOIDADCrAgAA4wMAIKwCAADkAwAwrQIAAOQDADCuAgAA5AMAMK8CAADkAwAwsAIAAOYDADCxAgAA5wMAMAPvAQEAAAABhAJAAAAAAYUCQAAAAAECAAAAGwAgHAAA6wMAIAMAAAAbACAcAADrAwAgHQAA6gMAIAEVAAC7BAAwCQUAAPcCACDYAQAA9gIAMNkBAAAZABDaAQAA9gIAMO8BAQDHAgAh8QEBAMcCACGEAkAAyAIAIYUCQADIAgAhoQIAAPUCACACAAAAGwAgFQAA6gMAIAIAAADoAwAgFQAA6QMAIAfYAQAA5wMAMNkBAADoAwAQ2gEAAOcDADDvAQEAxwIAIfEBAQDHAgAhhAJAAMgCACGFAkAAyAIAIQfYAQAA5wMAMNkBAADoAwAQ2gEAAOcDADDvAQEAxwIAIfEBAQDHAgAhhAJAAMgCACGFAkAAyAIAIQPvAQEAjAMAIYQCQACNAwAhhQJAAI0DACED7wEBAIwDACGEAkAAjQMAIYUCQACNAwAhA-8BAQAAAAGEAkAAAAABhQJAAAAAAQPgAUAAAAAB7wEBAAAAAYcCIAAAAAECAAAAFwAgHAAA9wMAIAMAAAAXACAcAAD3AwAgHQAA9gMAIAEVAAC6BAAwCQUAAPcCACDYAQAA-QIAMNkBAAAVABDaAQAA-QIAMOABQADIAgAh7wEBAMcCACHxAQEAxwIAIYcCIADeAgAhoQIAAPgCACACAAAAFwAgFQAA9gMAIAIAAAD0AwAgFQAA9QMAIAfYAQAA8wMAMNkBAAD0AwAQ2gEAAPMDADDgAUAAyAIAIe8BAQDHAgAh8QEBAMcCACGHAiAA3gIAIQfYAQAA8wMAMNkBAAD0AwAQ2gEAAPMDADDgAUAAyAIAIe8BAQDHAgAh8QEBAMcCACGHAiAA3gIAIQPgAUAAjQMAIe8BAQCMAwAhhwIgAKUDACED4AFAAI0DACHvAQEAjAMAIYcCIAClAwAhA-ABQAAAAAHvAQEAAAABhwIgAAAAAQMKAACyAwAgiQIBAAAAAYsCAgAAAAECAAAAEgAgHAAAgwQAIAMAAAASACAcAACDBAAgHQAAggQAIAEVAAC5BAAwCQoAAPcCACALAAD3AgAg2AEAAPsCADDZAQAAEAAQ2gEAAPsCADCJAgEAxwIAIYoCAQDHAgAhiwICANYCACGiAgAA-gIAIAIAAAASACAVAACCBAAgAgAAAIAEACAVAACBBAAgBtgBAAD_AwAw2QEAAIAEABDaAQAA_wMAMIkCAQDHAgAhigIBAMcCACGLAgIA1gIAIQbYAQAA_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_AwAhoAIBAL4DACEUBAAA2gMAIAkAANsDACAMAADcAwAgDQAA3QMAIA4AAN4DACAPAADfAwAg4AFAAI0DACHuAQEAvgMAIYUCQACNAwAhhgIBAL4DACGPAgEAjAMAIZICAQCMAwAhkwIBAIwDACGVAkAAvwMAIZYCAQC-AwAhnAIBAIwDACGdAiAApQMAIZ4CIAClAwAhnwJAAL8DACGgAgEAvgMAIRQEAAClBAAgCQAApgQAIAwAAKcEACANAACoBAAgDgAAqQQAIA8AAKoEACDgAUAAAAAB7gEBAAAAAYUCQAAAAAGGAgEAAAABjwIBAAAAAZICAQAAAAGTAgEAAAABlQJAAAAAAZYCAQAAAAGcAgEAAAABnQIgAAAAAZ4CIAAAAAGfAkAAAAABoAIBAAAAAQQcAACZBAAwqQIAAJoEADCrAgAAnAQAIK8CAACdBAAwBBwAAI0EADCpAgAAjgQAMKsCAACQBAAgrwIAAJEEADAEHAAAhAQAMKkCAACFBAAwqwIAAIcEACCvAgAA_AMAMAQcAAD4AwAwqQIAAPkDADCrAgAA-wMAIK8CAAD8AwAwBBwAAOwDADCpAgAA7QMAMKsCAADvAwAgrwIAAPADADAEHAAA4AMAMKkCAADhAwAwqwIAAOMDACCvAgAA5AMAMAMcAAC0BAAgqQIAALUEACCvAgAAAQAgDgEAAKwEACAEAACvBAAgCQAAsAQAIAwAALEEACANAACxBAAgDgAAsgQAIA8AALMEACDuAQAAugMAIIYCAAC6AwAglQIAALoDACCWAgAAugMAIJsCAAC6AwAgnwIAALoDACCgAgAAugMAIAUFAACsBAAgBwAArgQAIJQCAAC6AwAglQIAALoDACCWAgAAugMAIAAAAAAAABUBAACrBAAgCQAApgQAIAwAAKcEACANAACoBAAgDgAAqQQAIA8AAKoEACDgAUAAAAAB7gEBAAAAAYUCQAAAAAGGAgEAAAABjwIBAAAAAZICAQAAAAGTAgEAAAABlQJAAAAAAZYCAQAAAAGbAgEAAAABnAIBAAAAAZ0CIAAAAAGeAiAAAAABnwJAAAAAAaACAQAAAAECAAAAAQAgHAAAtAQAIA7gAUAAAAAB7gEBAAAAAYUCQAAAAAGGAgEAAAABjwIBAAAAAZICAQAAAAGTAgEAAAABlQJAAAAAAZYCAQAAAAGcAgEAAAABnQIgAAAAAZ4CIAAAAAGfAkAAAAABoAIBAAAAAQrgAUAAAAABhQJAAAAAAY8CAQAAAAGQAgEAAAABkQIBAAAAAZICAQAAAAGTAgEAAAABlAIBAAAAAZUCQAAAAAGWAgEAAAABAooCAQAAAAGLAgIAAAABAokCAQAAAAGLAgIAAAABA-ABQAAAAAHvAQEAAAABhwIgAAAAAQPvAQEAAAABhAJAAAAAAYUCQAAAAAEDAAAAAwAgHAAAtAQAIB0AAL4EACAXAAAAAwAgAQAA2QMAIAkAANsDACAMAADcAwAgDQAA3QMAIA4AAN4DACAPAADfAwAgFQAAvgQAIOABQACNAwAh7gEBAL4DACGFAkAAjQMAIYYCAQC-AwAhjwIBAIwDACGSAgEAjAMAIZMCAQCMAwAhlQJAAL8DACGWAgEAvgMAIZsCAQC-AwAhnAIBAIwDACGdAiAApQMAIZ4CIAClAwAhnwJAAL8DACGgAgEAvgMAIRUBAADZAwAgCQAA2wMAIAwAANwDACANAADdAwAgDgAA3gMAIA8AAN8DACDgAUAAjQMAIe4BAQC-AwAhhQJAAI0DACGGAgEAvgMAIY8CAQCMAwAhkgIBAIwDACGTAgEAjAMAIZUCQAC_AwAhlgIBAL4DACGbAgEAvgMAIZwCAQCMAwAhnQIgAKUDACGeAiAApQMAIZ8CQAC_AwAhoAIBAL4DACEVAQAAqwQAIAQAAKUEACAMAACnBAAgDQAAqAQAIA4AAKkEACAPAACqBAAg4AFAAAAAAe4BAQAAAAGFAkAAAAABhgIBAAAAAY8CAQAAAAGSAgEAAAABkwIBAAAAAZUCQAAAAAGWAgEAAAABmwIBAAAAAZwCAQAAAAGdAiAAAAABngIgAAAAAZ8CQAAAAAGgAgEAAAABAgAAAAEAIBwAAL8EACAE4AFAAAAAAe8BAQAAAAGFAkAAAAABjgIAAACOAgIDAAAAAwAgHAAAvwQAIB0AAMQEACAXAAAAAwAgAQAA2QMAIAQAANoDACAMAADcAwAgDQAA3QMAIA4AAN4DACAPAADfAwAgFQAAxAQAIOABQACNAwAh7gEBAL4DACGFAkAAjQMAIYYCAQC-AwAhjwIBAIwDACGSAgEAjAMAIZMCAQCMAwAhlQJAAL8DACGWAgEAvgMAIZsCAQC-AwAhnAIBAIwDACGdAiAApQMAIZ4CIAClAwAhnwJAAL8DACGgAgEAvgMAIRUBAADZAwAgBAAA2gMAIAwAANwDACANAADdAwAgDgAA3gMAIA8AAN8DACDgAUAAjQMAIe4BAQC-AwAhhQJAAI0DACGGAgEAvgMAIY8CAQCMAwAhkgIBAIwDACGTAgEAjAMAIZUCQAC_AwAhlgIBAL4DACGbAgEAvgMAIZwCAQCMAwAhnQIgAKUDACGeAiAApQMAIZ8CQAC_AwAhoAIBAL4DACEMBQAAzgMAIOABQAAAAAHxAQEAAAABhQJAAAAAAY8CAQAAAAGQAgEAAAABkQIBAAAAAZICAQAAAAGTAgEAAAABlAIBAAAAAZUCQAAAAAGWAgEAAAABAgAAAAkAIBwAAMUEACADAAAABwAgHAAAxQQAIB0AAMkEACAOAAAABwAgBQAAwAMAIBUAAMkEACDgAUAAjQMAIfEBAQCMAwAhhQJAAI0DACGPAgEAjAMAIZACAQCMAwAhkQIBAIwDACGSAgEAjAMAIZMCAQCMAwAhlAIBAL4DACGVAkAAvwMAIZYCAQC-AwAhDAUAAMADACDgAUAAjQMAIfEBAQCMAwAhhQJAAI0DACGPAgEAjAMAIZACAQCMAwAhkQIBAIwDACGSAgEAjAMAIZMCAQCMAwAhlAIBAL4DACGVAkAAvwMAIZYCAQC-AwAhFQEAAKsEACAEAAClBAAgCQAApgQAIAwAAKcEACAOAACpBAAgDwAAqgQAIOABQAAAAAHuAQEAAAABhQJAAAAAAYYCAQAAAAGPAgEAAAABkgIBAAAAAZMCAQAAAAGVAkAAAAABlgIBAAAAAZsCAQAAAAGcAgEAAAABnQIgAAAAAZ4CIAAAAAGfAkAAAAABoAIBAAAAAQIAAAABACAcAADKBAAgFQEAAKsEACAEAAClBAAgCQAApgQAIA0AAKgEACAOAACpBAAgDwAAqgQAIOABQAAAAAHuAQEAAAABhQJAAAAAAYYCAQAAAAGPAgEAAAABkgIBAAAAAZMCAQAAAAGVAkAAAAABlgIBAAAAAZsCAQAAAAGcAgEAAAABnQIgAAAAAZ4CIAAAAAGfAkAAAAABoAIBAAAAAQIAAAABACAcAADMBAAgAwAAAAMAIBwAAMoEACAdAADQBAAgFwAAAAMAIAEAANkDACAEAADaAwAgCQAA2wMAIAwAANwDACAOAADeAwAgDwAA3wMAIBUAANAEACDgAUAAjQMAIe4BAQC-AwAhhQJAAI0DACGGAgEAvgMAIY8CAQCMAwAhkgIBAIwDACGTAgEAjAMAIZUCQAC_AwAhlgIBAL4DACGbAgEAvgMAIZwCAQCMAwAhnQIgAKUDACGeAiAApQMAIZ8CQAC_AwAhoAIBAL4DACEVAQAA2QMAIAQAANoDACAJAADbAwAgDAAA3AMAIA4AAN4DACAPAADfAwAg4AFAAI0DACHuAQEAvgMAIYUCQACNAwAhhgIBAL4DACGPAgEAjAMAIZICAQCMAwAhkwIBAIwDACGVAkAAvwMAIZYCAQC-AwAhmwIBAL4DACGcAgEAjAMAIZ0CIAClAwAhngIgAKUDACGfAkAAvwMAIaACAQC-AwAhAwAAAAMAIBwAAMwEACAdAADTBAAgFwAAAAMAIAEAANkDACAEAADaAwAgCQAA2wMAIA0AAN0DACAOAADeAwAgDwAA3wMAIBUAANMEACDgAUAAjQMAIe4BAQC-AwAhhQJAAI0DACGGAgEAvgMAIY8CAQCMAwAhkgIBAIwDACGTAgEAjAMAIZUCQAC_AwAhlgIBAL4DACGbAgEAvgMAIZwCAQCMAwAhnQIgAKUDACGeAiAApQMAIZ8CQAC_AwAhoAIBAL4DACEVAQAA2QMAIAQAANoDACAJAADbAwAgDQAA3QMAIA4AAN4DACAPAADfAwAg4AFAAI0DACHuAQEAvgMAIYUCQACNAwAhhgIBAL4DACGPAgEAjAMAIZICAQCMAwAhkwIBAIwDACGVAkAAvwMAIZYCAQC-AwAhmwIBAL4DACGcAgEAjAMAIZ0CIAClAwAhngIgAKUDACGfAkAAvwMAIaACAQC-AwAhFQEAAKsEACAEAAClBAAgCQAApgQAIAwAAKcEACANAACoBAAgDwAAqgQAIOABQAAAAAHuAQEAAAABhQJAAAAAAYYCAQAAAAGPAgEAAAABkgIBAAAAAZMCAQAAAAGVAkAAAAABlgIBAAAAAZsCAQAAAAGcAgEAAAABnQIgAAAAAZ4CIAAAAAGfAkAAAAABoAIBAAAAAQIAAAABACAcAADUBAAgAwAAAAMAIBwAANQEACAdAADYBAAgFwAAAAMAIAEAANkDACAEAADaAwAgCQAA2wMAIAwAANwDACANAADdAwAgDwAA3wMAIBUAANgEACDgAUAAjQMAIe4BAQC-AwAhhQJAAI0DACGGAgEAvgMAIY8CAQCMAwAhkgIBAIwDACGTAgEAjAMAIZUCQAC_AwAhlgIBAL4DACGbAgEAvgMAIZwCAQCMAwAhnQIgAKUDACGeAiAApQMAIZ8CQAC_AwAhoAIBAL4DACEVAQAA2QMAIAQAANoDACAJAADbAwAgDAAA3AMAIA0AAN0DACAPAADfAwAg4AFAAI0DACHuAQEAvgMAIYUCQACNAwAhhgIBAL4DACGPAgEAjAMAIZICAQCMAwAhkwIBAIwDACGVAkAAvwMAIZYCAQC-AwAhmwIBAL4DACGcAgEAjAMAIZ0CIAClAwAhngIgAKUDACGfAkAAvwMAIaACAQC-AwAhFQEAAKsEACAEAAClBAAgCQAApgQAIAwAAKcEACANAACoBAAgDgAAqQQAIOABQAAAAAHuAQEAAAABhQJAAAAAAYYCAQAAAAGPAgEAAAABkgIBAAAAAZMCAQAAAAGVAkAAAAABlgIBAAAAAZsCAQAAAAGcAgEAAAABnQIgAAAAAZ4CIAAAAAGfAkAAAAABoAIBAAAAAQIAAAABACAcAADZBAAgAwAAAAMAIBwAANkEACAdAADdBAAgFwAAAAMAIAEAANkDACAEAADaAwAgCQAA2wMAIAwAANwDACANAADdAwAgDgAA3gMAIBUAAN0EACDgAUAAjQMAIe4BAQC-AwAhhQJAAI0DACGGAgEAvgMAIY8CAQCMAwAhkgIBAIwDACGTAgEAjAMAIZUCQAC_AwAhlgIBAL4DACGbAgEAvgMAIZwCAQCMAwAhnQIgAKUDACGeAiAApQMAIZ8CQAC_AwAhoAIBAL4DACEVAQAA2QMAIAQAANoDACAJAADbAwAgDAAA3AMAIA0AAN0DACAOAADeAwAg4AFAAI0DACHuAQEAvgMAIYUCQACNAwAhhgIBAL4DACGPAgEAjAMAIZICAQCMAwAhkwIBAIwDACGVAkAAvwMAIZYCAQC-AwAhmwIBAL4DACGcAgEAjAMAIZ0CIAClAwAhngIgAKUDACGfAkAAvwMAIaACAQC-AwAhCAEEAQQGAQgACAkKAgwTBQ0UBQ4YBg8cBwMFAAEHDgMIAAQBBgACAQcPAAIKAAELAAEBBQABAQUAAQYEHQAJHgAMHwANIAAOIQAPIgAAAQEsAQEBMgEDCAANIgAOIwAPAAAAAwgADSIADiMADwAAAAMIABUiABYjABcAAAADCAAVIgAWIwAXAAAAAwgAHSIAHiMAHwAAAAMIAB0iAB4jAB8BBQABAQUAAQMIACQiACUjACYAAAADCAAkIgAlIwAmAQYAAgEGAAIDCAArIgAsIwAtAAAAAwgAKyIALCMALQIKAAELAAECCgABCwABBQgAMiIANSMANnQAM3UANAAAAAAABQgAMiIANSMANnQAM3UANAEFAAEBBQABAwgAOyIAPCMAPQAAAAMIADsiADwjAD0AAAADCABDIgBEIwBFAAAAAwgAQyIARCMARQEFAAEBBQABAwgASiIASyMATAAAAAMIAEoiAEsjAEwAAAAFCABSIgBVIwBWdABTdQBUAAAAAAAFCABSIgBVIwBWdABTdQBUAAAAAwgAXCIAXSMAXgAAAAMIAFwiAF0jAF4AAAADCABkIgBlIwBmAAAAAwgAZCIAZSMAZhACAREjARIkARMlARQmARYoARcqCRgrChkuARowCRsxCx4zAR80ASA1CSQ4DCU5ECY7ESc8ESg_ESlAESpBEStDESxFCS1GEi5IES9KCTBLEzFMETJNETNOCTRRFDVSGDZUGTdVGThYGTlZGTpaGTtcGTxeCT1fGj5hGT9jCUBkG0FlGUJmGUNnCURqHEVrIEZsAkdtAkhuAklvAkpwAktyAkx0CU11IU53Ak95CVB6IlF7AlJ8AlN9CVSAASNVgQEnVoIBA1eDAQNYhAEDWYUBA1qGAQNbiAEDXIoBCV2LAShejQEDX48BCWCQASlhkQEDYpIBA2OTAQlklgEqZZcBLmaYAQVnmQEFaJoBBWmbAQVqnAEFa54BBWygAQltoQEvbqMBBW-lAQlwpgEwcacBBXKoAQVzqQEJdqwBMXetATd4rgEGea8BBnqwAQZ7sQEGfLIBBn20AQZ-tgEJf7cBOIABuQEGgQG7AQmCAbwBOYMBvQEGhAG-AQaFAb8BCYYBwgE6hwHDAT6IAcUBP4kBxgE_igHJAT-LAcoBP4wBywE_jQHNAT-OAc8BCY8B0AFAkAHSAT-RAdQBCZIB1QFBkwHWAT-UAdcBP5UB2AEJlgHbAUKXAdwBRpgB3QEHmQHeAQeaAd8BB5sB4AEHnAHhAQedAeMBB54B5QEJnwHmAUegAegBB6EB6gEJogHrAUijAewBB6QB7QEHpQHuAQmmAfEBSacB8gFNqAH0AU6pAfUBTqoB-AFOqwH5AU6sAfoBTq0B_AFOrgH-AQmvAf8BT7ABgQJOsQGDAgmyAYQCULMBhQJOtAGGAk61AYcCCbYBigJRtwGLAle4AY0CWLkBjgJYugGRAli7AZICWLwBkwJYvQGVAli-AZcCCb8BmAJZwAGaAljBAZwCCcIBnQJawwGeAljEAZ8CWMUBoAIJxgGjAlvHAaQCX8gBpgJgyQGnAmDKAaoCYMsBqwJgzAGsAmDNAa4CYM4BsAIJzwGxAmHQAbMCYNEBtQIJ0gG2AmLTAbcCYNQBuAJg1QG5AgnWAbwCY9cBvQJn" } 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 84195b1..90df309 100644 --- a/packages/forums-prisma-client/package.json +++ b/packages/forums-prisma-client/package.json @@ -1,5 +1,5 @@ { - "name": "prisma-client-07279bd72154b430dd00280c65b4b8b34dd7a3e1e8abdf51a8f22b2cf0650926", + "name": "prisma-client-8b145d316a03460c3572ad5af2d2d6742bd79e4fbf9dcb2b8e29bca301f35781", "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 67b2c09..74db76d 100644 --- a/packages/forums-prisma-client/schema.prisma +++ b/packages/forums-prisma-client/schema.prisma @@ -131,9 +131,11 @@ model TopicClosure { @@schema("forums") } +// Explicit topic choices override challenge-wide notification preferences. model TopicWatch { topicId String @db.VarChar(14) memberId String @db.VarChar(64) + watching Boolean @default(true) createdAt DateTime @default(now()) topic Topic @relation(fields: [topicId], references: [id], onDelete: Cascade) @@ -143,6 +145,19 @@ model TopicWatch { @@schema("forums") } +// Explicit challenge-wide preferences override resource-based default watches. +model ChallengeForumWatch { + challengeId String @db.VarChar(64) + memberId String @db.VarChar(64) + watching Boolean + createdAt DateTime @default(now()) + updatedAt DateTime @default(now()) @updatedAt + + @@id([challengeId, memberId]) + @@index([memberId]) + @@schema("forums") +} + model TopicReadState { topicId String @db.VarChar(14) memberId String @db.VarChar(64) diff --git a/prisma/migrations/20261016000000_challenge_forum_watch/migration.sql b/prisma/migrations/20261016000000_challenge_forum_watch/migration.sql new file mode 100644 index 0000000..91eb73a --- /dev/null +++ b/prisma/migrations/20261016000000_challenge_forum_watch/migration.sql @@ -0,0 +1,10 @@ +CREATE TABLE "forums"."ChallengeForumWatch" ( + "challengeId" VARCHAR(64) NOT NULL, + "memberId" VARCHAR(64) NOT NULL, + "watching" BOOLEAN NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT "ChallengeForumWatch_pkey" PRIMARY KEY ("challengeId", "memberId") +); + +CREATE INDEX "ChallengeForumWatch_memberId_idx" ON "forums"."ChallengeForumWatch"("memberId"); diff --git a/prisma/migrations/20261020000000_topic_watch_preferences/migration.sql b/prisma/migrations/20261020000000_topic_watch_preferences/migration.sql new file mode 100644 index 0000000..b96fa3b --- /dev/null +++ b/prisma/migrations/20261020000000_topic_watch_preferences/migration.sql @@ -0,0 +1,2 @@ +-- Preserve existing watches; false rows record explicit topic opt-outs. +ALTER TABLE "forums"."TopicWatch" ADD COLUMN "watching" BOOLEAN NOT NULL DEFAULT true; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index ed1c300..e84a99b 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -131,9 +131,11 @@ model TopicClosure { @@schema("forums") } +// Explicit topic choices override challenge-wide notification preferences. model TopicWatch { topicId String @db.VarChar(14) memberId String @db.VarChar(64) + watching Boolean @default(true) createdAt DateTime @default(now()) topic Topic @relation(fields: [topicId], references: [id], onDelete: Cascade) @@ -143,6 +145,19 @@ model TopicWatch { @@schema("forums") } +// Explicit challenge-wide preferences override resource-based default watches. +model ChallengeForumWatch { + challengeId String @db.VarChar(64) + memberId String @db.VarChar(64) + watching Boolean + createdAt DateTime @default(now()) + updatedAt DateTime @default(now()) @updatedAt + + @@id([challengeId, memberId]) + @@index([memberId]) + @@schema("forums") +} + model TopicReadState { topicId String @db.VarChar(14) memberId String @db.VarChar(64) diff --git a/src/config/notifications.config.ts b/src/config/notifications.config.ts index 3d516fb..0d73e12 100644 --- a/src/config/notifications.config.ts +++ b/src/config/notifications.config.ts @@ -5,7 +5,7 @@ * notification publisher. Missing template configuration disables notification * publishing without affecting forum writes. * - * @returns SendGrid template, Bus and Challenge API bases, Kafka error topic, and Auth0 M2M values. + * @returns SendGrid template, Bus, Challenge API, and public web bases, Kafka error topic, and Auth0 M2M values. * @throws Does not throw; consumers decide whether optional values are required. */ export default () => ({ @@ -14,6 +14,7 @@ export default () => ({ busApiUrl: process.env.BUSAPI_URL, busApiUrlAlias: process.env.BUS_API_URL, topcoderApiUrlBase: process.env.TOPCODER_API_URL_BASE, + topcoderUrl: process.env.TOPCODER_URL, challengeApiUrl: process.env.CHALLENGE_API_URL, kafkaErrorTopic: process.env.KAFKA_ERROR_TOPIC, auth0Url: process.env.AUTH0_URL, diff --git a/src/forums/challenge-api.service.spec.ts b/src/forums/challenge-api.service.spec.ts index 05e4d7d..0d39b5c 100644 --- a/src/forums/challenge-api.service.spec.ts +++ b/src/forums/challenge-api.service.spec.ts @@ -78,6 +78,35 @@ describe('ChallengeApiService', () => { ); }); + it('builds an environment-correct Opportunities link from the shared API host', () => { + expect(createService().getChallengeUrl('challenge/id?query')).toBe( + 'https://www.topcoder-dev.com/opportunities/challenge/challenge%2Fid%3Fquery', + ); + }); + + it('uses the explicit Topcoder web URL for public challenge links', () => { + const service = createService({ + 'notifications.topcoderUrl': 'https://community.example.com/ignored/path?query=1', + }); + + expect(service.getChallengeUrl('challenge-1')).toBe( + 'https://community.example.com/opportunities/challenge/challenge-1', + ); + }); + + it.each([ + [{ 'notifications.topcoderApiUrlBase': undefined }, 'must configure'], + [ + { 'notifications.topcoderUrl': 'javascript:alert(1)' }, + 'absolute HTTP(S) URL', + ], + [{ 'notifications.topcoderUrl': 'not-a-url' }, 'absolute HTTP(S) URL'], + ])('rejects unusable public challenge URL configuration: %p', (overrides, message) => { + expect(() => createService(overrides).getChallengeUrl('challenge-1')).toThrow( + message, + ); + }); + it.each([ ['notifications.topcoderApiUrlBase', 'CHALLENGE_API_URL'], ['notifications.m2mClientId', 'M2M_CLIENT_ID'], diff --git a/src/forums/challenge-api.service.ts b/src/forums/challenge-api.service.ts index 6296542..8462cec 100644 --- a/src/forums/challenge-api.service.ts +++ b/src/forums/challenge-api.service.ts @@ -5,7 +5,7 @@ import { auth } from 'tc-core-library-js'; const CHALLENGE_REQUEST_TIMEOUT_MS = 5_000; /** - * Looks up challenge titles for forum notifications through the Challenge API. + * Resolves challenge titles and public links for forum notifications. * The shared M2M library reuses cached tokens for the bus publisher's credentials; * client initialization is lazy so missing outbound configuration does not block startup. */ @@ -91,6 +91,58 @@ export class ChallengeApiService { return challenge.name; } + /** + * Builds the public Opportunities URL for a challenge notification. + * + * `TOPCODER_URL` is authoritative when configured. Existing deployments can + * fall back to `TOPCODER_API_URL_BASE`; its conventional `api.` host prefix + * is changed to `www.` while preserving the environment domain. + * + * @param challengeId Effective challenge id inherited by the notified topic. + * @returns Absolute public challenge-details URL. + * @throws Error when neither web nor API base configuration is usable. + */ + getChallengeUrl(challengeId: string): string { + const configuredWebBase = this.configService + .get('notifications.topcoderUrl') + ?.trim(); + const configuredApiBase = this.configService + .get('notifications.topcoderApiUrlBase') + ?.trim(); + const configuredBase = configuredWebBase || configuredApiBase; + + if (!configuredBase) { + throw new Error( + 'TOPCODER_URL or TOPCODER_API_URL_BASE must configure public challenge links.', + ); + } + + let publicUrl: URL; + + try { + publicUrl = new URL(configuredBase); + } catch { + throw new Error( + `${configuredWebBase ? 'TOPCODER_URL' : 'TOPCODER_API_URL_BASE'} must be an absolute HTTP(S) URL.`, + ); + } + + if (!['http:', 'https:'].includes(publicUrl.protocol)) { + throw new Error( + `${configuredWebBase ? 'TOPCODER_URL' : 'TOPCODER_API_URL_BASE'} must be an absolute HTTP(S) URL.`, + ); + } + + if (!configuredWebBase && publicUrl.hostname.startsWith('api.')) { + publicUrl.hostname = `www.${publicUrl.hostname.slice('api.'.length)}`; + } + + publicUrl.pathname = `/opportunities/challenge/${encodeURIComponent(challengeId)}`; + publicUrl.search = ''; + publicUrl.hash = ''; + return publicUrl.toString(); + } + /** * Resolves the challenges collection URL from explicit or shared API settings. * diff --git a/src/forums/challenge-forum-watch.controller.ts b/src/forums/challenge-forum-watch.controller.ts new file mode 100644 index 0000000..5f7a0b6 --- /dev/null +++ b/src/forums/challenge-forum-watch.controller.ts @@ -0,0 +1,85 @@ +import { Controller, Delete, Get, Header, Param, Put } from '@nestjs/common'; +import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger'; +import { + AUTHENTICATED_USER_ROLE, + Roles, +} from '../auth/decorators/roles.decorator'; +import { ClientIp } from '../auth/decorators/client-ip.decorator'; +import { CurrentUser } from '../auth/decorators/user.decorator'; +import { JwtUser } from '../auth/jwt.service'; +import { + ChallengeForumWatchService, + ChallengeForumWatchState, +} from './challenge-forum-watch.service'; + +/** + * Human-member routes for challenge-wide notification preferences, including + * empty forums. All operations enforce the shared challenge visibility and bans. + */ +@ApiTags('Challenge forum watches') +@ApiBearerAuth() +@Roles(AUTHENTICATED_USER_ROLE) +@Controller('challenges/:challengeId/watch') +export class ChallengeForumWatchController { + /** + * Creates the member preference controller. + * @param service Authorized preference reader/writer. + * @throws Does not throw; Nest resolves dependencies. + */ + constructor(private readonly service: ChallengeForumWatchService) {} + + /** + * Reads the caller's effective watch state for the forum overview. + * @param challengeId Challenge route identifier. + * @param user Authenticated human token. + * @param trustedClientIp Trusted client address. + * @returns Effective watch state, including resource-based defaults. + * @throws Propagates authentication, policy, ban, and database errors. + */ + @Get() + @Header('Cache-Control', 'private, no-store') + @ApiOperation({ summary: 'Read the current member’s challenge forum watch' }) + getWatch( + @Param('challengeId') challengeId: string, + @CurrentUser() user?: JwtUser, + @ClientIp() trustedClientIp?: string, + ): Promise { + return this.service.watchState(challengeId, user, trustedClientIp); + } + + /** + * Enables notifications for current and future topics in the challenge forum. + * @param challengeId Challenge route identifier. + * @param user Authenticated human token. + * @param trustedClientIp Trusted client address. + * @returns Persisted watch state. + * @throws Propagates authentication, policy, ban, and database errors. + */ + @Put() + @ApiOperation({ summary: 'Watch the challenge forum' }) + watch( + @Param('challengeId') challengeId: string, + @CurrentUser() user?: JwtUser, + @ClientIp() trustedClientIp?: string, + ): Promise { + return this.service.watchState(challengeId, user, trustedClientIp, true); + } + + /** + * Persists an opt-out from the forum-wide stream without deleting topic watches. + * @param challengeId Challenge route identifier. + * @param user Authenticated human token. + * @param trustedClientIp Trusted client address. + * @returns Persisted unwatched state. + * @throws Propagates authentication, policy, ban, and database errors. + */ + @Delete() + @ApiOperation({ summary: 'Unwatch the challenge forum' }) + unwatch( + @Param('challengeId') challengeId: string, + @CurrentUser() user?: JwtUser, + @ClientIp() trustedClientIp?: string, + ): Promise { + return this.service.watchState(challengeId, user, trustedClientIp, false); + } +} diff --git a/src/forums/challenge-forum-watch.service.spec.ts b/src/forums/challenge-forum-watch.service.spec.ts new file mode 100644 index 0000000..c93783c --- /dev/null +++ b/src/forums/challenge-forum-watch.service.spec.ts @@ -0,0 +1,138 @@ +import { ForbiddenException, UnauthorizedException } from '@nestjs/common'; +import { ChallengeForumWatchService } from './challenge-forum-watch.service'; + +/** + * Creates the watch service with isolated storage and authorization dependencies. + * @returns Service plus mocks for resource defaults, preferences, visibility, and bans. + * @throws Does not throw. + */ +function setup() { + const db = { + challengeForumWatch: { + findUnique: jest.fn().mockResolvedValue(null), + findMany: jest.fn().mockResolvedValue([]), + upsert: jest.fn().mockResolvedValue({}), + }, + }; + const resources = { + getChallengeDefaultWatcherMemberIds: jest + .fn() + .mockResolvedValue(['member']), + }; + const policy = { + decideForRestrictionVisibility: jest + .fn() + .mockResolvedValue({ allowed: true }), + }; + const moderation = { + decideForRequestActorBan: jest.fn().mockResolvedValue({ allowed: true }), + }; + const service = new ChallengeForumWatchService( + db as any, + resources as any, + policy as any, + moderation as any, + ); + return { db, resources, policy, moderation, service }; +} +const user = { userId: 'member', isMachine: false, roles: ['Topcoder User'] }; + +describe('ChallengeForumWatchService', () => { + it('defaults resource members to watching before any topics or preferences exist', async () => { + const { service, resources, db } = setup(); + await expect(service.watchState('challenge', user)).resolves.toEqual({ + challengeId: 'challenge', + memberId: 'member', + watching: true, + }); + expect(resources.getChallengeDefaultWatcherMemberIds).toHaveBeenCalledWith( + 'challenge', + 'member', + ); + expect(db.challengeForumWatch.upsert).not.toHaveBeenCalled(); + }); + + it('does not default other challenge viewers to watching', async () => { + const { service, resources } = setup(); + resources.getChallengeDefaultWatcherMemberIds.mockResolvedValue([]); + await expect(service.watchState('challenge', user)).resolves.toHaveProperty( + 'watching', + false, + ); + }); + + it.each([false, true])( + 'persists the explicit %s choice idempotently for the caller', + async (watching) => { + const { service, db, resources } = setup(); + await service.watchState('challenge', user, '127.0.0.1', watching); + expect(db.challengeForumWatch.upsert).toHaveBeenCalledWith({ + where: { + challengeId_memberId: { + challengeId: 'challenge', + memberId: 'member', + }, + }, + create: { challengeId: 'challenge', memberId: 'member', watching }, + update: { watching }, + }); + db.challengeForumWatch.findUnique.mockResolvedValue({ watching }); + await expect( + service.watchState('challenge', user), + ).resolves.toHaveProperty('watching', watching); + expect( + resources.getChallengeDefaultWatcherMemberIds, + ).not.toHaveBeenCalled(); + }, + ); + + it('merges registrants, copilots, and explicit watches while preserving opt-outs', async () => { + const { service, db, resources } = setup(); + resources.getChallengeDefaultWatcherMemberIds.mockResolvedValue([ + 'registrant', + 'copilot', + 'opted-out', + 'copilot', + ]); + db.challengeForumWatch.findMany.mockResolvedValue([ + { memberId: 'opted-out', watching: false }, + { memberId: 'other-viewer', watching: true }, + { memberId: 'copilot', watching: true }, + ]); + await expect(service.getWatcherMemberIds('challenge')).resolves.toEqual([ + 'registrant', + 'copilot', + 'other-viewer', + ]); + expect(db.challengeForumWatch.findMany).toHaveBeenCalledWith({ + where: { challengeId: 'challenge' }, + }); + }); + + it.each([undefined, { ...user, isMachine: true }, { ...user, userId: '' }])( + 'rejects callers without a human member identity', + async (caller) => { + const { service, db } = setup(); + await expect( + service.watchState('challenge', caller, undefined, true), + ).rejects.toBeInstanceOf(UnauthorizedException); + expect(db.challengeForumWatch.upsert).not.toHaveBeenCalled(); + }, + ); + + it('rejects bans and inaccessible challenges before reading or changing preferences', async () => { + const { service, db, policy, moderation } = setup(); + moderation.decideForRequestActorBan.mockResolvedValue({ allowed: false }); + await expect( + service.watchState('challenge', user, '127.0.0.1', true), + ).rejects.toBeInstanceOf(ForbiddenException); + expect(policy.decideForRestrictionVisibility).not.toHaveBeenCalled(); + moderation.decideForRequestActorBan.mockResolvedValue({ allowed: true }); + policy.decideForRestrictionVisibility.mockResolvedValue({ allowed: false }); + await expect(service.watchState('challenge', user)).rejects.toBeInstanceOf( + ForbiddenException, + ); + expect(db.challengeForumWatch.findUnique).not.toHaveBeenCalled(); + expect(db.challengeForumWatch.upsert).not.toHaveBeenCalled(); + }); +}); diff --git a/src/forums/challenge-forum-watch.service.ts b/src/forums/challenge-forum-watch.service.ts new file mode 100644 index 0000000..7438a96 --- /dev/null +++ b/src/forums/challenge-forum-watch.service.ts @@ -0,0 +1,133 @@ +import { + ForbiddenException, + Injectable, + UnauthorizedException, +} from '@nestjs/common'; +import { JwtUser } from '../auth/jwt.service'; +import { DbService } from '../db/db.service'; +import { buildForumsPrincipal } from './forums-access.types'; +import { ForumsAccessPolicyService } from './forums-access-policy.service'; +import { ForumsModerationService } from './forums-moderation.service'; +import { ResourceAccessService } from './resource-access.service'; + +/** Current member's effective challenge-wide watch preference. */ +export interface ChallengeForumWatchState { + challengeId: string; + memberId: string; + watching: boolean; +} + +/** + * Resolves challenge-wide watches from resource assignments and persisted choices. + * Registrants and copilots default to watching; explicit opt-outs survive later + * reads and resource changes. Topic-specific watches remain independent. + */ +@Injectable() +export class ChallengeForumWatchService { + /** + * Wires persistence and the existing resource, visibility, and ban policies. + * @param db Forums preference storage. + * @param resourceAccessService Challenge assignment lookup. + * @param accessPolicyService Shared challenge visibility policy. + * @param moderationService Shared member and trusted-IP ban policy. + * @throws Does not throw; Nest resolves dependencies. + */ + constructor( + private readonly db: DbService, + private readonly resourceAccessService: ResourceAccessService, + private readonly accessPolicyService: ForumsAccessPolicyService, + private readonly moderationService: ForumsModerationService, + ) {} + + /** + * Reads or updates the authenticated member's forum-wide watch preference. + * Works before the first topic exists and never accepts a target member id. + * @param challengeId Challenge whose forum is watched. + * @param user Validated human token. + * @param trustedClientIp Trusted request IP for ban enforcement. + * @param watching Explicit choice to persist, or undefined for a read. + * @returns Effective watch state for this member and challenge. + * @throws UnauthorizedException for missing or machine identities; ForbiddenException + * for bans or denied challenge access; propagates database lookup/write errors. + */ + async watchState( + challengeId: string, + user: JwtUser | undefined, + trustedClientIp?: string, + watching?: boolean, + ): Promise { + const principal = buildForumsPrincipal(user); + if (!principal?.memberId || principal.isMachine) { + throw new UnauthorizedException('An authenticated member is required.'); + } + const ban = await this.moderationService.decideForRequestActorBan( + principal, + trustedClientIp, + ); + if (!ban.allowed) throw new ForbiddenException(ban.reason); + const visibility = + await this.accessPolicyService.decideForRestrictionVisibility(principal, { + challengeId, + roleName: null, + hasRestrictionConflict: false, + }); + if (!visibility.allowed) throw new ForbiddenException(visibility.reason); + + const memberId = principal.memberId; + const where = { challengeId_memberId: { challengeId, memberId } }; + if (watching !== undefined) { + await this.db.challengeForumWatch.upsert({ + where, + create: { challengeId, memberId, watching }, + update: { watching }, + }); + } else { + watching = await this.isWatching(challengeId, memberId); + } + return { challengeId, memberId, watching }; + } + + /** + * Resolves a member's forum preference for already-authorized topic reads. + * @param challengeId Effective challenge scope of the visible topics. + * @param memberId Authenticated member whose inherited preference is needed. + * @returns Saved forum preference, or the current resource-based default. + * @throws Propagates preference database and resource lookup errors. + */ + async isWatching(challengeId: string, memberId: string): Promise { + const preference = await this.db.challengeForumWatch.findUnique({ + where: { challengeId_memberId: { challengeId, memberId } }, + }); + return ( + preference?.watching ?? + ( + await this.resourceAccessService.getChallengeDefaultWatcherMemberIds( + challengeId, + memberId, + ) + ).includes(memberId) + ); + } + + /** + * Resolves forum-wide notification candidates, applying explicit choices to defaults. + * The notification publisher still enforces each topic's visibility and bans. + * @param challengeId Effective challenge scope of the new topic or post. + * @returns Deduplicated member ids whose challenge-wide watch is enabled. + * @throws Propagates resource and preference database errors. + */ + async getWatcherMemberIds(challengeId: string): Promise { + const [defaults, preferences] = await Promise.all([ + this.resourceAccessService.getChallengeDefaultWatcherMemberIds( + challengeId, + ), + this.db.challengeForumWatch.findMany({ where: { challengeId } }), + ]); + const watchers = new Set(defaults); + for (const preference of preferences) { + if (preference.watching) watchers.add(preference.memberId); + else watchers.delete(preference.memberId); + } + return [...watchers]; + } +} diff --git a/src/forums/forums-command.service.spec.ts b/src/forums/forums-command.service.spec.ts index 22c21af..57fa007 100644 --- a/src/forums/forums-command.service.spec.ts +++ b/src/forums/forums-command.service.spec.ts @@ -94,10 +94,12 @@ function createCommandService(order: string[] = []) { updatedAt: createdAt, }), deleteMany: jest.fn().mockResolvedValue({ count: 1 }), - count: jest.fn().mockImplementation( - (args: { where: { reaction: PostReactionType } }) => - args.where.reaction === PostReactionType.THUMBS_UP ? 2 : 1, - ), + count: jest + .fn() + .mockImplementation( + (args: { where: { reaction: PostReactionType } }) => + args.where.reaction === PostReactionType.THUMBS_UP ? 2 : 1, + ), }, topicClosure: { findFirst: jest.fn().mockResolvedValue(null), @@ -370,6 +372,33 @@ describe('ForumsCommandService notification boundary', () => { expect(notificationService.publishPostNotification).not.toHaveBeenCalled(); }); + it('publishes a challenge root starter notification only after commit', async () => { + const order: string[] = []; + const { notificationService, service, tx } = createCommandService(order); + tx.topic.create.mockResolvedValue( + makeTopic({ challengeId: 'challenge-1' }), + ); + await service.createTopic( + { + title: 'New challenge topic', + content: 'Starter content', + challengeId: 'challenge-1', + }, + user, + ); + expect(order).toEqual(['post.create', 'commit', 'notify']); + expect(notificationService.publishPostNotification).toHaveBeenCalledWith( + expect.objectContaining({ + operationName: 'createTopic', + restrictions: { + challengeId: 'challenge-1', + roleName: null, + hasRestrictionConflict: false, + }, + }), + ); + }); + it('does not publish notifications for top-level createTopic commands', async () => { const { notificationService, service } = createCommandService(); @@ -652,7 +681,9 @@ describe('ForumsCommandService post reactions', () => { update: { reaction: PostReactionType.THUMBS_DOWN }, }); expect(tx.postReaction.count).toHaveBeenCalledTimes(2); - expect(moderationService.decideForLockedTopicMutation).not.toHaveBeenCalled(); + expect( + moderationService.decideForLockedTopicMutation, + ).not.toHaveBeenCalled(); }); it('removes a member reaction idempotently and returns a null viewer state', async () => { @@ -660,9 +691,7 @@ describe('ForumsCommandService post reactions', () => { tx.postReaction.deleteMany.mockResolvedValue({ count: 0 }); tx.postReaction.count.mockResolvedValue(0); - await expect( - service.removePostReaction('post-1', user), - ).resolves.toEqual({ + await expect(service.removePostReaction('post-1', user)).resolves.toEqual({ postId: 'post-1', viewerReaction: null, thumbsUpCount: 0, @@ -701,6 +730,27 @@ describe('ForumsCommandService post reactions', () => { }); describe('ForumsCommandService member-targeted state commands', () => { + it('persists a challenge-topic opt-out instead of deleting its preference', async () => { + const { service, topicContextService, tx } = createCommandService(); + topicContextService.loadTopicContext.mockResolvedValue({ + ...(await topicContextService.loadTopicContext()), + effectiveChallengeId: 'challenge-1', + }); + await expect( + service.removeTopicWatch('topic-1', undefined, user), + ).resolves.toEqual({ + topicId: 'topic-1', + memberId: '1', + watching: false, + }); + expect(tx.topicWatch.upsert).toHaveBeenCalledWith({ + where: { topicId_memberId: { topicId: 'topic-1', memberId: '1' } }, + create: { topicId: 'topic-1', memberId: '1', watching: false }, + update: { watching: false }, + }); + expect(tx.topicWatch.deleteMany).not.toHaveBeenCalled(); + }); + const commandCases = [ { name: 'addTopicWatch', @@ -717,8 +767,9 @@ describe('ForumsCommandService member-targeted state commands', () => { create: { topicId: 'topic-1', memberId: '2', + watching: true, }, - update: {}, + update: { watching: true }, }); }, }, diff --git a/src/forums/forums-command.service.ts b/src/forums/forums-command.service.ts index fea1b48..73e47a6 100644 --- a/src/forums/forums-command.service.ts +++ b/src/forums/forums-command.service.ts @@ -155,7 +155,9 @@ export class ForumsCommandService { ) {} /** - * Creates a topic and starter post in one transaction. + * Creates a topic and starter post in one transaction. Human authors receive + * read state; only general topics seed a separate author watch. Challenge + * topics inherit the forum preference until explicitly watched or unwatched. * * Child topics are allowed only when the resolved effective restrictions * remain non-challenge. Challenge-scoped child candidates are rejected by the @@ -164,7 +166,7 @@ export class ForumsCommandService { * @param dto Topic metadata and starter-post content. * @param user Authenticated token payload for the acting member. * @param trustedClientIp Optional trusted client IP resolved at the HTTP boundary. - * @returns The created topic command response and starter post after any child-topic notification attempt settles. + * @returns The created topic command response and starter post after any child-topic or challenge-topic notification attempt settles. * @throws UnauthorizedException when no authenticated command caller is present. * @throws BadRequestException when topic fields or parent linkage are invalid. * @throws ForbiddenException when shared policy, moderation bans, or parent lock state deny topic creation. @@ -270,19 +272,21 @@ export class ForumsCommandService { }); if (author.seedMemberState) { - await tx.topicWatch.upsert({ - where: { - topicId_memberId: { + if (!restrictions.effectiveChallengeId) { + await tx.topicWatch.upsert({ + where: { + topicId_memberId: { + topicId: topic.id, + memberId: author.memberId, + }, + }, + create: { topicId: topic.id, memberId: author.memberId, }, - }, - create: { - topicId: topic.id, - memberId: author.memberId, - }, - update: {}, - }); + update: {}, + }); + } await tx.topicReadState.upsert({ where: { @@ -306,7 +310,7 @@ export class ForumsCommandService { }, ); - if (parentTopicId) { + if (parentTopicId || restrictions.effectiveChallengeId) { await this.publishPostNotificationBestEffort( 'createTopic', result.topic, @@ -767,7 +771,7 @@ export class ForumsCommandService { } /** - * Adds an explicit watch for a member on a topic. + * Enables an explicit topic watch, replacing any saved topic opt-out. * * @param topicId Topic id to watch. * @param dto Optional target member body; required for scoped M2M callers. @@ -823,14 +827,15 @@ export class ForumsCommandService { create: { topicId, memberId: target.memberId, + watching: true, }, - update: {}, + update: { watching: true }, }); }); } /** - * Removes an explicit watch for a member from a topic. + * Persists a challenge-topic opt-out; removes general-topic watches as before. * * @param topicId Topic id to unwatch. * @param dto Optional target member body; required for scoped M2M callers. @@ -876,12 +881,17 @@ export class ForumsCommandService { return this.db.$transaction(async (tx) => { await this.ensureActiveTopic(tx, topicId); - await tx.topicWatch.deleteMany({ - where: { - topicId, - memberId: target.memberId, - }, - }); + if (context.effectiveChallengeId) { + await tx.topicWatch.upsert({ + where: { topicId_memberId: { topicId, memberId: target.memberId } }, + create: { topicId, memberId: target.memberId, watching: false }, + update: { watching: false }, + }); + } else { + await tx.topicWatch.deleteMany({ + where: { topicId, memberId: target.memberId }, + }); + } return { topicId, diff --git a/src/forums/forums-notification-read.integration.spec.ts b/src/forums/forums-notification-read.integration.spec.ts index 0c956ad..4e8e280 100644 --- a/src/forums/forums-notification-read.integration.spec.ts +++ b/src/forums/forums-notification-read.integration.spec.ts @@ -21,6 +21,7 @@ interface TopicClosureSeed { } interface TopicWatchSeed { + watching?: boolean; topicId: string; memberId: string; } @@ -192,6 +193,27 @@ function createSeededForumsDb(seed: SeededForumsData) { return [{ active: Boolean(topicId && isTopicActive(seed, topicId)) }]; } + if ( + queryText.includes('t."parentTopicId" IS NULL') && + queryText.includes('t."challengeId" =') + ) { + const challengeId = values + .map(queryValueToString) + .find((value) => + seed.topics.some((topic) => topic.challengeId === value), + ); + return seed.topics + .filter( + (topic) => + topic.challengeId === challengeId && + !topic.parentTopicId && + isTopicActive(seed, topic.id), + ) + .map((topic) => + buildTopicSummaryRow(seed, topic, queryValueToString(values[0])), + ); + } + if (queryText.includes('FROM "Topic" t') && topicId) { const topic = topicById().get(topicId); @@ -268,8 +290,8 @@ function createSeededForumsDb(seed: SeededForumsData) { authorMemberId: args.data.authorMemberId, authorHandle: args.data.authorHandle, content: args.data.content, - createdAt: new Date(`2026-06-04T00:1${nextPostId}:00.000Z`), - updatedAt: new Date(`2026-06-04T00:1${nextPostId}:00.000Z`), + createdAt: new Date(createdAt.getTime() + (10 + nextPostId) * 60_000), + updatedAt: new Date(createdAt.getTime() + (10 + nextPostId) * 60_000), }); nextPostId += 1; @@ -403,10 +425,11 @@ function createSeededForumsDb(seed: SeededForumsData) { args.where.topicId.in.includes(watch.topicId) && watch.memberId !== args.where.memberId.not, ) - .map((watch) => ({ memberId: watch.memberId })), + .map((watch) => ({ ...watch, watching: watch.watching ?? true })), upsert: (args: { where: { topicId_memberId: TopicWatchSeed }; create: TopicWatchSeed; + update: Partial; }) => { const existing = seed.topicWatches.find( (watch) => @@ -415,6 +438,7 @@ function createSeededForumsDb(seed: SeededForumsData) { ); if (existing) { + Object.assign(existing, args.update); return existing; } @@ -606,9 +630,15 @@ function buildTopicSummaryRow( viewsCount: seed.topicReadStates.filter( (state) => state.topicId === topic.id, ).length, - watching: seed.topicWatches.some( - (watch) => watch.topicId === topic.id && watch.memberId === memberId, - ), + watching: + seed.topicWatches.find( + (watch) => watch.topicId === topic.id && watch.memberId === memberId, + )?.watching ?? + (seed.topicWatches.some( + (watch) => watch.topicId === topic.id && watch.memberId === memberId, + ) + ? true + : null), starterPostExcerpt: posts[0]?.content?.slice(0, 280) ?? null, participantsCount: participants.length, participants: participants.slice(0, 5), @@ -688,10 +718,17 @@ describe('forums notification/read integration', () => { ['6', 'six@example.com'], ['7', 'seven@example.com'], ]); + const defaultWatchers = new Set(); + const forumPreferences = new Map< + string, + { challengeId: string; memberId: string; watching: boolean } + >(); const publishedEvents: { topic: string; payload: any }[] = []; beforeEach(async () => { publishedEvents.length = 0; + defaultWatchers.clear(); + forumPreferences.clear(); seedData = { topics: [ @@ -734,7 +771,27 @@ describe('forums notification/read integration', () => { memberBans: [], ipBans: [], }; - const db = createSeededForumsDb(seedData); + const db = { + ...createSeededForumsDb(seedData), + challengeForumWatch: { + findMany: jest.fn(({ where }: any) => + [...forumPreferences.values()].filter( + (preference) => preference.challengeId === where.challengeId, + ), + ), + findUnique: jest.fn( + ({ where: { challengeId_memberId: key } }: any) => + forumPreferences.get(`${key.challengeId}:${key.memberId}`) ?? null, + ), + upsert: jest.fn(({ create }: any) => { + forumPreferences.set( + `${create.challengeId}:${create.memberId}`, + create, + ); + return create; + }), + }, + }; const moduleRef = await Test.createTestingModule({ imports: [ ConfigModule.forRoot({ @@ -768,6 +825,14 @@ describe('forums notification/read integration', () => { }) .overrideProvider(ResourceAccessService) .useValue({ + getChallengeDefaultWatcherMemberIds: jest.fn( + (challengeId: string, memberId?: string) => + challengeId === 'challenge-1' + ? [...defaultWatchers].filter( + (id) => !memberId || memberId === id, + ) + : [], + ), getChallengeCopilotMemberIds: jest.fn( (challengeId: string, memberIds: readonly string[]) => new Set( @@ -844,6 +909,180 @@ describe('forums notification/read integration', () => { await app?.close(); }); + it('defaults registrants and copilots to watching new topics and persists forum opt-out/re-watch', async () => { + // No topic in this challenge exists yet, and neither recipient has a topic watch. + defaultWatchers.add('2'); + defaultWatchers.add('6'); + seedData.topicWatches = []; + for (const memberId of ['2', '6']) { + const response = await request(app.getHttpServer()) + .get('/challenges/challenge-1/watch') + .set('x-member-id', memberId) + .expect(200); + expect(response.body).toEqual({ + challengeId: 'challenge-1', + memberId, + watching: true, + }); + } + await request(app.getHttpServer()) + .delete('/challenges/challenge-1/watch') + .set('x-member-id', '2') + .expect(200); + const preference = await request(app.getHttpServer()) + .get('/challenges/challenge-1/watch') + .set('x-member-id', '2') + .expect(200); + expect(preference.body.watching).toBe(false); + await request(app.getHttpServer()) + .post('/topics') + .set('x-member-id', '1') + .send({ + challengeId: 'challenge-1', + title: 'First challenge topic', + content: 'First post', + }) + .expect(201); + expect(publishedEvents).toHaveLength(1); + expect(publishedEvents[0].payload.recipients).toEqual(['six@example.com']); + + await request(app.getHttpServer()) + .put('/challenges/challenge-1/watch') + .set('x-member-id', '2') + .send({ memberId: '3' }) + .expect(200); + expect(forumPreferences.has('challenge-1:3')).toBe(false); + await request(app.getHttpServer()) + .post('/topics') + .set('x-member-id', '1') + .send({ + challengeId: 'challenge-1', + title: 'Another challenge topic', + content: 'Another post', + }) + .expect(201); + expect(publishedEvents).toHaveLength(2); + expect(publishedEvents[1].payload.recipients).toEqual([ + 'two@example.com', + 'six@example.com', + ]); + }); + + it.each(['2', '6'])( + 'preserves topic overrides across forum toggles for member %s', + async (memberId) => { + defaultWatchers.add(memberId); + const topicIds: string[] = []; + for (const title of [ + 'Inherited discussion', + 'Explicit watch', + 'Explicit opt-out', + 'Inherited announcement', + ]) { + const created = await request(app.getHttpServer()) + .post('/topics') + .set('x-member-id', '6') + .send({ + challengeId: 'challenge-1', + title, + content: title, + isAnnouncement: title.includes('announcement'), + }) + .expect(201); + topicIds.push(created.body.topic.id); + } + expect( + seedData.topicWatches.filter((watch) => + topicIds.includes(watch.topicId), + ), + ).toEqual([]); + await request(app.getHttpServer()) + .put(`/topics/${topicIds[1]}/watch`) + .set('x-member-id', memberId) + .send({}) + .expect(200); + await request(app.getHttpServer()) + .delete(`/topics/${topicIds[2]}/watch`) + .set('x-member-id', memberId) + .expect(200); + + // List, detail and email recipients must agree throughout the full QA sequence. + for (const forumWatching of [true, false, true]) { + if (forumWatching) { + await request(app.getHttpServer()) + .put('/challenges/challenge-1/watch') + .set('x-member-id', memberId) + .send({}) + .expect(200); + } else { + await request(app.getHttpServer()) + .delete('/challenges/challenge-1/watch') + .set('x-member-id', memberId) + .expect(200); + const created = await request(app.getHttpServer()) + .post('/topics') + .set('x-member-id', memberId) + .send({ + challengeId: 'challenge-1', + title: 'Created while unwatched', + content: 'New topic', + }) + .expect(201); + topicIds.push(created.body.topic.id); + } + const list = await request(app.getHttpServer()) + .get('/topics/challenges/challenge-1') + .set('x-member-id', memberId) + .expect(200); + for (const [index, topicId] of topicIds.entries()) { + const expectedWatching = + index === 1 || (index !== 2 && forumWatching); + expect( + list.body.data.find((topic: { id: string }) => topic.id === topicId) + .watching, + ).toBe(expectedWatching); + const detail = await request(app.getHttpServer()) + .get(`/topics/${topicId}`) + .set('x-member-id', memberId) + .expect(200); + expect(detail.body.topic.watching).toBe(expectedWatching); + publishedEvents.length = 0; + await request(app.getHttpServer()) + .post(`/topics/${topicId}/posts`) + .set('x-member-id', '1') + .send({ content: 'Notification check' }) + .expect(201); + expect( + publishedEvents.flatMap((event) => event.payload.recipients), + ).toEqual(expectedWatching ? [emailsByMemberId.get(memberId)] : []); + } + } + await request(app.getHttpServer()) + .put(`/topics/${topicIds[2]}/watch`) + .set('x-member-id', memberId) + .send({}) + .expect(200); + const rewatched = await request(app.getHttpServer()) + .get(`/topics/${topicIds[2]}`) + .set('x-member-id', memberId) + .expect(200); + expect(rewatched.body.topic.watching).toBe(true); + }, + ); + + it('rejects inaccessible challenge watch reads and mutations', async () => { + await request(app.getHttpServer()) + .get('/challenges/challenge-1/watch') + .set('x-member-id', '4') + .expect(403); + await request(app.getHttpServer()) + .put('/challenges/challenge-1/watch') + .set('x-member-id', '4') + .send({}) + .expect(403); + expect(forumPreferences.size).toBe(0); + }); + it('notifies a member who watches through the API while excluding the posting author', async () => { seedData.topicWatches = [{ topicId: 'parent-1', memberId: '1' }]; @@ -861,7 +1100,7 @@ describe('forums notification/read integration', () => { expect(seedData.topicWatches).toEqual([ { topicId: 'parent-1', memberId: '1' }, - { topicId: 'parent-1', memberId: '2' }, + { topicId: 'parent-1', memberId: '2', watching: true }, ]); expect(publishedEvents).toHaveLength(1); expect(publishedEvents[0]).toEqual({ diff --git a/src/forums/forums-read-query.service.ts b/src/forums/forums-read-query.service.ts index a14a1a4..a1b48a2 100644 --- a/src/forums/forums-read-query.service.ts +++ b/src/forums/forums-read-query.service.ts @@ -6,7 +6,8 @@ import { DbService } from '../db/db.service'; * Database row returned by forum topic summary read queries. * * Lock fields are direct Topic projections. `lockedBy` and `lockedAt` may be - * null even when `locked` is true for imported legacy topics. + * null even when `locked` is true for imported legacy topics. `watching` is the + * explicit topic preference, or null when it inherits the challenge preference. */ export interface ForumsTopicSummaryRow { id: string; @@ -24,7 +25,7 @@ export interface ForumsTopicSummaryRow { updatedAt: Date; postsCount: number; viewsCount: number; - watching: boolean; + watching: boolean | null; starterPostExcerpt: string | null; participantsCount: number; participants: ForumsTopicParticipantRow[]; @@ -363,7 +364,7 @@ export class ForumsReadQueryService { t."updatedAt", post_stats."postsCount", view_stats."viewsCount", - watch_state."topicId" IS NOT NULL AS watching, + watch_state.watching AS watching, starter."starterPostExcerpt", participant_stats."participantsCount", participant_snapshots.participants, diff --git a/src/forums/forums-read.service.ts b/src/forums/forums-read.service.ts index 8cee791..cd00a41 100644 --- a/src/forums/forums-read.service.ts +++ b/src/forums/forums-read.service.ts @@ -6,6 +6,7 @@ import { UnauthorizedException, } from '@nestjs/common'; import { JwtUser } from '../auth/jwt.service'; +import { ChallengeForumWatchService } from './challenge-forum-watch.service'; import { PostReactionType } from '../../prisma/generated/client'; import { ForumsPostTreeNodeDto, @@ -79,6 +80,7 @@ export class ForumsReadService { * @param readQueryService Side-effect-free raw-query reader. * @param moderationService Shared runtime ban and lock gate. * @param resourceAccessService Adapter for challenge copilot assignments. + * @param challengeWatchService Effective forum preferences for inherited topic watches. * @throws Does not throw directly; dependencies are resolved by Nest. */ constructor( @@ -87,6 +89,7 @@ export class ForumsReadService { private readonly readQueryService: ForumsReadQueryService, private readonly moderationService: ForumsModerationService, private readonly resourceAccessService: ResourceAccessService, + private readonly challengeWatchService: ChallengeForumWatchService, ) {} /** @@ -139,6 +142,11 @@ export class ForumsReadService { this.resolveRootRowRestrictions(row), ); + await this.applyChallengeWatch( + visibleRows, + normalizedChallengeId, + principal, + ); return this.paginateRows(visibleRows, query); } @@ -231,6 +239,11 @@ export class ForumsReadService { (row) => this.resolveChildRowRestrictions(parentContext, row), ); + await this.applyChallengeWatch( + visibleRows, + parentContext.effectiveChallengeId, + principal, + ); return visibleRows.map((row) => this.mapTopicSummary(row)); } @@ -282,13 +295,24 @@ export class ForumsReadService { throw new NotFoundException('Topic not found.'); } + await this.applyChallengeWatch( + [detailSnapshot.summaryRow], + context.effectiveChallengeId, + principal, + ); const copilotMemberIds = await this.resolvePostAuthorCopilots( context.effectiveChallengeId, detailSnapshot.postRows, ); return { - permissions: { createPost: topicDecisions.canCreatePost.allowed, createTopic: topicDecisions.canCreateChildTopic.allowed, watch: topicDecisions.canAddWatch.allowed, update: topicDecisions.canUpdateTopic.allowed, delete: topicDecisions.canDeleteTopic.allowed }, + 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, @@ -298,6 +322,29 @@ export class ForumsReadService { }; } + /** + * Applies forum inheritance only to visible topics without an explicit choice. + * @param rows Authorized summary rows updated in place before DTO mapping. + * @param challengeId Effective challenge scope, or null for general forums. + * @param principal Current reader; guests and machines do not inherit watches. + * @returns Nothing after resolving at most one forum preference for the batch. + * @throws Propagates preference database and resource lookup errors. + */ + private async applyChallengeWatch( + rows: ForumsTopicSummaryRow[], + challengeId: string | null, + principal: ForumsPrincipal, + ): Promise { + if (!challengeId || !principal.memberId || principal.isMachine) return; + const inheritedRows = rows.filter((row) => row.watching === null); + if (!inheritedRows.length) return; + const watching = await this.challengeWatchService.isWatching( + challengeId, + principal.memberId, + ); + for (const row of inheritedRows) row.watching = watching; + } + /** * Builds a validated reader or a non-elevated anonymous principal. * @@ -306,7 +353,15 @@ export class ForumsReadService { * @throws Does not throw. */ private readPrincipal(user: JwtUser | undefined): ForumsPrincipal { - return buildForumsPrincipal(user) ?? { memberId: null, roles: [], scopes: [], isAdmin: false, isMachine: false }; + return ( + buildForumsPrincipal(user) ?? { + memberId: null, + roles: [], + scopes: [], + isAdmin: false, + isMachine: false, + } + ); } /** Requires a validated identity for challenge-specific listing. diff --git a/src/forums/forums-watch-notification.service.spec.ts b/src/forums/forums-watch-notification.service.spec.ts index a691ca5..781e22a 100644 --- a/src/forums/forums-watch-notification.service.spec.ts +++ b/src/forums/forums-watch-notification.service.spec.ts @@ -74,7 +74,9 @@ function createService(templateId: string | null = 'template-id') { ]), }, topicWatch: { - findMany: jest.fn().mockResolvedValue([{ memberId: '2' }]), + findMany: jest + .fn() + .mockResolvedValue([{ memberId: '2', watching: true }]), }, }; const memberDirectoryService = { @@ -102,6 +104,11 @@ function createService(templateId: string | null = 'template-id') { }; const challengeApiService = { getChallengeTitle: jest.fn().mockResolvedValue('Challenge title'), + getChallengeUrl: jest + .fn() + .mockReturnValue( + 'https://www.topcoder-dev.com/opportunities/challenge/challenge-1', + ), }; const configService = { get: jest.fn((key: string) => @@ -110,6 +117,9 @@ function createService(templateId: string | null = 'template-id') { : undefined, ), }; + const challengeWatchService = { + getWatcherMemberIds: jest.fn().mockResolvedValue([]), + }; const service = new ForumsWatchNotificationService( db as any, memberDirectoryService as any, @@ -119,9 +129,11 @@ function createService(templateId: string | null = 'template-id') { eventBusService as any, configService as any, challengeApiService as any, + challengeWatchService as any, ); return { + challengeWatchService, accessPolicyService, challengeApiService, configService, @@ -139,11 +151,126 @@ describe('ForumsWatchNotificationService', () => { jest.restoreAllMocks(); }); + it('notifies challenge-wide watchers of a new root topic, excluding its author and duplicates', async () => { + const { + challengeWatchService, + db, + memberDirectoryService, + eventBusService, + service, + } = createService(); + db.topicWatch.findMany.mockResolvedValue([ + { memberId: '2', watching: true }, + ]); + challengeWatchService.getWatcherMemberIds.mockResolvedValue([ + '1', + '2', + '3', + ]); + memberDirectoryService.getMembersByIds.mockResolvedValue([ + { memberId: '2', email: 'two@example.com', handle: 'two' }, + { memberId: '3', email: 'three@example.com', handle: 'three' }, + ]); + await service.publishPostNotification({ + topic: makeTopic({ challengeId: 'challenge-1' }), + post: makePost(), + restrictions: { + challengeId: 'challenge-1', + roleName: null, + hasRestrictionConflict: false, + }, + operationName: 'createTopic', + }); + expect(challengeWatchService.getWatcherMemberIds).toHaveBeenCalledWith( + 'challenge-1', + ); + expect(memberDirectoryService.getMembersByIds).toHaveBeenCalledWith([ + '2', + '3', + ]); + expect(eventBusService.postEvent).toHaveBeenCalledWith( + 'external.action.email', + expect.objectContaining({ + recipients: ['two@example.com', 'three@example.com'], + }), + ); + }); + + it('applies visibility and bans to challenge-wide watchers without topic watches', async () => { + const { + challengeWatchService, + db, + memberDirectoryService, + accessPolicyService, + moderationService, + eventBusService, + service, + } = createService(); + db.topicWatch.findMany.mockResolvedValue([]); + challengeWatchService.getWatcherMemberIds.mockResolvedValue([ + '2', + '3', + '4', + ]); + memberDirectoryService.getMembersByIds.mockResolvedValue( + ['2', '3', '4'].map((memberId) => ({ + memberId, + email: `${memberId}@example.com`, + handle: memberId, + })), + ); + moderationService.decideForTargetMemberBan.mockImplementation((memberId) => + Promise.resolve({ allowed: memberId !== '3' }), + ); + accessPolicyService.decideForRestrictionVisibility.mockImplementation( + (principal) => Promise.resolve({ allowed: principal.memberId !== '4' }), + ); + await service.publishPostNotification({ + topic: makeTopic({ challengeId: 'challenge-1', roleName: 'reviewer' }), + post: makePost(), + restrictions: { + challengeId: 'challenge-1', + roleName: 'reviewer', + hasRestrictionConflict: false, + }, + operationName: 'createPost', + }); + expect(eventBusService.postEvent).toHaveBeenCalledWith( + 'external.action.email', + expect.objectContaining({ + recipients: ['2@example.com'], + }), + ); + }); + + it('excludes an explicit topic opt-out even when forum and ancestor watches are enabled', async () => { + const { service, db, challengeWatchService, eventBusService } = + createService(); + challengeWatchService.getWatcherMemberIds.mockResolvedValue(['2']); + db.topicWatch.findMany.mockResolvedValue([ + { topicId: 'root-1', memberId: '2', watching: true }, + { topicId: 'topic-1', memberId: '2', watching: false }, + ]); + const result = await service.publishPostNotification({ + topic: makeTopic({ challengeId: 'challenge-1' }), + post: makePost(), + restrictions: { + challengeId: 'challenge-1', + roleName: null, + hasRestrictionConflict: false, + }, + operationName: 'createPost', + }); + expect(result.published).toBe(false); + expect(eventBusService.postEvent).not.toHaveBeenCalled(); + }); + it('dedupes topic and ancestor watches into one recipient email event', async () => { - const { challengeApiService, db, eventBusService, service } = createService(); + const { challengeApiService, db, eventBusService, service } = + createService(); db.topicWatch.findMany.mockResolvedValue([ - { memberId: '2' }, - { memberId: '2' }, + { memberId: '2', watching: true }, + { memberId: '2', watching: true }, ]); const result = await service.publishPostNotification({ @@ -163,10 +290,11 @@ describe('ForumsWatchNotificationService', () => { topicId: { in: ['topic-1', 'root-1'] }, memberId: { not: '1' }, }, - select: { memberId: true }, + select: { topicId: true, memberId: true, watching: true }, }); expect(eventBusService.postEvent).toHaveBeenCalledTimes(1); expect(challengeApiService.getChallengeTitle).not.toHaveBeenCalled(); + expect(challengeApiService.getChallengeUrl).not.toHaveBeenCalled(); expect(eventBusService.postEvent.mock.calls[0][1].data).not.toHaveProperty( 'challengeTitle', ); @@ -182,7 +310,9 @@ describe('ForumsWatchNotificationService', () => { it('excludes the persisted author even when the author watches the topic', async () => { const { db, eventBusService, service } = createService(); - db.topicWatch.findMany.mockResolvedValue([{ memberId: '2' }]); + db.topicWatch.findMany.mockResolvedValue([ + { memberId: '2', watching: true }, + ]); await service.publishPostNotification({ topic: makeTopic(), @@ -206,8 +336,12 @@ describe('ForumsWatchNotificationService', () => { }); it('filters watched members who cannot view a restricted child topic', async () => { - const { accessPolicyService, challengeApiService, eventBusService, service } = - createService(); + const { + accessPolicyService, + challengeApiService, + eventBusService, + service, + } = createService(); accessPolicyService.decideForRestrictionVisibility.mockResolvedValue({ allowed: false, reason: 'Required forum role is missing.', @@ -250,8 +384,8 @@ describe('ForumsWatchNotificationService', () => { service, } = createService(); db.topicWatch.findMany.mockResolvedValue([ - { memberId: '2' }, - { memberId: '3' }, + { memberId: '2', watching: true }, + { memberId: '3', watching: true }, ]); memberDirectoryService.getMembersByIds.mockResolvedValue([ { memberId: '2', email: 'two@example.com', handle: 'two' }, @@ -302,9 +436,9 @@ describe('ForumsWatchNotificationService', () => { service, } = createService(); db.topicWatch.findMany.mockResolvedValue([ - { memberId: '2' }, - { memberId: '3' }, - { memberId: '4' }, + { memberId: '2', watching: true }, + { memberId: '3', watching: true }, + { memberId: '4', watching: true }, ]); memberDirectoryService.getMembersByIds.mockResolvedValue([ { memberId: '2', email: 'two@example.com', handle: 'two' }, @@ -358,9 +492,9 @@ describe('ForumsWatchNotificationService', () => { }); db.topicWatch.findMany.mockResolvedValue([ - { memberId: '2' }, - { memberId: '3' }, - { memberId: '4' }, + { memberId: '2', watching: true }, + { memberId: '3', watching: true }, + { memberId: '4', watching: true }, ]); memberDirectoryService.getMembersByIds.mockResolvedValue([ { memberId: '2', email: 'two@example.com', handle: 'two' }, @@ -439,7 +573,9 @@ describe('ForumsWatchNotificationService', () => { }); expect(missingTemplate.db.topicClosure.findMany).not.toHaveBeenCalled(); - expect(missingTemplate.challengeApiService.getChallengeTitle).not.toHaveBeenCalled(); + expect( + missingTemplate.challengeApiService.getChallengeTitle, + ).not.toHaveBeenCalled(); expect(missingTemplate.eventBusService.postEvent).not.toHaveBeenCalled(); expect(warnSpy).toHaveBeenCalledWith( expect.stringContaining( @@ -491,8 +627,8 @@ describe('ForumsWatchNotificationService', () => { service, } = createService(); db.topicWatch.findMany.mockResolvedValue([ - { memberId: '2' }, - { memberId: '3' }, + { memberId: '2', watching: true }, + { memberId: '3', watching: true }, ]); memberDirectoryService.getMembersByIds.mockResolvedValue([ { memberId: '2', email: 'two@example.com', handle: 'two' }, @@ -514,12 +650,21 @@ describe('ForumsWatchNotificationService', () => { expect(challengeApiService.getChallengeTitle).toHaveBeenCalledWith( 'challenge-1', ); + expect(challengeApiService.getChallengeUrl).toHaveBeenCalledTimes(1); + expect(challengeApiService.getChallengeUrl).toHaveBeenCalledWith( + 'challenge-1', + ); expect(eventBusService.postEvent).toHaveBeenCalledWith( 'external.action.email', { data: { challengeId: 'challenge-1', + challengeName: 'Challenge title', challengeTitle: 'Challenge title', + challengeURL: + 'https://www.topcoder-dev.com/opportunities/challenge/challenge-1', + challengeUrl: + 'https://www.topcoder-dev.com/opportunities/challenge/challenge-1', topicId: 'topic-1', topicTitle: 'Topic title', postContent: 'Persisted post content', @@ -557,8 +702,38 @@ describe('ForumsWatchNotificationService', () => { const payload = eventBusService.postEvent.mock.calls[0][1]; expect(payload.data.challengeId).toBe('challenge-1'); expect(payload.data).not.toHaveProperty('challengeTitle'); + expect(payload.data.challengeURL).toBe( + 'https://www.topcoder-dev.com/opportunities/challenge/challenge-1', + ); expect(warnSpy).toHaveBeenCalledWith( expect.stringContaining('challenge challenge-1 title lookup failed'), ); }); + + it('still publishes the challenge name when public URL configuration is invalid', async () => { + const warnSpy = jest.spyOn(Logger.prototype, 'warn').mockImplementation(); + const { challengeApiService, eventBusService, service } = createService(); + challengeApiService.getChallengeUrl.mockImplementation(() => { + throw new Error('TOPCODER_URL is invalid'); + }); + + const result = await service.publishPostNotification({ + topic: makeTopic(), + post: makePost(), + restrictions: { + challengeId: 'challenge-1', + roleName: null, + hasRestrictionConflict: false, + }, + operationName: 'createPost', + }); + + expect(result).toEqual({ attemptedRecipientCount: 1, published: true }); + const payload = eventBusService.postEvent.mock.calls[0][1]; + expect(payload.data.challengeName).toBe('Challenge title'); + expect(payload.data).not.toHaveProperty('challengeURL'); + expect(warnSpy).toHaveBeenCalledWith( + expect.stringContaining('challenge challenge-1 URL resolution failed'), + ); + }); }); diff --git a/src/forums/forums-watch-notification.service.ts b/src/forums/forums-watch-notification.service.ts index df12c02..1fd4810 100644 --- a/src/forums/forums-watch-notification.service.ts +++ b/src/forums/forums-watch-notification.service.ts @@ -2,6 +2,7 @@ import { Injectable, Logger } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { Post, Topic } from '../../prisma/generated/client'; import { DbService } from '../db/db.service'; +import { ChallengeForumWatchService } from './challenge-forum-watch.service'; import { ChallengeApiService } from './challenge-api.service'; import { EventBusService } from './event-bus.service'; import { ForumsAccessPolicyService } from './forums-access-policy.service'; @@ -26,6 +27,11 @@ interface ForumsWatchNotificationEmailPayload { version: 'v3'; } +interface ForumsNotificationChallengeContext { + title?: string; + url?: string; +} + /** * Parameters used to publish a watch notification for newly persisted content. */ @@ -71,7 +77,8 @@ export class ForumsNotificationPublishError extends Error { * Publishes post-commit forum watch notifications. * * The service resolves explicit watches on the created post's topic and all - * ancestors, dedupes by member id, excludes the persisted author member id, + * ancestors plus effective challenge-wide watches, dedupes by member id, + * excludes the persisted author member id, * filters active member bans and remaining recipients through the shared forums * access policy, resolves the challenge title when applicable, and publishes one * SendGrid email event for the final recipient list. IP-ban checks are intentionally @@ -92,6 +99,7 @@ export class ForumsWatchNotificationService { * @param eventBusService Local event-bus adapter. * @param configService Nest configuration service containing notification settings. * @param challengeApiService M2M-authenticated lookup for challenge titles. + * @param challengeWatchService Resource-based defaults and explicit forum preferences. * @throws Does not throw directly; dependencies are resolved by Nest. */ constructor( @@ -103,6 +111,7 @@ export class ForumsWatchNotificationService { private readonly eventBusService: EventBusService, private readonly configService: ConfigService, private readonly challengeApiService: ChallengeApiService, + private readonly challengeWatchService: ChallengeForumWatchService, ) {} /** @@ -129,6 +138,7 @@ export class ForumsWatchNotificationService { const watcherMemberIds = await this.resolveWatcherMemberIds( params.topic.id, params.post.authorMemberId, + params.restrictions.challengeId, ); if (watcherMemberIds.length === 0) { @@ -160,12 +170,12 @@ export class ForumsWatchNotificationService { return { attemptedRecipientCount: 0, published: false }; } - const challengeTitle = await this.resolveChallengeTitle(params); + const challengeContext = await this.resolveChallengeContext(params); const payload = this.buildEmailPayload( params, templateId, recipientEmails, - challengeTitle, + challengeContext, ); try { @@ -188,16 +198,19 @@ export class ForumsWatchNotificationService { } /** - * Resolves watched members from topic and ancestor watch rows. + * Resolves topic, ancestor, and challenge-wide watches, excluding explicit + * opt-outs on the receiving topic even when the member watches the forum. * * @param topicId Topic id receiving the newly created post. * @param authorMemberId Persisted author member id to exclude. + * @param challengeId Effective challenge scope, or null for general forums. * @returns Deduped watcher member ids, excluding the author. - * @throws Prisma errors when closure or watch rows cannot be loaded. + * @throws Database errors when topic, resource, or challenge watch rows cannot be loaded. */ private async resolveWatcherMemberIds( topicId: string, authorMemberId: string, + challengeId: string | null, ): Promise { const closureRows = await this.db.topicClosure.findMany({ where: { descendantTopicId: topicId }, @@ -214,10 +227,27 @@ export class ForumsWatchNotificationService { topicId: { in: watchedTopicIds }, memberId: { not: authorMemberId }, }, - select: { memberId: true }, + select: { topicId: true, memberId: true, watching: true }, }); - return Array.from(new Set(watchRows.map((watch) => watch.memberId))); + const challengeWatchers = challengeId + ? await this.challengeWatchService.getWatcherMemberIds(challengeId) + : []; + const topicOptOuts = new Set( + watchRows + .filter((watch) => watch.topicId === topicId && !watch.watching) + .map((watch) => watch.memberId), + ); + return Array.from( + new Set([ + ...watchRows + .filter((watch) => watch.watching) + .map((watch) => watch.memberId), + ...challengeWatchers, + ]), + ).filter( + (memberId) => memberId !== authorMemberId && !topicOptOuts.has(memberId), + ); } /** @@ -374,30 +404,43 @@ export class ForumsWatchNotificationService { } /** - * Looks up the effective challenge title once per outgoing notification. + * Resolves the effective challenge name and public URL once per outgoing notification. * * @param params Current notification parameters with inherited challenge context. - * @returns Challenge title, or `undefined` for non-challenge topics or lookup failures. - * @throws Does not throw; lookup failures are logged so email delivery continues. + * @returns Available challenge template fields, or an empty object for non-challenge topics. + * @throws Does not throw; title and URL failures are logged independently so email delivery continues. */ - private async resolveChallengeTitle( + private async resolveChallengeContext( params: PublishForumsPostNotificationParams, - ): Promise { + ): Promise { const challengeId = params.restrictions.challengeId; if (!challengeId) { - return undefined; + return {}; } + const context: ForumsNotificationChallengeContext = {}; + try { - return await this.challengeApiService.getChallengeTitle(challengeId); + context.title = + await this.challengeApiService.getChallengeTitle(challengeId); } catch (error) { const message = error instanceof Error ? error.message : 'unknown error'; this.logger.warn( `${params.operationName} notification for topic ${params.topic.id}, post ${params.post.id}: challenge ${challengeId} title lookup failed: ${message}`, ); - return undefined; } + + try { + context.url = this.challengeApiService.getChallengeUrl(challengeId); + } catch (error) { + const message = error instanceof Error ? error.message : 'unknown error'; + this.logger.warn( + `${params.operationName} notification for topic ${params.topic.id}, post ${params.post.id}: challenge ${challengeId} URL resolution failed: ${message}`, + ); + } + + return context; } /** @@ -406,7 +449,7 @@ export class ForumsWatchNotificationService { * @param params Current notification publish parameters. * @param templateId Configured SendGrid template id. * @param recipients Final recipient email list. - * @param challengeTitle Challenge API name when the effective challenge resolves. + * @param challengeContext Challenge API name and public Opportunities URL when available. * @returns Event-bus email payload. * @throws Does not throw. */ @@ -414,14 +457,25 @@ export class ForumsWatchNotificationService { params: PublishForumsPostNotificationParams, templateId: string, recipients: string[], - challengeTitle: string | undefined, + challengeContext: ForumsNotificationChallengeContext, ): ForumsWatchNotificationEmailPayload { return { data: { ...(params.restrictions.challengeId ? { challengeId: params.restrictions.challengeId } : {}), - ...(challengeTitle ? { challengeTitle } : {}), + ...(challengeContext.title + ? { + challengeName: challengeContext.title, + challengeTitle: challengeContext.title, + } + : {}), + ...(challengeContext.url + ? { + challengeURL: challengeContext.url, + challengeUrl: challengeContext.url, + } + : {}), topicId: params.topic.id, topicTitle: params.topic.title, postContent: params.post.content ?? '', diff --git a/src/forums/forums.module.ts b/src/forums/forums.module.ts index afcd6f9..2923b03 100644 --- a/src/forums/forums.module.ts +++ b/src/forums/forums.module.ts @@ -1,6 +1,8 @@ import { Module } from '@nestjs/common'; import { DbModule } from '../db/db.module'; import { ChallengeAccessService } from './challenge-access.service'; +import { ChallengeForumWatchController } from './challenge-forum-watch.controller'; +import { ChallengeForumWatchService } from './challenge-forum-watch.service'; import { ChallengeApiService } from './challenge-api.service'; import { EventBusService } from './event-bus.service'; import { ForumsAccessPolicyService } from './forums-access-policy.service'; @@ -34,12 +36,14 @@ import { TopicsController } from './topics.controller'; @Module({ imports: [DbModule], controllers: [ + ChallengeForumWatchController, TopicsController, PostsController, ModerationController, PublicForumsController, ], providers: [ + ChallengeForumWatchService, ChallengeAccessService, ChallengeApiService, EventBusService, diff --git a/src/forums/public-forums-query.service.ts b/src/forums/public-forums-query.service.ts index bc551a8..26c1ebf 100644 --- a/src/forums/public-forums-query.service.ts +++ b/src/forums/public-forums-query.service.ts @@ -159,7 +159,7 @@ export class PublicForumsQueryService { ? Prisma.sql`((${this.visibleThreads(principal, categoryIds)}) OR t.id IN (${Prisma.join(categoryIds)}))` : this.visibleThreads(principal, categoryIds); const watchFilter = watching - ? Prisma.sql`AND EXISTS (SELECT 1 FROM "TopicWatch" w WHERE w."topicId" = t.id AND w."memberId" = ${principal.memberId})` + ? Prisma.sql`AND EXISTS (SELECT 1 FROM "TopicWatch" w WHERE w."topicId" = t.id AND w."memberId" = ${principal.memberId} AND w.watching = true)` : Prisma.empty; const searchFilter = search ? Prisma.sql`AND (STRPOS(LOWER(t.title), ${search}) > 0 OR EXISTS ( diff --git a/src/forums/resource-access.service.spec.ts b/src/forums/resource-access.service.spec.ts new file mode 100644 index 0000000..2d26e2d --- /dev/null +++ b/src/forums/resource-access.service.spec.ts @@ -0,0 +1,44 @@ +import { ResourceAccessService } from './resource-access.service'; + +describe('ResourceAccessService default forum watchers', () => { + it('selects submitters and copilot roles only within the requested challenge', async () => { + const service = new ResourceAccessService({ get: jest.fn() } as any); + const findMany = jest + .fn() + .mockResolvedValue([ + { memberId: 'registrant' }, + { memberId: 'copilot' }, + { memberId: 'copilot' }, + ]); + (service as any).initialized = true; + (service as any).client = { resource: { findMany } }; + await expect( + service.getChallengeDefaultWatcherMemberIds('challenge'), + ).resolves.toEqual(['registrant', 'copilot']); + expect(findMany).toHaveBeenCalledWith({ + where: { + challengeId: 'challenge', + resourceRole: { + OR: [ + { nameLower: 'submitter' }, + { nameLower: { contains: 'copilot' } }, + ], + }, + }, + select: { memberId: true }, + }); + await service.getChallengeDefaultWatcherMemberIds('challenge', 'member'); + expect(findMany).toHaveBeenLastCalledWith( + expect.objectContaining({ + where: expect.objectContaining({ memberId: 'member' }), + }), + ); + }); + + it('returns no defaults when the resource database is not configured', async () => { + const service = new ResourceAccessService({ get: jest.fn() } as any); + await expect( + service.getChallengeDefaultWatcherMemberIds('challenge'), + ).resolves.toEqual([]); + }); +}); diff --git a/src/forums/resource-access.service.ts b/src/forums/resource-access.service.ts index eb695a3..cf04da8 100644 --- a/src/forums/resource-access.service.ts +++ b/src/forums/resource-access.service.ts @@ -119,6 +119,39 @@ export class ResourceAccessService implements OnModuleDestroy { return new Set(resources.map((resource) => resource.memberId)); } + /** + * Finds registrants and copilots who watch a challenge forum by default. + * Resource assignments are read at delivery time, so registration and copilot + * assignment take effect without requiring a forum visit or event consumer. + * + * @param challengeId Challenge whose default watchers are requested. + * @param memberId Optional member filter for the current-member watch endpoint. + * @returns Deduplicated member ids with a Submitter or Copilot resource role. + * @throws Prisma errors when a configured resource database lookup fails. + */ + async getChallengeDefaultWatcherMemberIds( + challengeId: string, + memberId?: string, + ): Promise { + const client = this.resolveClient(); + if (!client) return []; + + const resources = await client.resource.findMany({ + where: { + challengeId, + ...(memberId ? { memberId } : {}), + resourceRole: { + OR: [ + { nameLower: 'submitter' }, + { nameLower: { contains: 'copilot' } }, + ], + }, + }, + select: { memberId: true }, + }); + return Array.from(new Set(resources.map((resource) => resource.memberId))); + } + /** * Disconnects the resource Prisma client during Nest module shutdown. * diff --git a/src/forums/topic-watch-schema.integration.spec.ts b/src/forums/topic-watch-schema.integration.spec.ts new file mode 100644 index 0000000..e09b135 --- /dev/null +++ b/src/forums/topic-watch-schema.integration.spec.ts @@ -0,0 +1,112 @@ +import { ConfigService } from '@nestjs/config'; +import { DbService } from '../db/db.service'; +import { ForumsReadQueryService } from './forums-read-query.service'; + +const databaseUrl = process.env.FORUMS_DATABASE_URL ?? process.env.DATABASE_URL; +const itWithDatabase = databaseUrl ? it : it.skip; +const challengeId = `watch-schema-${process.pid}-${Date.now()}`; + +/** Verifies topic preference persistence and raw SQL projections against migrated PostgreSQL. */ +describe('topic watch schema persistence', () => { + let db: DbService; + let queries: ForumsReadQueryService; + const topicIds: string[] = []; + + beforeAll(async () => { + if (!databaseUrl) return; + const url = new URL(databaseUrl); + url.searchParams.set('options', '-c search_path=forums,public'); + db = new DbService(new ConfigService({ database: { url: url.href } })); + await db.$connect(); + queries = new ForumsReadQueryService(db); + for (const title of ['Inherited', 'Watched', 'Unwatched']) { + const topic = await db.topic.create({ + data: { + challengeId, + title, + authorMemberId: 'watch-author', + authorHandle: 'watch-author', + }, + }); + topicIds.push(topic.id); + await db.topicClosure.create({ + data: { + ancestorTopicId: topic.id, + descendantTopicId: topic.id, + depth: 0, + }, + }); + } + await db.topicWatch.create({ + data: { topicId: topicIds[1], memberId: 'watch-member' }, + }); + await db.topicWatch.create({ + data: { topicId: topicIds[2], memberId: 'watch-member', watching: false }, + }); + }); + + afterAll(async () => { + if (!db) return; + await db.topic.deleteMany({ where: { challengeId } }); + await db.challengeForumWatch.deleteMany({ where: { challengeId } }); + await db.$disconnect(); + }); + + itWithDatabase( + 'distinguishes inherited, explicit watched and explicit unwatched topics in list and detail SQL', + async () => { + for (const watching of [false, true]) { + await db.challengeForumWatch.upsert({ + where: { + challengeId_memberId: { challengeId, memberId: 'watch-member' }, + }, + create: { challengeId, memberId: 'watch-member', watching }, + update: { watching }, + }); + const rows = await queries.findChallengeRootTopicSummaries( + challengeId, + 'watch-member', + ); + expect( + topicIds.map((id) => rows.find((row) => row.id === id)?.watching), + ).toEqual([null, true, false]); + const detail = await queries.findTopicDetailSnapshot( + topicIds[2], + 'watch-member', + ); + expect(detail?.summaryRow.watching).toBe(false); + } + expect( + await db.topicWatch.count({ where: { topicId: { in: topicIds } } }), + ).toBe(2); + const anonymousRows = await queries.findChallengeRootTopicSummaries( + challengeId, + null, + ); + expect(anonymousRows.every((row) => row.watching === null)).toBe(true); + }, + ); + + itWithDatabase( + 'updates the same explicit opt-out row when a topic is watched again', + async () => { + const where = { + topicId_memberId: { topicId: topicIds[2], memberId: 'watch-member' }, + }; + for (const watching of [false, false, true]) { + await db.topicWatch.upsert({ + where, + create: { ...where.topicId_memberId, watching }, + update: { watching }, + }); + } + expect(await db.topicWatch.count({ where: where.topicId_memberId })).toBe( + 1, + ); + expect( + (await queries.findTopicDetailSnapshot(topicIds[2], 'watch-member')) + ?.summaryRow.watching, + ).toBe(true); + }, + ); +}); diff --git a/src/forums/topics.controller.ts b/src/forums/topics.controller.ts index d60c255..c6e8c4f 100644 --- a/src/forums/topics.controller.ts +++ b/src/forums/topics.controller.ts @@ -108,7 +108,7 @@ export class TopicsController { @ApiOperation({ summary: 'Create a forum topic with a starter post', description: - 'Command route requiring an authenticated member token or `create:forums-topics`. Top-level non-challenge topics are allowed for human admins and scoped M2M callers. Top-level challenge topics are allowed for eligible challenge members, challenge copilots, and admins; M2M callers cannot create challenge roots. Child topics require visible parents, monotonic restrictions, and a resolved effective non-challenge context; challenge-scoped child creates are forbidden. Active member bans and trusted exact-IP bans return 403 before writes. Locked parents reject child-topic creation unless the caller is an administrator or eligible challenge copilot. M2M-created content uses the system author and does not seed member watch or read-state rows. Successful child-topic starter posts trigger best-effort watch notifications after commit.', + 'Command route requiring an authenticated member token or `create:forums-topics`. Top-level non-challenge topics are allowed for human admins and scoped M2M callers. Top-level challenge topics are allowed for eligible challenge members, challenge copilots, and admins; M2M callers cannot create challenge roots. Child topics require visible parents, monotonic restrictions, and a resolved effective non-challenge context; challenge-scoped child creates are forbidden. Active member bans and trusted exact-IP bans return 403 before writes. Locked parents reject child-topic creation unless the caller is an administrator or eligible challenge copilot. M2M-created content uses the system author and does not seed member watch or read-state rows. Successful child-topic and top-level challenge starter posts trigger best-effort watch notifications after commit.', }) @ApiResponse({ status: 201, @@ -166,7 +166,9 @@ export class TopicsController { type: ForumsTopicSummaryPageDto, }) @ApiBadRequestResponse({ description: 'Invalid pagination query.' }) - @ApiUnauthorizedResponse({ description: 'A supplied bearer token is invalid.' }) + @ApiUnauthorizedResponse({ + description: 'A supplied bearer token is invalid.', + }) @ApiForbiddenResponse({ description: 'Active forums ban.' }) listGeneralRootTopics( @Query() query: ForumsTopicListQueryDto, @@ -260,7 +262,9 @@ export class TopicsController { description: 'Visible direct child topics.', type: [ForumsTopicSummaryDto], }) - @ApiUnauthorizedResponse({ description: 'A supplied bearer token is invalid.' }) + @ApiUnauthorizedResponse({ + description: 'A supplied bearer token is invalid.', + }) @ApiForbiddenResponse({ description: 'Active forums ban or parent topic visibility denied.', }) @@ -300,7 +304,9 @@ export class TopicsController { description: 'Topic detail with embedded post tree.', type: ForumsTopicDetailDto, }) - @ApiUnauthorizedResponse({ description: 'A supplied bearer token is invalid.' }) + @ApiUnauthorizedResponse({ + description: 'A supplied bearer token is invalid.', + }) @ApiForbiddenResponse({ description: 'Active forums ban or topic visibility denied.', }) @@ -484,7 +490,7 @@ export class TopicsController { } /** - * Removes a watch on a topic for a member. + * Unwatches a topic, persisting an opt-out for challenge-scoped topics. * * @param topicId Topic id from the route. * @param dto Optional target member body; required for scoped M2M callers. @@ -501,12 +507,13 @@ export class TopicsController { @ApiOperation({ summary: 'Unwatch a forum topic', description: - 'Command route requiring an authenticated member token or `remove:forums-topic-watch`. Human tokens unwatch for themselves and active member or trusted exact-IP bans return 403. Scoped M2M callers must supply a `memberId` that resolves in external Members and Identity data; active target-member bans return 403 before context loading, and policy evaluates inherited challenge and role visibility for that target member before removing the watch row.', + 'Command route requiring an authenticated member token or `remove:forums-topic-watch`. Human tokens unwatch for themselves and active member or trusted exact-IP bans return 403. Scoped M2M callers must supply a `memberId` that resolves in external Members and Identity data; active target-member bans return 403 before context loading, and policy evaluates inherited challenge and role visibility for that target member before persisting a challenge-topic opt-out or removing a general-topic watch row.', }) @ApiParam({ name: 'topicId', description: 'Topic id.' }) @ApiResponse({ status: 200, - description: 'Topic watch removed.', + description: + 'Topic unwatched; challenge-topic opt-outs override the forum preference.', type: TopicWatchStateCommandResponseDto, }) @ApiBadRequestResponse({ description: 'Invalid member target.' })