From c0c7bf41ce16cce4598a4df00b4a58e58fcff81b Mon Sep 17 00:00:00 2001 From: Omar Ibrahim Date: Fri, 10 Jul 2026 16:54:14 -0500 Subject: [PATCH 01/51] Feat(commons): add nested comment thread components --- .../app/components/comment/CommentActions.vue | 77 ++++ .../components/comment/CommentElbowSvg.vue | 12 + .../app/components/comment/CommentInput.vue | 121 ++++++ .../components/comment/CommentMetadataBar.vue | 42 ++ .../app/components/comment/CommentSeeMore.vue | 25 ++ .../app/components/comment/CommentSpine.vue | 3 + .../components/comment/CommentSpineEraser.vue | 18 + .../comment/CommentTextRepresentation.vue | 69 ++++ .../app/components/comment/CommentView.vue | 249 ++++++++++++ .../app/components/comment/CommentsPanel.vue | 118 ++++++ .../comment/ConfirmDeleteCommentDialog.vue | 58 +++ .../app/components/comment/comment-tree.ts | 118 ++++++ .../app/components/comment/fixtures.ts | 360 ++++++++++++++++++ .../app/components/comment/types.ts | 63 +++ 14 files changed, 1333 insertions(+) create mode 100644 apps/modeling-commons-frontend/app/components/comment/CommentActions.vue create mode 100644 apps/modeling-commons-frontend/app/components/comment/CommentElbowSvg.vue create mode 100644 apps/modeling-commons-frontend/app/components/comment/CommentInput.vue create mode 100644 apps/modeling-commons-frontend/app/components/comment/CommentMetadataBar.vue create mode 100644 apps/modeling-commons-frontend/app/components/comment/CommentSeeMore.vue create mode 100644 apps/modeling-commons-frontend/app/components/comment/CommentSpine.vue create mode 100644 apps/modeling-commons-frontend/app/components/comment/CommentSpineEraser.vue create mode 100644 apps/modeling-commons-frontend/app/components/comment/CommentTextRepresentation.vue create mode 100644 apps/modeling-commons-frontend/app/components/comment/CommentView.vue create mode 100644 apps/modeling-commons-frontend/app/components/comment/CommentsPanel.vue create mode 100644 apps/modeling-commons-frontend/app/components/comment/ConfirmDeleteCommentDialog.vue create mode 100644 apps/modeling-commons-frontend/app/components/comment/comment-tree.ts create mode 100644 apps/modeling-commons-frontend/app/components/comment/fixtures.ts create mode 100644 apps/modeling-commons-frontend/app/components/comment/types.ts diff --git a/apps/modeling-commons-frontend/app/components/comment/CommentActions.vue b/apps/modeling-commons-frontend/app/components/comment/CommentActions.vue new file mode 100644 index 00000000..f43ab76a --- /dev/null +++ b/apps/modeling-commons-frontend/app/components/comment/CommentActions.vue @@ -0,0 +1,77 @@ + + + diff --git a/apps/modeling-commons-frontend/app/components/comment/CommentElbowSvg.vue b/apps/modeling-commons-frontend/app/components/comment/CommentElbowSvg.vue new file mode 100644 index 00000000..ae533ea4 --- /dev/null +++ b/apps/modeling-commons-frontend/app/components/comment/CommentElbowSvg.vue @@ -0,0 +1,12 @@ + diff --git a/apps/modeling-commons-frontend/app/components/comment/CommentInput.vue b/apps/modeling-commons-frontend/app/components/comment/CommentInput.vue new file mode 100644 index 00000000..03af477e --- /dev/null +++ b/apps/modeling-commons-frontend/app/components/comment/CommentInput.vue @@ -0,0 +1,121 @@ + + + diff --git a/apps/modeling-commons-frontend/app/components/comment/CommentMetadataBar.vue b/apps/modeling-commons-frontend/app/components/comment/CommentMetadataBar.vue new file mode 100644 index 00000000..6d704ec6 --- /dev/null +++ b/apps/modeling-commons-frontend/app/components/comment/CommentMetadataBar.vue @@ -0,0 +1,42 @@ + + + diff --git a/apps/modeling-commons-frontend/app/components/comment/CommentSeeMore.vue b/apps/modeling-commons-frontend/app/components/comment/CommentSeeMore.vue new file mode 100644 index 00000000..9ae199de --- /dev/null +++ b/apps/modeling-commons-frontend/app/components/comment/CommentSeeMore.vue @@ -0,0 +1,25 @@ + + + diff --git a/apps/modeling-commons-frontend/app/components/comment/CommentSpine.vue b/apps/modeling-commons-frontend/app/components/comment/CommentSpine.vue new file mode 100644 index 00000000..a6592e2a --- /dev/null +++ b/apps/modeling-commons-frontend/app/components/comment/CommentSpine.vue @@ -0,0 +1,3 @@ + diff --git a/apps/modeling-commons-frontend/app/components/comment/CommentSpineEraser.vue b/apps/modeling-commons-frontend/app/components/comment/CommentSpineEraser.vue new file mode 100644 index 00000000..7d4c562a --- /dev/null +++ b/apps/modeling-commons-frontend/app/components/comment/CommentSpineEraser.vue @@ -0,0 +1,18 @@ + + + \ No newline at end of file diff --git a/apps/modeling-commons-frontend/app/components/comment/CommentTextRepresentation.vue b/apps/modeling-commons-frontend/app/components/comment/CommentTextRepresentation.vue new file mode 100644 index 00000000..687d7f30 --- /dev/null +++ b/apps/modeling-commons-frontend/app/components/comment/CommentTextRepresentation.vue @@ -0,0 +1,69 @@ + + + + diff --git a/apps/modeling-commons-frontend/app/components/comment/CommentView.vue b/apps/modeling-commons-frontend/app/components/comment/CommentView.vue new file mode 100644 index 00000000..7483eeac --- /dev/null +++ b/apps/modeling-commons-frontend/app/components/comment/CommentView.vue @@ -0,0 +1,249 @@ + + + diff --git a/apps/modeling-commons-frontend/app/components/comment/CommentsPanel.vue b/apps/modeling-commons-frontend/app/components/comment/CommentsPanel.vue new file mode 100644 index 00000000..f94385d1 --- /dev/null +++ b/apps/modeling-commons-frontend/app/components/comment/CommentsPanel.vue @@ -0,0 +1,118 @@ + + + diff --git a/apps/modeling-commons-frontend/app/components/comment/ConfirmDeleteCommentDialog.vue b/apps/modeling-commons-frontend/app/components/comment/ConfirmDeleteCommentDialog.vue new file mode 100644 index 00000000..7eab0c8d --- /dev/null +++ b/apps/modeling-commons-frontend/app/components/comment/ConfirmDeleteCommentDialog.vue @@ -0,0 +1,58 @@ + + + \ No newline at end of file diff --git a/apps/modeling-commons-frontend/app/components/comment/comment-tree.ts b/apps/modeling-commons-frontend/app/components/comment/comment-tree.ts new file mode 100644 index 00000000..3f3a62c2 --- /dev/null +++ b/apps/modeling-commons-frontend/app/components/comment/comment-tree.ts @@ -0,0 +1,118 @@ +import type { Comment, CommentPagination } from "./types"; + +export function visibleReplies(comment: Comment, revealedCount: number): Array { + return (comment.replies ?? []).slice(0, revealedCount); +} + +export function hiddenLoadedReplyCount(comment: Comment, revealedCount: number): number { + return Math.max(0, (comment.replies ?? []).length - revealedCount); +} + +export function serverRemainingReplyCount(comment: Comment): number { + const totalReplies = comment.replyPagination?.count ?? 0; + return Math.max(0, totalReplies - (comment.replies ?? []).length); +} + +// At the nesting limit the full pagination count feeds the continue-thread +// link; at visible depths see-more first reveals locally loaded replies and +// only then reports the server remainder (which a click turns into a `load`). +export function remainingReplyCount( + comment: Comment, + revealedCount: number, + maximumNested: number, +): number { + if (maximumNested <= 0) return comment.replyPagination?.count ?? 0; + const hiddenLoaded = hiddenLoadedReplyCount(comment, revealedCount); + return hiddenLoaded > 0 ? hiddenLoaded : serverRemainingReplyCount(comment); +} + +export function hasVisibleReplies(comment: Comment, maximumNested: number): boolean { + return maximumNested > 0 && (comment.replies ?? []).length > 0; +} + +export function hasSpine(comment: Comment, revealedCount: number, maximumNested: number): boolean { + return ( + hasVisibleReplies(comment, maximumNested) || + remainingReplyCount(comment, revealedCount, maximumNested) > 0 + ); +} + +export function remainingCommentCount( + comments: Array, + pagination: CommentPagination, +): number { + return Math.max(0, pagination.count - comments.length); +} + +export function findCommentById(comments: Array, id: string): Comment | null { + for (const comment of comments) { + if (comment.id === id) return comment; + const found = findCommentById(comment.replies ?? [], id); + if (found) return found; + } + return null; +} + +// The mutation helpers below are immutable: they return a new array when the +// target is found and the SAME array reference when nothing changed, so +// callers can reassign and untouched nodes keep their identity. +export function updateCommentById( + comments: Array, + id: string, + updater: (comment: Comment) => Comment, +): Array { + let changed = false; + const next = comments.map((comment) => { + if (comment.id === id) { + changed = true; + return updater(comment); + } + const replies = comment.replies ?? []; + const nextReplies = updateCommentById(replies, id, updater); + if (nextReplies === replies) return comment; + changed = true; + return { ...comment, replies: nextReplies }; + }); + return changed ? next : comments; +} + +export function insertReply( + comments: Array, + parentId: string, + reply: Comment, +): Array { + return updateCommentById(comments, parentId, (parent) => ({ + ...parent, + replies: [reply, ...(parent.replies ?? [])], + replyPagination: { + lastPage: parent.replyPagination?.lastPage ?? 0, + count: (parent.replyPagination?.count ?? 0) + 1, + }, + })); +} + +export function removeCommentById(comments: Array, id: string): Array { + if (comments.some((comment) => comment.id === id)) { + return comments.filter((comment) => comment.id !== id); + } + let changed = false; + const next = comments.map((comment) => { + const replies = comment.replies ?? []; + const nextReplies = removeCommentById(replies, id); + if (nextReplies === replies) return comment; + changed = true; + const removedDirectChild = nextReplies.length === replies.length - 1; + if (!removedDirectChild || !comment.replyPagination) { + return { ...comment, replies: nextReplies }; + } + return { + ...comment, + replies: nextReplies, + replyPagination: { + ...comment.replyPagination, + count: Math.max(0, comment.replyPagination.count - 1), + }, + }; + }); + return changed ? next : comments; +} diff --git a/apps/modeling-commons-frontend/app/components/comment/fixtures.ts b/apps/modeling-commons-frontend/app/components/comment/fixtures.ts new file mode 100644 index 00000000..f032c77c --- /dev/null +++ b/apps/modeling-commons-frontend/app/components/comment/fixtures.ts @@ -0,0 +1,360 @@ +import type { Comment, CommentAuthor } from "./types"; + +const commentText = `Lorem Ipsum is https://www.omar-ibrahim.com simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since 1966, when designers at Letraset and James Mosley, the librarian at St Bride Printing Library in London, took a 1914 Cicero translation and scrambled it to make dummy text for Letraset's Body Type sheets. It has survived not only many decades, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised thanks to these sheets and more recently with desktop publishing software like Aldus PageMaker and Microsoft Word including versions of Lorem Ipsum.Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since 1966, when designers at Letraset and James Mosley, the librarian at St Bride Printing Library in London, took a 1914 Cicero translation and scrambled it to make dummy text for Letraset's Body Type sheets. It has survived not only many decades, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised thanks to these sheets and more recently with desktop publishing software like Aldus PageMaker and Microsoft Word including versions of Lorem Ipsum`; + +const authors = [ + { name: "Omar Ibrahim", image: "https://i.pravatar.cc/150?u=omar" }, + { name: "Jane Doe", image: "https://i.pravatar.cc/150?u=jane" }, + { name: "John Smith", image: "https://i.pravatar.cc/150?u=john" }, +] as const satisfies Array; + +const [omar, jane, john] = authors; + +export const DEMO_MODEL_ID = "model-demo"; + +const stampModelId = (comment: Comment, modelId: string = DEMO_MODEL_ID): Comment => ({ + ...comment, + modelId, + replies: comment.replies?.map((reply) => stampModelId(reply, modelId)), +}); + +export const longComment: Comment = stampModelId({ + id: "1", + author: omar, + content: commentText, + createdAt: "2024-03-11T15:30:00", + likes: 24, + likedByMe: true, + permissions: { + canDelete: true, + canEdit: true, + }, + replyPagination: { + count: 2, + lastPage: 1, + }, + replies: [ + { + id: "2", + author: jane, + content: "This is a reply to the comment.", + createdAt: "2024-03-11T16:02:00", + likes: 10, + likedByMe: false, + replyPagination: { + count: 0, + lastPage: 0, + }, + replies: [ + { + id: "3", + author: john, + content: "And this is a nested reply.", + createdAt: "2024-03-11T16:45:00", + likes: 2, + likedByMe: false, + replyPagination: { + count: 0, + lastPage: 0, + }, + replies: [], + }, + ], + }, + { + id: "4", + author: john, + content: "Short one.", + createdAt: "2024-03-12T09:15:00", + likes: 0, + likedByMe: false, + replyPagination: { + count: 0, + lastPage: 0, + }, + replies: [], + }, + ], +}); + +export const deepThread: Comment = stampModelId({ + id: "100", + author: omar, + content: "Hot take: tabs are objectively better than spaces. Fight me.", + createdAt: "2024-03-10T09:00:00", + likes: 142, + likedByMe: true, + replyPagination: { + count: 4, + lastPage: 1, + }, + replies: [ + { + id: "101", + author: jane, + content: "You're wrong and you know it.", + createdAt: "2024-03-10T09:05:00", + likes: 89, + likedByMe: false, + replyPagination: { + count: 3, + lastPage: 1, + }, + replies: [ + { + id: "102", + author: omar, + content: "Explain yourself.", + createdAt: "2024-03-10T09:07:00", + likes: 12, + likedByMe: false, + replyPagination: { + count: 2, + lastPage: 1, + }, + replies: [ + { + id: "103", + author: jane, + content: + "Spaces render identically everywhere. Tabs depend on editor config. That's the whole argument.", + createdAt: "2024-03-10T09:12:00", + likes: 54, + likedByMe: true, + replyPagination: { + count: 2, + lastPage: 0, + }, + replies: [ + { + id: "104", + author: john, + content: "That's a feature, not a bug. Accessibility.", + createdAt: "2024-03-10T09:20:00", + likes: 31, + likedByMe: false, + replyPagination: { + count: 1, + lastPage: 0, + }, + replies: [ + { + id: "105", + author: jane, + content: "Ok that's actually a fair point.", + createdAt: "2024-03-10T09:25:00", + edited: true, + likes: 18, + likedByMe: false, + replyPagination: { + count: 1, + lastPage: 0, + }, + replies: [ + { + id: "106", + author: omar, + content: commentText, + createdAt: "2024-03-10T09:31:00", + likes: 4, + likedByMe: false, + replyPagination: { + count: 0, + lastPage: 0, + }, + replies: [], + }, + ], + }, + ], + }, + { + id: "107", + author: omar, + content: "Config your editor. It's 2024.", + createdAt: "2024-03-10T09:22:00", + likes: 6, + likedByMe: false, + replyPagination: { + count: 0, + lastPage: 0, + }, + replies: [], + }, + ], + }, + { + id: "108", + author: john, + content: "Both of you are wasting your lives.", + createdAt: "2024-03-10T09:14:00", + likes: 203, + likedByMe: true, + replyPagination: { + count: 0, + lastPage: 0, + }, + replies: [], + }, + ], + }, + { + id: "109", + author: john, + content: "Seconded.", + createdAt: "2024-03-10T09:09:00", + likes: 3, + likedByMe: false, + replyPagination: { + count: 0, + lastPage: 0, + }, + replies: [], + }, + { + id: "110", + author: omar, + content: "Unfollowed.", + createdAt: "2024-03-10T09:10:00", + likes: 0, + likedByMe: false, + replyPagination: { + count: 0, + lastPage: 0, + }, + replies: [], + }, + ], + }, + { + id: "111", + author: john, + content: + "My editor converts tabs to spaces on save so I genuinely don't know what I'm using.", + createdAt: "2024-03-10T10:00:00", + likes: 77, + likedByMe: false, + replyPagination: { + count: 2, + lastPage: 0, + }, + replies: [ + { + id: "112", + author: omar, + content: "This is the correct answer.", + createdAt: "2024-03-10T10:02:00", + likes: 9, + likedByMe: false, + replyPagination: { + count: 0, + lastPage: 0, + }, + replies: [], + }, + { + id: "113", + author: jane, + content: "Blissful ignorance.", + createdAt: "2024-03-10T10:03:00", + likes: 15, + likedByMe: true, + replyPagination: { + count: 1, + lastPage: 0, + }, + replies: [ + { + id: "114", + author: john, + content: "I sleep well.", + createdAt: "2024-03-10T10:05:00", + likes: 44, + likedByMe: false, + replyPagination: { + count: 0, + lastPage: 0, + }, + replies: [], + }, + ], + }, + ], + }, + { + id: "115", + author: jane, + content: "Locking this thread.", + createdAt: "2024-03-10T11:00:00", + likes: 500, + likedByMe: true, + replyPagination: { + count: 0, + lastPage: 0, + }, + replies: [], + }, + { + id: "116", + author: john, + content: "You can't, you're not a mod.", + createdAt: "2024-03-10T11:01:00", + likes: 612, + likedByMe: true, + replyPagination: { + count: 0, + lastPage: 0, + }, + replies: [], + }, + ], +}); + +export const shortComment: Comment = stampModelId({ + id: "5", + author: jane, + content: "Nice.", + createdAt: "2024-03-12T11:00:00", + likes: 1, + likedByMe: false, + replyPagination: { + count: 0, + lastPage: 0, + }, + replies: [], +}); + +export const editedComment: Comment = stampModelId({ + id: "6", + author: omar, + content: "I fixed a typo in this one.", + createdAt: "2024-03-12T12:30:00", + edited: true, + likes: 3, + likedByMe: true, + replyPagination: { + count: 0, + lastPage: 0, + }, + replies: [], +}); + +export const noRepliesComment: Comment = stampModelId({ + id: "7", + author: john, + content: "Standalone comment with no thread.", + createdAt: "2024-03-12T13:00:00", + likes: 7, + replyPagination: { + count: 0, + lastPage: 0, + }, + replies: [], +}); + +export const comments: Array = [ + longComment, + shortComment, + editedComment, + noRepliesComment, +]; diff --git a/apps/modeling-commons-frontend/app/components/comment/types.ts b/apps/modeling-commons-frontend/app/components/comment/types.ts new file mode 100644 index 00000000..03344af6 --- /dev/null +++ b/apps/modeling-commons-frontend/app/components/comment/types.ts @@ -0,0 +1,63 @@ +export type CommentAuthor = { + url?: string; + name: string; + image: string; +}; + +export type CommentMetadataBarProps = { + author: CommentAuthor; + createdAt: string; + edited?: boolean; + collapsible?: boolean; + collapsed?: boolean; + hiddenReplyCount?: number; +}; + +export type CommentPagination = { + lastPage: number; + count: number; +}; + +export type CommentPermissions = { + canEdit?: boolean; + canDelete?: boolean; +}; + +export type Comment = { + id: string; + modelId?: string; + author: CommentAuthor; + /* Plaintext string */ + content: string; + createdAt: string; + edited?: boolean; + likes: number; + likedByMe?: boolean; + replies?: Array; + replyPagination?: CommentPagination; + permissions?: CommentPermissions; +}; + +export const COMMENT_TREE_DEFAULTS = { + maximumNested: 3, + maximumShownRepliesPerLevel: 2, +} as const; + +export type CommentViewSettings = { + maximumNested?: number; + maximumShownRepliesPerLevel?: number; + isNested?: boolean; + parentHasSeeMoreReplies?: boolean; + isLastSibling?: boolean; + readOnly?: boolean; +}; + +export type CommentViewProps = { + comment: Comment; + parentAuthorName?: string; +} & CommentViewSettings; + +export type CommentsPanelProps = { + comments: Array; + pagination: CommentPagination; +} & CommentViewSettings; From c6c88a33a8eeef938a3a689903388de2a6a750d1 Mon Sep 17 00:00:00 2001 From: Omar Ibrahim Date: Fri, 10 Jul 2026 16:54:22 -0500 Subject: [PATCH 02/51] test(commons): add comment component test suite --- .../app/components/comment/BUGS.md | 245 +++++++++++++++ .../components/comment/CommentActions.test.ts | 82 +++++ .../components/comment/CommentInput.test.ts | 110 +++++++ .../comment/CommentTextRepresentation.test.ts | 56 ++++ .../comment/CommentView.collapse.test.ts | 101 ++++++ .../components/comment/CommentView.test.ts | 135 ++++++++ .../comment/CommentView.thread-link.test.ts | 73 +++++ .../components/comment/CommentsPanel.test.ts | 81 +++++ .../app/components/comment/bugs.test.ts | 289 ++++++++++++++++++ .../components/comment/comment-tree.test.ts | 288 +++++++++++++++++ .../tests/helpers/comment.ts | 59 ++++ .../tests/helpers/index.ts | 1 + 12 files changed, 1520 insertions(+) create mode 100644 apps/modeling-commons-frontend/app/components/comment/BUGS.md create mode 100644 apps/modeling-commons-frontend/app/components/comment/CommentActions.test.ts create mode 100644 apps/modeling-commons-frontend/app/components/comment/CommentInput.test.ts create mode 100644 apps/modeling-commons-frontend/app/components/comment/CommentTextRepresentation.test.ts create mode 100644 apps/modeling-commons-frontend/app/components/comment/CommentView.collapse.test.ts create mode 100644 apps/modeling-commons-frontend/app/components/comment/CommentView.test.ts create mode 100644 apps/modeling-commons-frontend/app/components/comment/CommentView.thread-link.test.ts create mode 100644 apps/modeling-commons-frontend/app/components/comment/CommentsPanel.test.ts create mode 100644 apps/modeling-commons-frontend/app/components/comment/bugs.test.ts create mode 100644 apps/modeling-commons-frontend/app/components/comment/comment-tree.test.ts create mode 100644 apps/modeling-commons-frontend/tests/helpers/comment.ts diff --git a/apps/modeling-commons-frontend/app/components/comment/BUGS.md b/apps/modeling-commons-frontend/app/components/comment/BUGS.md new file mode 100644 index 00000000..6da8c67e --- /dev/null +++ b/apps/modeling-commons-frontend/app/components/comment/BUGS.md @@ -0,0 +1,245 @@ +# Nested-comments feature — bug report (historical) + +Found by a dedicated bug-hunt pass on `app/components/comment/`. All 16 bugs below have since been +fixed — every entry carries a **FIXED** line describing what closed it. The former `it.fails` pins +now live on as regular regression tests in `bugs.test.ts`; BUG numbers below match the `// BUG-` +comments in that file. + +## 1. Load-more comments button can never appear — HIGH + +- **Where:** `CommentsPanel.vue:80` +- **What happens:** `remainingComments` reads `props.count`, but no `count` prop exists — the data + lives in `props.pagination.count`. `props.count` is always `undefined`, so `remainingComments` + is always `0 - comments.length <= 0` and the "Load N more comments" button never renders + (verified against the only production usage, `pages/theme.vue`, which passes + `pagination: { count: 100 }` with 4 comments and shows no button). This is also a TS error + hiding in the pre-existing check-types noise. +- **Expected:** button shows `pagination.count - comments.length` when positive. +- **Test:** `bugs.test.ts` — "shows a load-more button when pagination.count exceeds the shown comments" +- **Fix direction:** use `props.pagination.count` (and clamp at 0). +- **FIXED (2026-07-10):** `remainingCommentCount` in `comment-tree.ts` now takes `(comments, pagination)` and returns `max(0, pagination.count - comments.length)`; the panel passes `props.pagination`. The quirk-pinning test in `comment-tree.test.ts` was rewritten to the corrected semantics. + +## 2. Delete-confirm dialog gets stuck open and permanently loading — HIGH + +- **Where:** `CommentsPanel.vue:53-64` +- **What happens:** `handleDelete` sets `deleting = true` and never resets it, and never sets + `deleteOpen = false`. After confirming a delete, the modal stays open with a spinning, disabled + Delete button forever. If the user escapes via Cancel/ESC, `deleting` is still `true`, so every + future delete dialog opens already-spinning and unusable. +- **Expected:** on confirm, close the dialog and reset `deleting` (in a `finally`). +- **Test:** `bugs.test.ts` — "closes the delete dialog and resets the loading state after confirm" +- **Fix direction:** `deleteOpen.value = false; deleting.value = false` (finally-block) in `handleDelete`. +- **FIXED (2026-07-10):** `handleDelete` closes the dialog and resets `deleting` in a `finally`, and also clears `deleteTarget` after a confirmed delete. + +## 3. Panel's `readOnly` prop is silently ignored — MEDIUM + +- **Where:** `CommentsPanel.vue:48` (vs. the `readOnly` declared via `CommentsPanelProps & CommentViewSettings`) +- **What happens:** the local `const readOnly = computed(() => !user.value.isLoggedIn)` shadows the + declared `readOnly` prop in the template. A consumer passing `read-only` (e.g. archived/locked + thread) still gets a composer and interactive comments for logged-in users. +- **Expected:** effective read-only = `props.readOnly || !isLoggedIn`. +- **Test:** `bugs.test.ts` — "respects an explicit readOnly prop even when the user is logged in" +- **Fix direction:** rename the computed and OR in the prop; also stop relying on prop shadowing. +- **FIXED (2026-07-10):** the local computed is renamed `isReadOnly` and computes `props.readOnly || !user.isLoggedIn`, so consumers can force a read-only panel for logged-in users. + +## 4. Panel emits nothing — all interactions are silently discarded — MEDIUM + +- **Where:** `CommentsPanel.vue:9-21` (and `defineEmits` absent) +- **What happens:** the panel listens only to `@delete`/`@write` from `CommentView` and declares no + emits of its own. `reply`, `edit`, `like`, `unlike`, and `load` events from the comment tree die + at the panel; the top-level `` has no `@submit`/`@cancel` handlers at all, so a + posted top-level comment is discarded while `CommentInput` clears the textarea (user-visible data + loss). `handleDelete`/`loadMoreComments` are TODO stubs and `deleteTarget` is write-only. +- **Expected:** the panel (or a future container per task #4) forwards/handles every interaction. +- **Test:** report-only (pending backend/container design; would pin the wrong contract now). +- **Fix direction:** wire `@submit` on the composer and forward tree events (or handle them) when + the data layer lands; make `handleDelete` use `deleteTarget`. +- **Addendum (boundary refactor):** the panel now re-emits `create`/`reply`/`edit`/`like`/`unlike`/ + `load`/`load-more` and emits `delete` (with `deleteTarget`) on confirm — but nothing consumes them + yet, so there is still no backend call or optimistic UI and interactions have no persistent effect. +- **FIXED (2026-07-10):** closed by container consumption — `CommentsSection` consumes every panel + emit (`create`/`reply`/`edit`/`like`/`unlike`/`delete`/`load`/`load-more`) with optimistic tree + mutations, and the panel's `handleDelete` emits `delete` with the stored `deleteTarget`. + `load`/`load-more` remain inert seams pending the backend. + +## 5. `maximumShownRepliesPerLevel` is not propagated to nested levels — MEDIUM + +- **Where:** `CommentView.vue:54-70` +- **What happens:** the recursive `` passes `maximum-nested`, `is-nested`, etc., but + not `maximum-shown-replies-per-level`. Every level below the first reverts to the component + default (5). With the panel's default of 2, one tree mixes both limits. +- **Expected:** the configured value applies at every depth. +- **Test:** `bugs.test.ts` — "propagates maximumShownRepliesPerLevel to nested reply levels" +- **Fix direction:** add `:maximum-shown-replies-per-level="maximumShownRepliesPerLevel"` to the + recursive call (see also bug 16 on default drift). +- **FIXED (2026-07-10):** the recursive `` now binds `:maximum-shown-replies-per-level`, so the configured value applies at every depth. + +## 6. Closing an input emits a spurious `write` event — LOW + +- **Where:** `CommentView.vue:142-153` (also 155-163) +- **What happens:** `toggleReplyInput`/`toggleEditMode` emit `write` unconditionally, so cancelling + a reply/edit — and the internal toggle call inside `submitCommentEdit`/`submitCommentReply` — + fires a second `write` that is not a write attempt. Harmless today (the panel only toasts when + logged out, and inputs never open while logged out), but any consumer counting/gating on `write` + will misbehave, and each submit double-emits it. +- **Expected:** `write` fires only on actual write attempts (opening an input, like, delete). +- **Test:** `bugs.test.ts` — "does not emit write when the reply input is closed via cancel" +- **Fix direction:** emit `write` only when opening/acting, not when closing; don't route submit + through the toggle helpers. +- **FIXED (2026-07-10):** the toggles emit `write` only on the open transition; cancel and submit go through dedicated `closeReplyInput`/`closeEditMode` helpers that emit nothing. + +## 7. Open input becomes unclosable if `readOnly` flips while open — LOW + +- **Where:** `CommentView.vue:142-153` +- **What happens:** the toggles `return` early when `props.readOnly` is true — before flipping the + ref. If `readOnly` becomes true while a reply/edit input is open (e.g. session expiry refreshes + auth state), Cancel and Submit can no longer close the input; every attempt just re-emits `write` + (login toast) with the input stuck open. +- **Expected:** closing is always allowed; only opening is gated. +- **Test:** `bugs.test.ts` — "still allows closing an open reply input after readOnly becomes true" +- **Fix direction:** gate only the open transition (`if (readOnly && !isReplyInputVisible) return`), + or split open/close functions. +- **FIXED (2026-07-10):** `readOnly` now gates only the open transition; the close path (toggle-while-open, cancel, submit) always works. + +## 8. See-more at the nesting limit is a dead end — MEDIUM + +- **Where:** `CommentView.vue:134-139` with the see-more block at 74-88 +- **What happens:** when `maximumNested <= 0`, `hasVisibleReplies` is permanently false, yet + `remainingReplies` is the full `replyPagination.count`, so "See N more replies" renders and + clicking emits `load`. Even if a future handler loads the replies, they can never be displayed at + that depth — the button can never do anything visible. Related smell at visible depths: + `remainingReplies = count - shownReplies.length` counts locally-loaded-but-hidden replies + (hidden by `maximumShownRepliesPerLevel`) as needing a `load` fetch; a naive page-append handler + would duplicate them. (The lead's other claim — `maximumNested - 1` decrementing below 0 — was + cleared: children are not rendered at `maximumNested <= 0`, so negative values never occur.) +- **Expected:** at the limit, see-more should navigate to a thread view or expand in place + (pending task #5); the `load` emit should mean "fetch what isn't loaded", not "reveal". +- **Test:** report-only (correct behavior is a pending design decision). +- **Fix direction:** distinct affordance at the nesting limit; separate "reveal hidden loaded + replies" from "load more from server". +- **Addendum (continue-thread):** the dead end is superseded — at the limit `CommentView` now + renders a "Continue this thread (N replies)" link to `/comments/` instead of + `CommentSeeMore`, so the `load`-emit-at-limit no longer occurs. Remaining: the `/comments/[id]` + thread page is a stub pending implementation, and the visible-depth smell above (counting + locally-loaded-but-hidden replies as needing a `load` fetch) still stands. +- **FIXED (2026-07-10):** residual reveal-vs-load — `CommentView` keeps a per-node `revealedCount` ref (seeded from `maximumShownRepliesPerLevel`); see-more first reveals all locally loaded replies, then shows only the clamped server remainder (`hiddenLoadedReplyCount`/`serverRemainingReplyCount` in `comment-tree.ts`) and only then emits `load`; the nesting-limit branch still reports the full count for the continue-thread link. + +## 9. `CommentSeeMore`'s declared emit is dead API; wiring relies on attr fallthrough — LOW + +- **Where:** `CommentSeeMore.vue:21-23` vs `CommentView.vue:84-87` +- **What happens:** `CommentSeeMore` declares and emits `see-more-replies`, but `CommentView` + listens for `@click`. Because `click` is not a declared emit, the listener falls through as an + attribute to the root `UButton`, so clicking happens to work (proved by the passing companion + test) — but the component's declared API does nothing, and the wiring breaks the moment someone + adds `click` to `defineEmits` or a wrapper element. +- **Expected:** parent consumes the declared event. +- **Test:** `bugs.test.ts` — "emits load when CommentSeeMore fires its declared see-more-replies event" + (+ passing companion "see-more button click reaches the parent only via attribute fallthrough"). + Note: the pinned expectation is `@see-more-replies` → `load`; if the fix instead deletes the + custom emit and standardizes on `click`, retire this test alongside. +- **Fix direction:** `@see-more-replies="emit('load', ...)"` in CommentView (or drop the custom emit). +- **FIXED (2026-07-10):** `CommentView` now listens to the declared `@see-more-replies` event (routed through the bug-8 reveal-then-load handler); the attribute-fallthrough companion test was retired with the `@click` wiring it proved. + +## 10. Dismissing the delete dialog via ESC/overlay skips `cancel` — LOW + +- **Where:** `ConfirmDeleteCommentDialog.vue:36-42` +- **What happens:** only the Cancel button emits `cancel`; when `UModal` closes itself (ESC, + overlay click), the `open` model just flips to false. The panel's `@cancel="cleanupDeleteEvent"` + never runs, so `deleteTarget` stays stale. Low impact today (it is overwritten on the next + `confirmDelete` and currently unread — see bug 4), but the cleanup contract is broken. +- **Expected:** any dismissal path other than confirm behaves as cancel. +- **Test:** `bugs.test.ts` — "emits cancel when the modal is dismissed via ESC/overlay" +- **Fix direction:** watch the model for `true → false` transitions (or `@update:open`) and emit + `cancel` when not confirming. +- **FIXED (2026-07-10):** the dialog watches its `open` model and emits `cancel` on any `true → false` transition that is not confirm-driven; a `confirming` flag set by the Delete button suppresses a spurious cancel on confirm-close, and the Cancel button now just closes and lets the watcher emit. + +## 11. Enter always submits — a newline is impossible to type — MEDIUM + +- **Where:** `CommentInput.vue:28` +- **What happens:** `@keydown.enter.prevent="handleSubmit"` fires for every Enter, including + Shift+Enter (no `.exact`/modifier check), so multi-line comments cannot be typed at all despite + the control being a textarea with autoresize. +- **Expected:** Enter submits, Shift+Enter inserts a newline (or the reverse — but one path must + produce a newline). +- **Test:** `bugs.test.ts` — "inserts a newline instead of submitting on Shift+Enter" +- **Fix direction:** guard the handler: `if (event.shiftKey) return` before `preventDefault`, e.g. + a method receiving the event or `@keydown.enter.exact.prevent`. +- **FIXED (2026-07-10):** `@keydown.enter.exact.prevent` — plain Enter submits; Shift+Enter (or any modified Enter) skips the handler and its `preventDefault`, so the textarea inserts a newline and `autoresize` grows it. + +## 12. Input clears optimistically on submit — text lost if the parent rejects — LOW + +- **Where:** `CommentInput.vue:108-112` +- **What happens:** `handleSubmit` emits and immediately sets `comment.value = ""`. Once a backend + exists, any failed/rejected submission loses the user's text with no recovery. +- **Expected:** clear only after the parent accepts (async callback/prop) or restore on failure. +- **Test:** report-only (correct contract depends on the future async submit API). +- **Fix direction:** let the parent own clearing (e.g. `submit` handler returns a promise, or a + `clear()` expose). +- **FIXED (2026-07-10):** `CommentInput` no longer clears itself on submit; it `defineExpose`s `clear()` and parents own clearing. The panel's top-level composer calls `clear()` via a template ref after emitting `create` — acceptance is synchronous-optimistic today; restore-on-failure lands with the backend at the `CommentsSection` `runOptimistic` seam. `CommentView`'s reply/edit inputs unmount on close (`v-if`) and re-seed from `initialText` on reopen, so they need no `clear()` call. + +## 13. "Edit your reply to X" placeholder branch is unreachable — LOW + +- **Where:** `CommentInput.vue:96-99` vs the edit usage in `CommentView.vue:19-26` +- **What happens:** the placeholder branch for `isEditing && target` exists, but `CommentView` + never passes `:target` together with `is-editing`, and no other call site does. Dead branch in + production; only the isolated component test exercises it. +- **Expected:** either pass `:target="author.name"`-equivalent when editing a reply, or drop the branch. +- **Test:** report-only (which side is wrong is a product decision). +- **Fix direction:** pass the replied-to author when editing nested replies, or delete the branch. +- **FIXED (2026-07-10):** the recursive `CommentView` passes `:parent-author-name="comment.author.name"` down (new optional `parentAuthorName` prop) and the edit input receives `:target="parentAuthorName"`, so editing a nested reply shows "Edit your reply to X" while top-level edits keep the plain placeholder. The reply input's `:target` (own author) is unchanged. + +## 14. `CommentActions.replyCount` is a dead prop — LOW + +- **Where:** `CommentActions.vue:40` (fed from `CommentView.vue:33`) +- **What happens:** `CommentView` passes `:reply-count="replyPagination.count"` and the prop is + declared, but the template/computed never use it — the Reply button shows no count. +- **Expected:** show the count next to Reply, or remove the prop. +- **Test:** report-only (dead API, no behavior to pin). +- **Fix direction:** render it (`Reply · {{ replyCount }}`) or delete prop + binding. +- **FIXED (2026-07-10):** the Reply button now renders `Reply · N` when the count is positive (plain `Reply` at zero), mirroring how the Like button renders `likes`. + +## 15. URLs with balanced parentheses get their closing paren stripped — LOW + +- **Where:** `CommentTextRepresentation.vue:8-11` (`splitTrailingPunct`) +- **What happens:** the trailing-punctuation stripper unconditionally removes `)` (and friends) + from the end of a URL, so `https://en.wikipedia.org/wiki/Rust_(programming_language)` links to + `.../Rust_(programming_language` — a broken page. Stripping is only correct when the paren is + unbalanced (the `(see https://example.com).` case the baseline test covers). +- **Expected:** keep trailing `)`/`]` when balanced by an opener inside the URL. +- **Test:** `bugs.test.ts` — "keeps the closing paren of a URL with balanced parentheses" +- **Fix direction:** standard linkifier heuristic — count openers/closers in the URL and re-attach + closers up to balance. +- **FIXED (2026-07-10):** `splitTrailingPunct` re-attaches trailing `)`/`]`/`}` closers while the URL contains more matching openers than closers; unbalanced closers are still stripped, keeping the baseline `(see https://example.com).` behavior. + +## 16. Default settings drift: panel (3 / 2) vs view (4 / 5) — LOW + +- **Where:** `CommentsPanel.vue:38-43` vs `CommentView.vue:96-110` +- **What happens:** `maximumNested`/`maximumShownRepliesPerLevel` default to 3/2 on the panel but + 4/5 on the view. A standalone `CommentView` behaves differently from a panel-hosted one, and + combined with bug 5 a single panel-hosted tree currently mixes 2 (top level) with 5 (below). +- **Expected:** one source of truth for defaults. +- **Test:** report-only (fold into the bug-5 fix). +- **Fix direction:** export shared default constants from `types.ts` and use them in both components. +- **FIXED (2026-07-10):** `COMMENT_TREE_DEFAULTS` (3 / 2 — the panel's values) exported from `types.ts` and used by both `CommentsPanel` and `CommentView` `withDefaults`. + +--- + +## Leads investigated and CLEARED (not bugs) + +- **`maximumNested - 1` going below 0** — children are only rendered when `maximumNested > 0`, so + a value below 0 is never instantiated; no functional effect. +- **Recursive `v-bind="reply"` prop leakage** — `Comment` keys don't overlap `CommentViewSettings` + keys, and the explicit bindings after `v-bind` take precedence; no clash between a reply's own + `replyPagination`/`permissions` and panel-level settings. +- **`remainingReplies` double-count at visible depths** — `count - shownReplies.length` is + arithmetically consistent with fixtures where `replyPagination.count` equals total replies + (the label "See N more replies" matches what is hidden); the residual `load`-semantics concern + is captured under bug 8. +- **Edit mode showing stale content after a prop update** — the edit `CommentInput` is `v-if`- + mounted per editing session, so it re-reads `content` each time it opens; while open, ignoring + external content changes is acceptable editing behavior. +- **`formatRelativeDate` edge cases** — future dates (all diffs negative) fall through every + `> 0` check and return "just now", which degrades gracefully; boundaries are already covered by + `app/utils/formatters.test.ts`. +- **Double-`write` on the panel toast path for logged-out users** — inputs never open while + read-only, so the submit/cancel double-emit (bug 6) cannot cause double toasts today. diff --git a/apps/modeling-commons-frontend/app/components/comment/CommentActions.test.ts b/apps/modeling-commons-frontend/app/components/comment/CommentActions.test.ts new file mode 100644 index 00000000..7ba74f1c --- /dev/null +++ b/apps/modeling-commons-frontend/app/components/comment/CommentActions.test.ts @@ -0,0 +1,82 @@ +import { mountSuspended } from "@nuxt/test-utils/runtime"; +import { describe, expect, it } from "vitest"; +import { UDropdownMenu } from "#components"; +import type { DropdownMenuItem } from "#ui/types"; +import CommentActions from "./CommentActions.vue"; + +type DropdownConstructor = new () => { $props: { items: Array } }; +const DropdownMenu = UDropdownMenu as unknown as DropdownConstructor; + +describe("CommentActions", () => { + it("shows the like count", async () => { + const wrapper = await mountSuspended(CommentActions, { props: { likes: 24 } }); + expect(wrapper.find("[title='Like']").text()).toContain("24"); + }); + + it("shows the reply count on the reply button when positive", async () => { + const wrapper = await mountSuspended(CommentActions, { + props: { likes: 0, replyCount: 3 }, + }); + expect(wrapper.find("[title='Reply']").text()).toBe("Reply · 3"); + }); + + it("shows a bare reply label when there are no replies", async () => { + const wrapper = await mountSuspended(CommentActions, { + props: { likes: 0, replyCount: 0 }, + }); + expect(wrapper.find("[title='Reply']").text()).toBe("Reply"); + }); + + it("emits like and reply when the buttons are clicked", async () => { + const wrapper = await mountSuspended(CommentActions, { props: { likes: 0 } }); + await wrapper.find("[title='Like']").trigger("click"); + await wrapper.find("[title='Reply']").trigger("click"); + + expect(wrapper.emitted("like")).toHaveLength(1); + expect(wrapper.emitted("reply")).toHaveLength(1); + }); + + it("highlights the like button when liked by me", async () => { + const wrapper = await mountSuspended(CommentActions, { + props: { likes: 1, likedByMe: true }, + }); + const liked = wrapper.find("[title='Like']").classes(); + + const unlikedWrapper = await mountSuspended(CommentActions, { + props: { likes: 1, likedByMe: false }, + }); + const unliked = unlikedWrapper.find("[title='Like']").classes(); + + expect(liked.join(" ")).toMatch(/text-red-\d+/); + expect(liked).not.toEqual(unliked); + }); + + it("hides the actions dropdown without permissions", async () => { + const wrapper = await mountSuspended(CommentActions, { props: { likes: 0 } }); + expect(wrapper.find("[title='More actions']").exists()).toBe(false); + }); + + it("exposes edit and delete actions when permitted", async () => { + const wrapper = await mountSuspended(CommentActions, { + props: { likes: 0, canEdit: true, canDelete: true }, + }); + + const items = wrapper.findComponent(DropdownMenu).props("items"); + expect(items.map((item) => item.label)).toEqual(["Edit", "Delete"]); + + items[0]!.onSelect?.(new Event("select")); + items[1]!.onSelect?.(new Event("select")); + + expect(wrapper.emitted("edit")).toHaveLength(1); + expect(wrapper.emitted("delete")).toHaveLength(1); + }); + + it("only exposes the delete action without edit permission", async () => { + const wrapper = await mountSuspended(CommentActions, { + props: { likes: 0, canDelete: true }, + }); + + const items = wrapper.findComponent(DropdownMenu).props("items"); + expect(items.map((item) => item.label)).toEqual(["Delete"]); + }); +}); diff --git a/apps/modeling-commons-frontend/app/components/comment/CommentInput.test.ts b/apps/modeling-commons-frontend/app/components/comment/CommentInput.test.ts new file mode 100644 index 00000000..95fe4178 --- /dev/null +++ b/apps/modeling-commons-frontend/app/components/comment/CommentInput.test.ts @@ -0,0 +1,110 @@ +import { mockNuxtImport, mountSuspended } from "@nuxt/test-utils/runtime"; +import { describe, expect, it } from "vitest"; +import { defineComponent, h, ref } from "vue"; +import CommentInput from "./CommentInput.vue"; +import { useProfileMock, useUserMock } from "~~/tests/helpers"; + +mockNuxtImport("useUser", () => () => useUserMock()); +mockNuxtImport("useProfile", () => () => useProfileMock()); + +type CommentInputProps = { + id?: string; + target?: string; + initialText?: string; + isEditing?: boolean; +}; + +async function mountInput(props: CommentInputProps = {}) { + return mountSuspended(CommentInput, { props }); +} + +function textareaValue(wrapper: Awaited>) { + return (wrapper.find("textarea").element as HTMLTextAreaElement).value; +} + +describe("CommentInput placeholder", () => { + it("defaults to leaving a comment", async () => { + const wrapper = await mountInput(); + expect(wrapper.find("textarea").attributes("placeholder")).toBe("Leave a comment..."); + }); + + it("targets the comment author when replying", async () => { + const wrapper = await mountInput({ target: "Jane Doe" }); + expect(wrapper.find("textarea").attributes("placeholder")).toBe("Reply to Jane Doe..."); + }); + + it("switches to editing copy", async () => { + const wrapper = await mountInput({ isEditing: true }); + expect(wrapper.find("textarea").attributes("placeholder")).toBe("Edit your comment..."); + }); + + it("combines editing and reply-target copy", async () => { + const wrapper = await mountInput({ isEditing: true, target: "Jane Doe" }); + expect(wrapper.find("textarea").attributes("placeholder")).toBe( + "Edit your reply to Jane Doe...", + ); + }); +}); + +describe("CommentInput submission", () => { + it("emits submit with the typed text and keeps it until the parent clears", async () => { + const wrapper = await mountInput(); + await wrapper.find("textarea").setValue("Hello there"); + await wrapper.find("[title='Post']").trigger("click"); + + expect(wrapper.emitted("submit")).toEqual([["Hello there"]]); + expect(textareaValue(wrapper)).toBe("Hello there"); + }); + + it("empties the textarea via the exposed clear() method", async () => { + const ClearHarness = defineComponent({ + setup() { + const input = ref<{ clear: () => void } | null>(null); + return () => + h("div", [ + h(CommentInput, { ref: input }), + h("button", { + "data-testid": "external-clear", + onClick: () => input.value?.clear(), + }), + ]); + }, + }); + + const wrapper = await mountSuspended(ClearHarness); + await wrapper.find("textarea").setValue("Hello there"); + + await wrapper.find("[data-testid='external-clear']").trigger("click"); + + expect((wrapper.find("textarea").element as HTMLTextAreaElement).value).toBe(""); + }); + + it("submits on enter", async () => { + const wrapper = await mountInput(); + const textarea = wrapper.find("textarea"); + await textarea.setValue("Enter submit"); + await textarea.trigger("keydown", { key: "Enter" }); + + expect(wrapper.emitted("submit")).toEqual([["Enter submit"]]); + }); + + it("ignores whitespace-only submissions", async () => { + const wrapper = await mountInput(); + await wrapper.find("textarea").setValue(" "); + await wrapper.find("[title='Post']").trigger("click"); + + expect(wrapper.emitted("submit")).toBeUndefined(); + }); + + it("seeds the textarea from initialText", async () => { + const wrapper = await mountInput({ initialText: "Original text" }); + expect(textareaValue(wrapper)).toBe("Original text"); + }); + + it("emits cancel when the cancel button is clicked", async () => { + const wrapper = await mountInput(); + await wrapper.find("[title='Cancel']").trigger("click"); + + expect(wrapper.emitted("cancel")).toEqual([[]]); + }); +}); diff --git a/apps/modeling-commons-frontend/app/components/comment/CommentTextRepresentation.test.ts b/apps/modeling-commons-frontend/app/components/comment/CommentTextRepresentation.test.ts new file mode 100644 index 00000000..d17375aa --- /dev/null +++ b/apps/modeling-commons-frontend/app/components/comment/CommentTextRepresentation.test.ts @@ -0,0 +1,56 @@ +import { mountSuspended } from "@nuxt/test-utils/runtime"; +import { describe, expect, it } from "vitest"; +import CommentTextRepresentation from "./CommentTextRepresentation.vue"; + +async function mountText(text: string) { + return mountSuspended(CommentTextRepresentation, { props: { text } }); +} + +describe("CommentTextRepresentation", () => { + it("renders plain text without anchors", async () => { + const wrapper = await mountText("Just a regular comment."); + expect(wrapper.text()).toBe("Just a regular comment."); + expect(wrapper.find("a").exists()).toBe(false); + }); + + it("linkifies https URLs with safe anchor attributes", async () => { + const wrapper = await mountText("Check https://example.com/docs for details"); + + const link = wrapper.find("a"); + expect(link.exists()).toBe(true); + expect(link.attributes("href")).toBe("https://example.com/docs"); + expect(link.attributes("target")).toBe("_blank"); + expect(link.attributes("rel")).toBe("noopener noreferrer nofollow"); + expect(link.text()).toBe("https://example.com/docs"); + expect(wrapper.text()).toContain("Check"); + expect(wrapper.text()).toContain("for details"); + }); + + it("linkifies http URLs", async () => { + const wrapper = await mountText("Legacy link http://example.com here"); + expect(wrapper.find("a").attributes("href")).toBe("http://example.com"); + }); + + it("excludes trailing punctuation from the link", async () => { + const wrapper = await mountText("(see https://example.com/docs)."); + + const link = wrapper.find("a"); + expect(link.attributes("href")).toBe("https://example.com/docs"); + expect(link.text()).toBe("https://example.com/docs"); + expect(wrapper.text()).toContain(")."); + }); + + it("does not linkify non-http(s) schemes", async () => { + const wrapper = await mountText("Try ftp://example.com/file or javascript:alert(1) instead"); + expect(wrapper.find("a").exists()).toBe(false); + }); + + it("linkifies multiple URLs independently", async () => { + const wrapper = await mountText("First https://a.example.com then https://b.example.com done"); + + const links = wrapper.findAll("a"); + expect(links).toHaveLength(2); + expect(links[0]!.attributes("href")).toBe("https://a.example.com"); + expect(links[1]!.attributes("href")).toBe("https://b.example.com"); + }); +}); diff --git a/apps/modeling-commons-frontend/app/components/comment/CommentView.collapse.test.ts b/apps/modeling-commons-frontend/app/components/comment/CommentView.collapse.test.ts new file mode 100644 index 00000000..1d517988 --- /dev/null +++ b/apps/modeling-commons-frontend/app/components/comment/CommentView.collapse.test.ts @@ -0,0 +1,101 @@ +import { mockNuxtImport } from "@nuxt/test-utils/runtime"; +import { beforeEach, describe, expect, it } from "vitest"; +import CommentActions from "./CommentActions.vue"; +import CommentView from "./CommentView.vue"; +import { deepThread, shortComment } from "./fixtures"; +import { + mountCommentView, + resetCommentMocks, + setLoggedIn, + useProfileMock, + useUserMock, +} from "~~/tests/helpers"; + +mockNuxtImport("useUser", () => () => useUserMock()); +mockNuxtImport("useProfile", () => () => useProfileMock()); + +beforeEach(() => { + resetCommentMocks(); +}); + +describe("CommentView collapsing", () => { + it("hides the body, actions, and replies and shows a hidden-replies summary", async () => { + const wrapper = await mountCommentView(deepThread); + expect(wrapper.text()).toContain("Hot take"); + expect(wrapper.text()).toContain("You're wrong and you know it."); + + await wrapper.find("button[aria-expanded]").trigger("click"); + + expect(wrapper.text()).not.toContain("Hot take"); + expect(wrapper.text()).not.toContain("You're wrong and you know it."); + expect(wrapper.findComponent(CommentActions).exists()).toBe(false); + expect(wrapper.text()).toContain("4 replies hidden"); + expect(wrapper.text()).toContain(deepThread.author.name); + }); + + it("shows a plain collapsed marker when there are no replies", async () => { + const wrapper = await mountCommentView(shortComment); + + await wrapper.find("button[aria-expanded]").trigger("click"); + + expect(wrapper.text()).not.toContain("Nice."); + expect(wrapper.text()).toContain("collapsed"); + expect(wrapper.text()).not.toContain("hidden"); + }); + + it("restores the body, actions, and replies on expand", async () => { + const wrapper = await mountCommentView(deepThread); + const toggle = wrapper.find("button[aria-expanded]"); + + await toggle.trigger("click"); + await toggle.trigger("click"); + + expect(wrapper.text()).toContain("Hot take"); + expect(wrapper.text()).toContain("You're wrong and you know it."); + expect(wrapper.findComponent(CommentActions).exists()).toBe(true); + expect(wrapper.text()).not.toContain("replies hidden"); + }); + + it("collapses only the toggled subtree for nested replies", async () => { + const wrapper = await mountCommentView(deepThread); + const nested = wrapper + .findAllComponents(CommentView) + .find((view) => view.props("comment")?.id === "101"); + expect(nested).toBeDefined(); + + await nested!.find("button[aria-expanded]").trigger("click"); + + expect(wrapper.text()).toContain("Hot take"); + expect(wrapper.text()).not.toContain("You're wrong and you know it."); + expect(wrapper.text()).not.toContain("Explain yourself."); + expect(wrapper.text()).toContain("My editor converts tabs to spaces"); + expect(wrapper.text()).toContain("3 replies hidden"); + }); + + it("collapses and expands when read-only and logged out without emitting write", async () => { + setLoggedIn(false); + const wrapper = await mountCommentView(deepThread, { readOnly: true }); + const toggle = wrapper.find("button[aria-expanded]"); + + await toggle.trigger("click"); + expect(wrapper.text()).not.toContain("Hot take"); + + await toggle.trigger("click"); + expect(wrapper.text()).toContain("Hot take"); + expect(wrapper.emitted("write")).toBeUndefined(); + }); + + it("flips aria-expanded and the accessible label with the collapse state", async () => { + const wrapper = await mountCommentView(shortComment); + const toggle = wrapper.find("button[aria-expanded]"); + expect(toggle.attributes("aria-expanded")).toBe("true"); + expect(toggle.attributes("aria-label")).toBe("Collapse comment"); + + await toggle.trigger("click"); + expect(toggle.attributes("aria-expanded")).toBe("false"); + expect(toggle.attributes("aria-label")).toBe("Expand comment"); + + await toggle.trigger("click"); + expect(toggle.attributes("aria-expanded")).toBe("true"); + }); +}); diff --git a/apps/modeling-commons-frontend/app/components/comment/CommentView.test.ts b/apps/modeling-commons-frontend/app/components/comment/CommentView.test.ts new file mode 100644 index 00000000..d9051a00 --- /dev/null +++ b/apps/modeling-commons-frontend/app/components/comment/CommentView.test.ts @@ -0,0 +1,135 @@ +import { mockNuxtImport } from "@nuxt/test-utils/runtime"; +import { describe, expect, it } from "vitest"; +import { nextTick } from "vue"; +import CommentActions from "./CommentActions.vue"; +import CommentInput from "./CommentInput.vue"; +import CommentView from "./CommentView.vue"; +import { editedComment, longComment, shortComment } from "./fixtures"; +import { mountCommentView, useProfileMock, useUserMock } from "~~/tests/helpers"; + +mockNuxtImport("useUser", () => () => useUserMock()); +mockNuxtImport("useProfile", () => () => useProfileMock()); + +describe("CommentView rendering", () => { + it("renders the author name, content, and like count", async () => { + const wrapper = await mountCommentView(shortComment); + expect(wrapper.text()).toContain(shortComment.author.name); + expect(wrapper.text()).toContain(shortComment.content); + expect(wrapper.findComponent(CommentActions).props("likes")).toBe(shortComment.likes); + }); + + it("marks edited comments", async () => { + const wrapper = await mountCommentView(editedComment); + expect(wrapper.text()).toContain("(edited)"); + }); + + it("renders nested replies", async () => { + const wrapper = await mountCommentView(longComment); + expect(wrapper.text()).toContain("This is a reply to the comment."); + expect(wrapper.text()).toContain("And this is a nested reply."); + expect(wrapper.text()).toContain("Short one."); + }); +}); + +describe("CommentView interactions", () => { + it("emits like with its own id when not yet liked", async () => { + const wrapper = await mountCommentView(shortComment); + wrapper.findComponent(CommentActions).vm.$emit("like"); + + expect(wrapper.emitted("like")).toEqual([[{ commentId: shortComment.id }]]); + expect(wrapper.emitted("unlike")).toBeUndefined(); + }); + + it("emits unlike when already liked by me", async () => { + const wrapper = await mountCommentView(editedComment); + wrapper.findComponent(CommentActions).vm.$emit("like"); + + expect(wrapper.emitted("unlike")).toEqual([[{ commentId: editedComment.id }]]); + expect(wrapper.emitted("like")).toBeUndefined(); + }); + + it("emits delete with its own id", async () => { + const wrapper = await mountCommentView(shortComment); + wrapper.findComponent(CommentActions).vm.$emit("delete"); + + expect(wrapper.emitted("delete")).toEqual([[{ commentId: shortComment.id }]]); + }); + + it("opens a reply input targeting the author and emits reply on submit", async () => { + const wrapper = await mountCommentView(shortComment); + expect(wrapper.findComponent(CommentInput).exists()).toBe(false); + + wrapper.findComponent(CommentActions).vm.$emit("reply"); + await nextTick(); + + const input = wrapper.findComponent(CommentInput); + expect(input.exists()).toBe(true); + expect(input.props("target")).toBe(shortComment.author.name); + + input.vm.$emit("submit", "A brand new reply"); + await nextTick(); + + expect(wrapper.emitted("reply")).toEqual([ + [{ commentId: shortComment.id, content: "A brand new reply" }], + ]); + expect(wrapper.findComponent(CommentInput).exists()).toBe(false); + }); + + it("opens an edit input seeded with the content and emits edit on submit", async () => { + const wrapper = await mountCommentView(editedComment); + + wrapper.findComponent(CommentActions).vm.$emit("edit"); + await nextTick(); + + const input = wrapper.findComponent(CommentInput); + expect(input.exists()).toBe(true); + expect(input.props("initialText")).toBe(editedComment.content); + expect(input.props("isEditing")).toBe(true); + + input.vm.$emit("submit", "Updated content"); + await nextTick(); + + expect(wrapper.emitted("edit")).toEqual([ + [{ commentId: editedComment.id, content: "Updated content" }], + ]); + expect(wrapper.findComponent(CommentInput).exists()).toBe(false); + }); + + it("bubbles events from nested replies with the reply's payload", async () => { + const wrapper = await mountCommentView(longComment); + const nested = wrapper + .findAllComponents(CommentView) + .find((view) => view.props("comment")?.id === "3"); + expect(nested).toBeDefined(); + + nested!.vm.$emit("like", { commentId: "3" }); + expect(wrapper.emitted("like")).toEqual([[{ commentId: "3" }]]); + + nested!.vm.$emit("delete", { commentId: "3" }); + expect(wrapper.emitted("delete")).toEqual([[{ commentId: "3" }]]); + }); +}); + +describe("CommentView read-only", () => { + it("emits write instead of opening the reply input", async () => { + const wrapper = await mountCommentView(shortComment, { readOnly: true }); + + wrapper.findComponent(CommentActions).vm.$emit("reply"); + await nextTick(); + + expect(wrapper.emitted("write")).toHaveLength(1); + expect(wrapper.findComponent(CommentInput).exists()).toBe(false); + }); + + it("emits write instead of like and delete", async () => { + const wrapper = await mountCommentView(shortComment, { readOnly: true }); + const actions = wrapper.findComponent(CommentActions); + + actions.vm.$emit("like"); + actions.vm.$emit("delete"); + + expect(wrapper.emitted("like")).toBeUndefined(); + expect(wrapper.emitted("delete")).toBeUndefined(); + expect(wrapper.emitted("write")).toHaveLength(2); + }); +}); diff --git a/apps/modeling-commons-frontend/app/components/comment/CommentView.thread-link.test.ts b/apps/modeling-commons-frontend/app/components/comment/CommentView.thread-link.test.ts new file mode 100644 index 00000000..826c1aad --- /dev/null +++ b/apps/modeling-commons-frontend/app/components/comment/CommentView.thread-link.test.ts @@ -0,0 +1,73 @@ +import { mockNuxtImport } from "@nuxt/test-utils/runtime"; +import { beforeEach, describe, expect, it } from "vitest"; +import CommentSeeMore from "./CommentSeeMore.vue"; +import { DEMO_MODEL_ID, deepThread, longComment, shortComment } from "./fixtures"; +import type { Comment } from "./types"; +import { mountCommentView, resetCommentMocks, useProfileMock, useUserMock } from "~~/tests/helpers"; + +mockNuxtImport("useUser", () => () => useUserMock()); +mockNuxtImport("useProfile", () => () => useProfileMock()); + +beforeEach(() => { + resetCommentMocks(); +}); + +const threadHref = (comment: Comment) => `/models/${comment.modelId}/comments/${comment.id}`; + +const withoutModelId = (comment: Comment): Comment => ({ + ...comment, + modelId: undefined, + replies: comment.replies?.map(withoutModelId), +}); + +describe("continue-thread link at the nesting limit", () => { + it("renders a model-scoped thread link instead of the see-more button", async () => { + const wrapper = await mountCommentView(deepThread, { maximumNested: 0 }); + + const link = wrapper.find(`a[href="${threadHref(deepThread)}"]`); + expect(link.exists()).toBe(true); + expect(link.text()).toContain("Continue this thread"); + expect(link.text()).toContain("4 replies"); + expect(wrapper.findComponent(CommentSeeMore).exists()).toBe(false); + }); + + it("does not emit load when the thread link is clicked", async () => { + const wrapper = await mountCommentView(deepThread, { maximumNested: 0 }); + + await wrapper.find(`a[href="${threadHref(deepThread)}"]`).trigger("click"); + + expect(wrapper.emitted("load")).toBeUndefined(); + }); + + it("renders a thread link per branch that hits the limit", async () => { + const wrapper = await mountCommentView(deepThread, { + maximumNested: 1, + maximumShownRepliesPerLevel: 5, + }); + + expect(wrapper.find(`a[href="/models/${DEMO_MODEL_ID}/comments/101"]`).exists()).toBe(true); + expect(wrapper.find(`a[href="/models/${DEMO_MODEL_ID}/comments/111"]`).exists()).toBe(true); + expect(wrapper.findComponent(CommentSeeMore).exists()).toBe(false); + }); + + it("keeps the see-more button and shows no link above the limit", async () => { + const wrapper = await mountCommentView(longComment, { maximumShownRepliesPerLevel: 1 }); + + expect(wrapper.findComponent(CommentSeeMore).exists()).toBe(true); + expect(wrapper.find('a[href*="/comments/"]').exists()).toBe(false); + }); + + it("renders neither link nor see-more at the limit with zero replies", async () => { + const wrapper = await mountCommentView(shortComment, { maximumNested: 0 }); + + expect(wrapper.find('a[href*="/comments/"]').exists()).toBe(false); + expect(wrapper.findComponent(CommentSeeMore).exists()).toBe(false); + }); + + it("hides the affordance entirely at the limit when modelId is missing", async () => { + const wrapper = await mountCommentView(withoutModelId(deepThread), { maximumNested: 0 }); + + expect(wrapper.find('a[href*="/comments/"]').exists()).toBe(false); + expect(wrapper.findComponent(CommentSeeMore).exists()).toBe(false); + }); +}); diff --git a/apps/modeling-commons-frontend/app/components/comment/CommentsPanel.test.ts b/apps/modeling-commons-frontend/app/components/comment/CommentsPanel.test.ts new file mode 100644 index 00000000..d11fab68 --- /dev/null +++ b/apps/modeling-commons-frontend/app/components/comment/CommentsPanel.test.ts @@ -0,0 +1,81 @@ +import { mockNuxtImport } from "@nuxt/test-utils/runtime"; +import { beforeEach, describe, expect, it } from "vitest"; +import { nextTick } from "vue"; +import CommentInput from "./CommentInput.vue"; +import CommentView from "./CommentView.vue"; +import ConfirmDeleteCommentDialog from "./ConfirmDeleteCommentDialog.vue"; +import { editedComment, noRepliesComment, shortComment } from "./fixtures"; +import { + mountCommentsPanel, + resetCommentMocks, + setLoggedIn, + toastAddMock, + useProfileMock, + useToastMock, + useUserMock, +} from "~~/tests/helpers"; + +mockNuxtImport("useUser", () => () => useUserMock()); +mockNuxtImport("useProfile", () => () => useProfileMock()); +mockNuxtImport("useToast", () => () => useToastMock()); + +const flatComments = [shortComment, editedComment, noRepliesComment]; + +beforeEach(() => { + resetCommentMocks(); +}); + +describe("CommentsPanel", () => { + it("renders a CommentView for every top-level comment", async () => { + const wrapper = await mountCommentsPanel(flatComments); + expect(wrapper.findAllComponents(CommentView)).toHaveLength(flatComments.length); + expect(wrapper.text()).toContain("Nice."); + expect(wrapper.text()).toContain("I fixed a typo in this one."); + expect(wrapper.text()).toContain("Standalone comment with no thread."); + }); + + it("shows the top-level comment input when logged in", async () => { + const wrapper = await mountCommentsPanel(flatComments); + expect(wrapper.findComponent(CommentInput).exists()).toBe(true); + expect(wrapper.findComponent(CommentView).props("readOnly")).toBe(false); + }); + + it("hides the comment input and marks comments read-only when logged out", async () => { + setLoggedIn(false); + const wrapper = await mountCommentsPanel(flatComments); + expect(wrapper.findComponent(CommentInput).exists()).toBe(false); + expect(wrapper.findComponent(CommentView).props("readOnly")).toBe(true); + }); + + it("opens the delete confirmation dialog when a comment emits delete", async () => { + const wrapper = await mountCommentsPanel(flatComments); + const dialog = wrapper.findComponent(ConfirmDeleteCommentDialog); + expect(dialog.props("open")).toBe(false); + + wrapper.findComponent(CommentView).vm.$emit("delete", { commentId: shortComment.id }); + await nextTick(); + + expect(dialog.props("open")).toBe(true); + }); + + it("shows the login-required toast when a logged-out visitor attempts to write", async () => { + setLoggedIn(false); + const wrapper = await mountCommentsPanel(flatComments); + + wrapper.findComponent(CommentView).vm.$emit("write"); + await nextTick(); + + expect(toastAddMock).toHaveBeenCalledWith( + expect.objectContaining({ title: "Login Required" }), + ); + }); + + it("does not toast on write attempts when logged in", async () => { + const wrapper = await mountCommentsPanel(flatComments); + + wrapper.findComponent(CommentView).vm.$emit("write"); + await nextTick(); + + expect(toastAddMock).not.toHaveBeenCalled(); + }); +}); diff --git a/apps/modeling-commons-frontend/app/components/comment/bugs.test.ts b/apps/modeling-commons-frontend/app/components/comment/bugs.test.ts new file mode 100644 index 00000000..f781e4b3 --- /dev/null +++ b/apps/modeling-commons-frontend/app/components/comment/bugs.test.ts @@ -0,0 +1,289 @@ +/** + * Regression suite for the nested-comments feature. + */ +import { mockNuxtImport, mountSuspended } from "@nuxt/test-utils/runtime"; +import { beforeEach, describe, expect, it } from "vitest"; +import { nextTick } from "vue"; +import { UModal } from "#components"; +import CommentActions from "./CommentActions.vue"; +import CommentInput from "./CommentInput.vue"; +import CommentSeeMore from "./CommentSeeMore.vue"; +import CommentTextRepresentation from "./CommentTextRepresentation.vue"; +import CommentView from "./CommentView.vue"; +import ConfirmDeleteCommentDialog from "./ConfirmDeleteCommentDialog.vue"; +import { deepThread, editedComment, longComment, noRepliesComment, shortComment } from "./fixtures"; +import { + mountCommentView, + mountCommentsPanel, + resetCommentMocks, + useProfileMock, + useToastMock, + useUserMock, +} from "~~/tests/helpers"; + +mockNuxtImport("useUser", () => () => useUserMock()); +mockNuxtImport("useProfile", () => () => useProfileMock()); +mockNuxtImport("useToast", () => () => useToastMock()); + +const flatComments = [shortComment, editedComment, noRepliesComment]; + +beforeEach(() => { + resetCommentMocks(); +}); + +describe("CommentsPanel bugs", () => { + // BUG-1 (fixed): remainingComments reads pagination.count (clamped at 0), + // so the load-more button renders whenever the server holds more comments. + it("shows a load-more button when pagination.count exceeds the shown comments", async () => { + const wrapper = await mountCommentsPanel(flatComments, { + pagination: { count: 10, lastPage: 4 }, + }); + + const loadMore = wrapper + .findAll("button") + .find((button) => button.text().includes("more comment")); + expect(loadMore, "expected a 'Load 7 more comments' button").toBeDefined(); + }); + + // BUG-2 (fixed): handleDelete closes the dialog and resets `deleting` in a + // finally, so the dialog cannot get stuck open or permanently loading. + it("closes the delete dialog and resets the loading state after confirm", async () => { + const wrapper = await mountCommentsPanel(flatComments); + wrapper.findComponent(CommentView).vm.$emit("delete", { commentId: shortComment.id }); + await nextTick(); + + const dialog = wrapper.findComponent(ConfirmDeleteCommentDialog); + expect(dialog.props("open")).toBe(true); + + dialog.vm.$emit("confirm"); + await nextTick(); + await nextTick(); + + expect(dialog.props("open")).toBe(false); + expect(dialog.props("deleting")).toBe(false); + }); + + // BUG-3 (fixed): the effective read-only state is `props.readOnly || !isLoggedIn` + // (local computed renamed to isReadOnly), so a consumer can force a + // read-only panel for a logged-in user. + it("respects an explicit readOnly prop even when the user is logged in", async () => { + const wrapper = await mountCommentsPanel(flatComments, { readOnly: true }); + + expect(wrapper.findComponent(CommentInput).exists()).toBe(false); + expect(wrapper.findComponent(CommentView).props("readOnly")).toBe(true); + }); + + // BUG-12 (fixed): CommentInput no longer clears itself on submit; the panel + // clears its top-level composer only after emitting create. (Restore-on-failure + // lands with the backend at the CommentsSection runOptimistic seam.) + it("clears the top-level composer after emitting create", async () => { + const wrapper = await mountCommentsPanel(flatComments); + + const textarea = wrapper.find("textarea"); + await textarea.setValue("A fresh top-level comment"); + await textarea.trigger("keydown", { key: "Enter" }); + + expect(wrapper.emitted("create")).toEqual([[{ content: "A fresh top-level comment" }]]); + expect((textarea.element as HTMLTextAreaElement).value).toBe(""); + }); +}); + +describe("CommentView bugs", () => { + // BUG-5 (fixed): the recursive render must pass + // maximum-shown-replies-per-level down so every depth uses the configured value. + it("propagates maximumShownRepliesPerLevel to nested reply levels", async () => { + const wrapper = await mountCommentView(deepThread, { maximumShownRepliesPerLevel: 1 }); + + const nested = wrapper + .findAllComponents(CommentView) + .find((view) => view.props("comment")?.id === "101"); + expect(nested).toBeDefined(); + expect(nested!.props("maximumShownRepliesPerLevel")).toBe(1); + }); + + // BUG-6 (fixed): `write` fires only on genuine write attempts — closing or + // cancelling an input must not emit it. + it("does not emit write when the reply input is closed via cancel", async () => { + const wrapper = await mountCommentView(shortComment); + + wrapper.findComponent(CommentActions).vm.$emit("reply"); + await nextTick(); + expect(wrapper.emitted("write")).toHaveLength(1); + + wrapper.findComponent(CommentInput).vm.$emit("cancel"); + await nextTick(); + + expect(wrapper.emitted("write")).toHaveLength(1); + }); + + // BUG-7 (fixed): readOnly gates only the open transition, so an input that is + // already open stays closable if readOnly flips (e.g. session expiry). + it("still allows closing an open reply input after readOnly becomes true", async () => { + const wrapper = await mountCommentView(shortComment); + + wrapper.findComponent(CommentActions).vm.$emit("reply"); + await nextTick(); + expect(wrapper.findComponent(CommentInput).exists()).toBe(true); + + await wrapper.setProps({ readOnly: true }); + expect(wrapper.props("readOnly")).toBe(true); + + wrapper.findComponent(CommentInput).vm.$emit("cancel"); + await nextTick(); + + expect(wrapper.findComponent(CommentInput).exists()).toBe(false); + }); + + // BUG-9 (fixed): CommentView consumes CommentSeeMore's declared + // see-more-replies emit. `load` fires once all loaded replies are revealed + // and only a server remainder is left. (The former attribute-fallthrough + // companion test is retired — the @click wiring it proved no longer exists.) + it("emits load when CommentSeeMore fires its declared see-more-replies event", async () => { + const partiallyLoaded = { ...longComment, replyPagination: { count: 5, lastPage: 1 } }; + const wrapper = await mountCommentView(partiallyLoaded, { maximumShownRepliesPerLevel: 2 }); + + const seeMore = wrapper.findComponent(CommentSeeMore); + expect(seeMore.exists()).toBe(true); + expect(seeMore.props("replyCount")).toBe(3); + + seeMore.vm.$emit("see-more-replies"); + await nextTick(); + + expect(wrapper.emitted("load")).toEqual([[{ commentId: longComment.id }]]); + }); + + // BUG-8 residual (fixed): see-more first reveals locally loaded replies that + // maximumShownRepliesPerLevel hid — it must not emit `load` for them. + it("reveals hidden loaded replies on see-more without emitting load", async () => { + const wrapper = await mountCommentView(longComment, { maximumShownRepliesPerLevel: 1 }); + + expect(wrapper.text()).not.toContain("Short one."); + const seeMore = wrapper.findComponent(CommentSeeMore); + expect(seeMore.props("replyCount")).toBe(1); + + seeMore.vm.$emit("see-more-replies"); + await nextTick(); + + expect(wrapper.emitted("load")).toBeUndefined(); + expect(wrapper.text()).toContain("Short one."); + expect(wrapper.findComponent(CommentSeeMore).exists()).toBe(false); + }); + + // BUG-8 residual (fixed): after the reveal, see-more counts only the server + // remainder and a further click emits `load`. + it("shows the server remainder after revealing and emits load on the next click", async () => { + const partiallyLoaded = { ...longComment, replyPagination: { count: 5, lastPage: 1 } }; + const wrapper = await mountCommentView(partiallyLoaded, { maximumShownRepliesPerLevel: 1 }); + + const seeMore = wrapper.findComponent(CommentSeeMore); + expect(seeMore.props("replyCount")).toBe(1); + seeMore.vm.$emit("see-more-replies"); + await nextTick(); + + const afterReveal = wrapper.findComponent(CommentSeeMore); + expect(afterReveal.props("replyCount")).toBe(3); + afterReveal.vm.$emit("see-more-replies"); + await nextTick(); + + expect(wrapper.emitted("load")).toEqual([[{ commentId: partiallyLoaded.id }]]); + }); + + // BUG-13 (fixed): the recursive render passes the parent comment's author + // name down, so editing a nested reply shows "Edit your reply to X". + it("shows 'Edit your reply to ' when editing a nested reply", async () => { + const wrapper = await mountCommentView(deepThread); + + const nested = wrapper + .findAllComponents(CommentView) + .find((view) => view.props("comment")?.id === "101"); + expect(nested).toBeDefined(); + expect(nested!.props("parentAuthorName")).toBe(deepThread.author.name); + + nested!.findComponent(CommentActions).vm.$emit("edit"); + await nextTick(); + + const editInput = nested!.findComponent(CommentInput); + expect(editInput.find("textarea").attributes("placeholder")).toBe( + `Edit your reply to ${deepThread.author.name}...`, + ); + }); + + // BUG-13 (fixed): top-level comments have no parent, so their edit input + // keeps the plain placeholder. + it("keeps the plain edit placeholder for top-level comments", async () => { + const wrapper = await mountCommentView(deepThread); + + wrapper.findComponent(CommentActions).vm.$emit("edit"); + await nextTick(); + + const editInput = wrapper.findComponent(CommentInput); + expect(editInput.find("textarea").attributes("placeholder")).toBe("Edit your comment..."); + }); +}); + +describe("ConfirmDeleteCommentDialog bugs", () => { + // BUG-10 (fixed): any close that is not confirm-driven (ESC, overlay click, + // Cancel button) emits `cancel` so the parent's cleanup always runs. + it("emits cancel when the modal is dismissed via ESC/overlay", async () => { + const wrapper = await mountSuspended(ConfirmDeleteCommentDialog, { + props: { deleting: false, open: true }, + }); + + const modal = wrapper.findComponent(UModal); + expect(modal.exists()).toBe(true); + + modal.vm.$emit("update:open", false); + await nextTick(); + + expect(wrapper.emitted("update:open")).toEqual([[false]]); + expect(wrapper.emitted("cancel")).toBeTruthy(); + }); + + // BUG-10 (fixed) companion: a confirm-driven close must not fire a spurious + // cancel on top of the confirm. + it("does not emit cancel when the close follows a confirm", async () => { + const wrapper = await mountSuspended(ConfirmDeleteCommentDialog, { + props: { deleting: false, open: true }, + }); + + const confirmButton = Array.from(document.body.querySelectorAll("button")) + .filter((button) => button.textContent?.includes("Delete comment")) + .at(-1); + expect(confirmButton).toBeDefined(); + confirmButton!.click(); + await nextTick(); + expect(wrapper.emitted("confirm")).toHaveLength(1); + + await wrapper.setProps({ open: false }); + await nextTick(); + + expect(wrapper.emitted("cancel")).toBeUndefined(); + }); +}); + +describe("CommentInput bugs", () => { + // BUG-11 (fixed): Enter submits via `.exact`, so Shift+Enter falls through + // to the textarea and inserts a newline. + it("inserts a newline instead of submitting on Shift+Enter", async () => { + const wrapper = await mountSuspended(CommentInput, { props: {} }); + const textarea = wrapper.find("textarea"); + + await textarea.setValue("line one"); + await textarea.trigger("keydown", { key: "Enter", shiftKey: true }); + + expect(wrapper.emitted("submit")).toBeUndefined(); + }); +}); + +describe("CommentTextRepresentation bugs", () => { + // BUG-15 (fixed): trailing closers balanced by an opener inside the URL are + // kept (e.g. Wikipedia links); unbalanced ones are still stripped. + it("keeps the closing paren of a URL with balanced parentheses", async () => { + const url = "https://en.wikipedia.org/wiki/Rust_(programming_language)"; + const wrapper = await mountSuspended(CommentTextRepresentation, { + props: { text: `Read ${url} for context` }, + }); + + expect(wrapper.find("a").attributes("href")).toBe(url); + }); +}); diff --git a/apps/modeling-commons-frontend/app/components/comment/comment-tree.test.ts b/apps/modeling-commons-frontend/app/components/comment/comment-tree.test.ts new file mode 100644 index 00000000..5bfcba00 --- /dev/null +++ b/apps/modeling-commons-frontend/app/components/comment/comment-tree.test.ts @@ -0,0 +1,288 @@ +import { describe, expect, it } from "vitest"; +import { + findCommentById, + hasSpine, + hasVisibleReplies, + hiddenLoadedReplyCount, + insertReply, + remainingCommentCount, + remainingReplyCount, + removeCommentById, + serverRemainingReplyCount, + updateCommentById, + visibleReplies, +} from "./comment-tree"; +import { deepThread, editedComment, longComment, noRepliesComment, shortComment } from "./fixtures"; +import type { Comment, CommentsPanelProps } from "./types"; + +const withoutReplies: Comment = { ...shortComment, replies: undefined }; +const withoutPagination: Comment = { ...longComment, replyPagination: undefined }; + +describe("visibleReplies", () => { + it("returns the first N replies", () => { + expect(visibleReplies(deepThread, 2).map((reply) => reply.id)).toEqual(["101", "111"]); + }); + + it("returns all replies when the limit exceeds them", () => { + expect(visibleReplies(longComment, 5)).toHaveLength(2); + }); + + it("returns no replies at a limit of zero", () => { + expect(visibleReplies(longComment, 0)).toEqual([]); + }); + + it("falls back to an empty list when replies are missing", () => { + expect(visibleReplies(withoutReplies, 3)).toEqual([]); + }); +}); + +const partiallyLoaded: Comment = { ...longComment, replyPagination: { count: 5, lastPage: 1 } }; + +describe("remainingReplyCount", () => { + it("counts hidden loaded replies first at visible depths", () => { + expect(remainingReplyCount(longComment, 0, 4)).toBe(2); + expect(remainingReplyCount(longComment, 1, 4)).toBe(1); + }); + + it("reports the server remainder once all loaded replies are revealed", () => { + expect(remainingReplyCount(partiallyLoaded, 1, 4)).toBe(1); + expect(remainingReplyCount(partiallyLoaded, 2, 4)).toBe(3); + }); + + it("is zero when everything loaded is revealed and the server has no more", () => { + expect(remainingReplyCount(longComment, 2, 4)).toBe(0); + }); + + it("reports the full pagination count at the nesting limit for the continue-thread link", () => { + expect(remainingReplyCount(longComment, 0, 0)).toBe(2); + expect(remainingReplyCount(longComment, 2, 0)).toBe(2); + }); + + it("clamps at zero when more replies are revealed than counted", () => { + expect(remainingReplyCount(longComment, 3, 4)).toBe(0); + }); + + it("still offers loaded replies for reveal when pagination is missing", () => { + expect(remainingReplyCount(withoutPagination, 0, 4)).toBe(2); + expect(remainingReplyCount(withoutPagination, 2, 4)).toBe(0); + }); +}); + +describe("hiddenLoadedReplyCount", () => { + it("counts loaded replies beyond the revealed window", () => { + expect(hiddenLoadedReplyCount(longComment, 0)).toBe(2); + expect(hiddenLoadedReplyCount(longComment, 1)).toBe(1); + expect(hiddenLoadedReplyCount(longComment, 2)).toBe(0); + }); + + it("clamps at zero and tolerates missing replies", () => { + expect(hiddenLoadedReplyCount(longComment, 5)).toBe(0); + expect(hiddenLoadedReplyCount(withoutReplies, 0)).toBe(0); + }); +}); + +describe("serverRemainingReplyCount", () => { + it("subtracts the loaded replies from the pagination count", () => { + expect(serverRemainingReplyCount(partiallyLoaded)).toBe(3); + }); + + it("is zero when everything is loaded or pagination is missing", () => { + expect(serverRemainingReplyCount(longComment)).toBe(0); + expect(serverRemainingReplyCount(withoutPagination)).toBe(0); + }); +}); + +describe("hasVisibleReplies", () => { + it("is true when depth remains and replies exist", () => { + expect(hasVisibleReplies(longComment, 1)).toBe(true); + }); + + it("is false at the nesting limit even when replies exist", () => { + expect(hasVisibleReplies(longComment, 0)).toBe(false); + }); + + it("is false without replies", () => { + expect(hasVisibleReplies(shortComment, 4)).toBe(false); + expect(hasVisibleReplies(withoutReplies, 4)).toBe(false); + }); +}); + +describe("hasSpine", () => { + it("is true when replies are visible", () => { + expect(hasSpine(longComment, 2, 4)).toBe(true); + }); + + it("is true at the nesting limit when hidden replies remain", () => { + expect(hasSpine(longComment, 0, 0)).toBe(true); + }); + + it("is false for a comment without replies", () => { + expect(hasSpine(shortComment, 0, 4)).toBe(false); + }); +}); + +describe("remainingCommentCount", () => { + it("subtracts the shown comments from the pagination count", () => { + expect(remainingCommentCount([shortComment], { count: 10, lastPage: 4 })).toBe(9); + }); + + // BUGS.md #1 (fixed): the total lives in pagination.count, so the props + // CommentsPanel actually passes yield a positive remainder. + it("counts the remainder for the props CommentsPanel actually passes", () => { + const panelProps: CommentsPanelProps = { + comments: [shortComment, editedComment, noRepliesComment], + pagination: { count: 10, lastPage: 4 }, + }; + expect(remainingCommentCount(panelProps.comments, panelProps.pagination)).toBe(7); + }); + + it("clamps at zero when more comments are shown than counted", () => { + expect(remainingCommentCount([shortComment, editedComment], { count: 1, lastPage: 1 })).toBe(0); + }); + + it("is zero for an empty panel with an empty pagination", () => { + expect(remainingCommentCount([], { count: 0, lastPage: 0 })).toBe(0); + }); +}); + +const cloneTree = (comments: Array): Array => structuredClone(comments); + +const makeComment = (id: string, overrides: Partial = {}): Comment => ({ + id, + author: { name: "Test Author", image: "" }, + content: `comment ${id}`, + createdAt: "2024-03-01T00:00:00", + likes: 0, + replies: [], + replyPagination: { count: 0, lastPage: 0 }, + ...overrides, +}); + +describe("findCommentById", () => { + it("finds a top-level comment", () => { + expect(findCommentById([deepThread], "100")).toBe(deepThread); + }); + + it("finds a deeply nested comment", () => { + expect(findCommentById([deepThread], "106")?.content).toContain("Lorem Ipsum"); + }); + + it("searches across multiple roots", () => { + expect(findCommentById([shortComment, longComment], "3")?.id).toBe("3"); + }); + + it("returns null for an unknown id", () => { + expect(findCommentById([deepThread, longComment], "nope")).toBeNull(); + }); + + it("handles comments without a replies array", () => { + expect(findCommentById([{ ...shortComment, replies: undefined }], "x")).toBeNull(); + }); +}); + +describe("updateCommentById", () => { + it("updates a top-level comment", () => { + const next = updateCommentById([shortComment, editedComment], "5", (comment) => ({ + ...comment, + content: "Updated.", + })); + expect(next[0]?.content).toBe("Updated."); + expect(next[1]).toBe(editedComment); + }); + + it("updates a deeply nested comment without mutating the original tree", () => { + const original = cloneTree([deepThread]); + const next = updateCommentById(original, "105", (comment) => ({ + ...comment, + likes: comment.likes + 1, + })); + expect(findCommentById(next, "105")?.likes).toBe(19); + expect(findCommentById(original, "105")?.likes).toBe(18); + }); + + it("preserves references of untouched siblings", () => { + const original = cloneTree([deepThread]); + const next = updateCommentById(original, "101", (comment) => ({ ...comment, likes: 0 })); + expect(next[0]?.replies?.[1]).toBe(original[0]?.replies?.[1]); + }); + + it("returns the same array reference when the id is not found", () => { + const original = [shortComment, editedComment]; + expect(updateCommentById(original, "nope", (comment) => ({ ...comment, likes: 99 }))).toBe( + original, + ); + }); +}); + +describe("insertReply", () => { + it("prepends the reply and bumps the parent's replyPagination count", () => { + const reply = makeComment("local-1"); + const next = insertReply(cloneTree([longComment]), "1", reply); + const parent = findCommentById(next, "1"); + expect(parent?.replies?.[0]?.id).toBe("local-1"); + expect(parent?.replies).toHaveLength(3); + expect(parent?.replyPagination).toEqual({ count: 3, lastPage: 1 }); + }); + + it("inserts into a nested parent", () => { + const next = insertReply(cloneTree([deepThread]), "103", makeComment("local-2")); + const parent = findCommentById(next, "103"); + expect(parent?.replies?.map((r) => r.id)).toEqual(["local-2", "104", "107"]); + expect(parent?.replyPagination?.count).toBe(3); + }); + + it("creates pagination and replies for a parent missing both", () => { + const bare = makeComment("bare", { replies: undefined, replyPagination: undefined }); + const next = insertReply([bare], "bare", makeComment("local-3")); + expect(next[0]?.replies).toHaveLength(1); + expect(next[0]?.replyPagination).toEqual({ count: 1, lastPage: 0 }); + }); + + it("returns the same array reference when the parent is not found", () => { + const original = [shortComment]; + expect(insertReply(original, "nope", makeComment("local-4"))).toBe(original); + }); +}); + +describe("removeCommentById", () => { + it("removes a top-level comment without touching reply counts", () => { + const original = cloneTree([longComment, shortComment]); + const next = removeCommentById(original, "5"); + expect(next.map((comment) => comment.id)).toEqual(["1"]); + expect(next[0]).toBe(original[0]); + }); + + it("removes a nested comment and decrements its direct parent's count", () => { + const next = removeCommentById(cloneTree([deepThread]), "104"); + expect(findCommentById(next, "104")).toBeNull(); + expect(findCommentById(next, "103")?.replyPagination?.count).toBe(1); + }); + + it("leaves ancestor counts untouched on a deep removal", () => { + const next = removeCommentById(cloneTree([deepThread]), "106"); + expect(findCommentById(next, "105")?.replyPagination?.count).toBe(0); + expect(findCommentById(next, "104")?.replyPagination?.count).toBe(1); + expect(findCommentById(next, "100")?.replyPagination?.count).toBe(4); + }); + + it("does not decrement the parent's count below zero", () => { + const parent = makeComment("p", { + replies: [makeComment("c")], + replyPagination: { count: 0, lastPage: 0 }, + }); + const next = removeCommentById([parent], "c"); + expect(next[0]?.replyPagination?.count).toBe(0); + }); + + it("returns the same array reference when the id is not found", () => { + const original = [shortComment, editedComment]; + expect(removeCommentById(original, "nope")).toBe(original); + }); + + it("does not mutate the original tree", () => { + const original = cloneTree([deepThread]); + removeCommentById(original, "104"); + expect(findCommentById(original, "104")).not.toBeNull(); + expect(findCommentById(original, "103")?.replyPagination?.count).toBe(2); + }); +}); diff --git a/apps/modeling-commons-frontend/tests/helpers/comment.ts b/apps/modeling-commons-frontend/tests/helpers/comment.ts new file mode 100644 index 00000000..db68e590 --- /dev/null +++ b/apps/modeling-commons-frontend/tests/helpers/comment.ts @@ -0,0 +1,59 @@ +import { mountSuspended } from "@nuxt/test-utils/runtime"; +import { vi } from "vitest"; +import { computed, ref } from "vue"; +import CommentView from "~/components/comment/CommentView.vue"; +import CommentsPanel from "~/components/comment/CommentsPanel.vue"; +import type { Comment, CommentViewSettings, CommentsPanelProps } from "~/components/comment/types"; +import { makeSession, makeUser } from "./fixtures"; + +const loggedIn = ref(true); + +const currentUser = computed(() => { + if (!loggedIn.value) { + return { session: null, isLoggedIn: false as const, user: null }; + } + const user = makeUser(); + return { ...user, session: makeSession(user.id), isLoggedIn: true as const, user }; +}); + +export const toastAddMock = vi.fn(); + +export function useUserMock() { + return currentUser; +} + +export function useProfileMock() { + return { profile: computed(() => currentUser.value.user), refresh: vi.fn() }; +} + +export function useToastMock() { + return { add: toastAddMock, remove: vi.fn(), update: vi.fn(), clear: vi.fn() }; +} + +export function setLoggedIn(value: boolean) { + loggedIn.value = value; +} + +export function resetCommentMocks() { + loggedIn.value = true; + toastAddMock.mockClear(); +} + +export function mountCommentsPanel( + comments: Array, + overrides: Partial = {}, +) { + return mountSuspended(CommentsPanel, { + props: { + comments, + pagination: { count: comments.length, lastPage: 1 }, + ...overrides, + }, + }); +} + +export function mountCommentView(comment: Comment, overrides: Partial = {}) { + return mountSuspended(CommentView, { + props: { comment, ...overrides }, + }); +} diff --git a/apps/modeling-commons-frontend/tests/helpers/index.ts b/apps/modeling-commons-frontend/tests/helpers/index.ts index 52678fd7..52ad7ed7 100644 --- a/apps/modeling-commons-frontend/tests/helpers/index.ts +++ b/apps/modeling-commons-frontend/tests/helpers/index.ts @@ -1,3 +1,4 @@ +export * from "./comment"; export * from "./fixtures"; export * from "./mockApi"; export * from "./mockUser"; From c378fce3378fb7f655c9add763f915be31ae7485 Mon Sep 17 00:00:00 2001 From: Omar Ibrahim Date: Fri, 10 Jul 2026 16:54:31 -0500 Subject: [PATCH 03/51] Feat(commons): add comments container with placeholder data layer --- .../comment/CommentsSection.test.ts | 216 ++++++++++++++++++ .../components/comment/CommentsSection.vue | 190 +++++++++++++++ .../app/composables/comments/useComments.ts | 62 +++++ .../nuxt/composables/useComments.test.ts | 96 ++++++++ 4 files changed, 564 insertions(+) create mode 100644 apps/modeling-commons-frontend/app/components/comment/CommentsSection.test.ts create mode 100644 apps/modeling-commons-frontend/app/components/comment/CommentsSection.vue create mode 100644 apps/modeling-commons-frontend/app/composables/comments/useComments.ts create mode 100644 apps/modeling-commons-frontend/tests/nuxt/composables/useComments.test.ts diff --git a/apps/modeling-commons-frontend/app/components/comment/CommentsSection.test.ts b/apps/modeling-commons-frontend/app/components/comment/CommentsSection.test.ts new file mode 100644 index 00000000..c88bb44e --- /dev/null +++ b/apps/modeling-commons-frontend/app/components/comment/CommentsSection.test.ts @@ -0,0 +1,216 @@ +import { mockNuxtImport, mountSuspended } from "@nuxt/test-utils/runtime"; +import { flushPromises } from "@vue/test-utils"; +import { beforeEach, describe, expect, it } from "vitest"; +import { nextTick } from "vue"; +import CommentsPanel from "./CommentsPanel.vue"; +import CommentsSection from "./CommentsSection.vue"; +import { findCommentById } from "./comment-tree"; +import { comments as fixtureComments } from "./fixtures"; +import type { Comment, CommentPagination } from "./types"; +import { + resetCommentMocks, + useProfileMock, + useToastMock, + useUserMock, +} from "~~/tests/helpers"; + +mockNuxtImport("useUser", () => () => useUserMock()); +mockNuxtImport("useProfile", () => () => useProfileMock()); +mockNuxtImport("useToast", () => () => useToastMock()); + +beforeEach(() => { + resetCommentMocks(); +}); + +async function mountCommentsSection(props: { modelId?: string; commentId?: string }) { + const wrapper = await mountSuspended(CommentsSection, { props }); + await flushPromises(); + await nextTick(); + return wrapper; +} + +function panelComments(wrapper: Awaited>) { + return wrapper.getComponent(CommentsPanel).props("comments") as Array; +} + +function panelPagination(wrapper: Awaited>) { + return wrapper.getComponent(CommentsPanel).props("pagination") as CommentPagination; +} + +async function emitFromPanel( + wrapper: Awaited>, + event: string, + payload?: unknown, +) { + wrapper.getComponent(CommentsPanel).vm.$emit(event, payload); + await nextTick(); +} + +describe("CommentsSection sources", () => { + it("renders the panel with the fixture comments for a modelId", async () => { + const wrapper = await mountCommentsSection({ modelId: "model-1" }); + + const comments = panelComments(wrapper); + expect(comments.map((comment) => comment.id)).toEqual( + fixtureComments.map((comment) => comment.id), + ); + expect(panelPagination(wrapper)).toEqual({ count: fixtureComments.length, lastPage: 1 }); + }); + + it("renders a single thread rooted at the given commentId", async () => { + const wrapper = await mountCommentsSection({ commentId: "100" }); + + const comments = panelComments(wrapper); + expect(comments).toHaveLength(1); + expect(comments[0]?.id).toBe("100"); + expect(comments[0]?.replies?.length).toBeGreaterThan(0); + expect(panelPagination(wrapper)).toEqual({ count: 1, lastPage: 1 }); + }); + + it("roots the thread at a nested commentId", async () => { + const wrapper = await mountCommentsSection({ commentId: "103" }); + + const comments = panelComments(wrapper); + expect(comments[0]?.id).toBe("103"); + }); + + it("renders an empty panel for an unknown commentId", async () => { + const wrapper = await mountCommentsSection({ commentId: "does-not-exist" }); + + expect(panelComments(wrapper)).toEqual([]); + expect(panelPagination(wrapper)).toEqual({ count: 0, lastPage: 0 }); + }); + + it("prefers modelId when both sources are given", async () => { + const wrapper = await mountCommentsSection({ modelId: "model-1", commentId: "100" }); + + expect(panelComments(wrapper)).toHaveLength(fixtureComments.length); + }); + + it("renders nothing when neither source is given", async () => { + const wrapper = await mountCommentsSection({}); + + expect(wrapper.findComponent(CommentsPanel).exists()).toBe(false); + }); +}); + +describe("CommentsSection optimistic mutations", () => { + it("prepends a created comment authored by the current profile", async () => { + const wrapper = await mountCommentsSection({ modelId: "model-1" }); + + await emitFromPanel(wrapper, "create", { content: "Fresh take" }); + + const comments = panelComments(wrapper); + expect(comments).toHaveLength(fixtureComments.length + 1); + expect(comments[0]).toMatchObject({ + id: "local-1", + content: "Fresh take", + likes: 0, + author: { name: "Ada Lovelace" }, + permissions: { canEdit: true, canDelete: true }, + }); + expect(panelPagination(wrapper).count).toBe(fixtureComments.length + 1); + }); + + it("inserts a reply into the target comment and bumps its reply count", async () => { + const wrapper = await mountCommentsSection({ modelId: "model-1" }); + + await emitFromPanel(wrapper, "reply", { commentId: "1", content: "Replying here" }); + + const parent = findCommentById(panelComments(wrapper), "1"); + expect(parent?.replies?.[0]).toMatchObject({ id: "local-1", content: "Replying here" }); + expect(parent?.replyPagination?.count).toBe(3); + expect(panelPagination(wrapper).count).toBe(fixtureComments.length); + }); + + it("edits a comment's content and marks it edited", async () => { + const wrapper = await mountCommentsSection({ modelId: "model-1" }); + + await emitFromPanel(wrapper, "edit", { commentId: "5", content: "Nicer." }); + + const edited = findCommentById(panelComments(wrapper), "5"); + expect(edited?.content).toBe("Nicer."); + expect(edited?.edited).toBe(true); + }); + + it("applies like and unlike to the target comment", async () => { + const wrapper = await mountCommentsSection({ modelId: "model-1" }); + + await emitFromPanel(wrapper, "like", { commentId: "5" }); + let target = findCommentById(panelComments(wrapper), "5"); + expect(target).toMatchObject({ likes: 2, likedByMe: true }); + + await emitFromPanel(wrapper, "unlike", { commentId: "5" }); + target = findCommentById(panelComments(wrapper), "5"); + expect(target).toMatchObject({ likes: 1, likedByMe: false }); + }); + + it("removes a deleted top-level comment and decrements the pagination count", async () => { + const wrapper = await mountCommentsSection({ modelId: "model-1" }); + + await emitFromPanel(wrapper, "delete", { commentId: "5" }); + + expect(findCommentById(panelComments(wrapper), "5")).toBeNull(); + expect(panelComments(wrapper)).toHaveLength(fixtureComments.length - 1); + expect(panelPagination(wrapper).count).toBe(fixtureComments.length - 1); + }); + + it("removes a nested comment and decrements only its parent's reply count", async () => { + const wrapper = await mountCommentsSection({ modelId: "model-1" }); + + await emitFromPanel(wrapper, "delete", { commentId: "2" }); + + const comments = panelComments(wrapper); + expect(findCommentById(comments, "2")).toBeNull(); + expect(findCommentById(comments, "1")?.replyPagination?.count).toBe(1); + expect(comments).toHaveLength(fixtureComments.length); + expect(panelPagination(wrapper).count).toBe(fixtureComments.length); + }); + + it("does not wedge after consecutive mutations", async () => { + const wrapper = await mountCommentsSection({ modelId: "model-1" }); + + await emitFromPanel(wrapper, "like", { commentId: "5" }); + await emitFromPanel(wrapper, "like", { commentId: "7" }); + await emitFromPanel(wrapper, "create", { content: "Still responsive" }); + + const comments = panelComments(wrapper); + expect(findCommentById(comments, "5")?.likes).toBe(2); + expect(findCommentById(comments, "7")?.likes).toBe(8); + expect(comments[0]?.content).toBe("Still responsive"); + }); + + it("assigns unique local ids to successive creations", async () => { + const wrapper = await mountCommentsSection({ modelId: "model-1" }); + + await emitFromPanel(wrapper, "create", { content: "First" }); + await emitFromPanel(wrapper, "reply", { commentId: "7", content: "Second" }); + + const comments = panelComments(wrapper); + expect(comments[0]?.id).toBe("local-1"); + expect(findCommentById(comments, "7")?.replies?.[0]?.id).toBe("local-2"); + }); + + it("resets local optimistic state when the source changes", async () => { + const wrapper = await mountCommentsSection({ modelId: "model-1" }); + + await emitFromPanel(wrapper, "create", { content: "Ephemeral" }); + expect(panelComments(wrapper)).toHaveLength(fixtureComments.length + 1); + + await wrapper.setProps({ modelId: "model-2" }); + await flushPromises(); + await nextTick(); + + expect(panelComments(wrapper)).toHaveLength(fixtureComments.length); + expect(findCommentById(panelComments(wrapper), "local-1")).toBeNull(); + }); + + it("keeps load and load-more as inert seams", async () => { + const wrapper = await mountCommentsSection({ modelId: "model-1" }); + + await emitFromPanel(wrapper, "load", { commentId: "1" }); + await emitFromPanel(wrapper, "load-more", panelPagination(wrapper)); + + expect(panelComments(wrapper)).toHaveLength(fixtureComments.length); + }); +}); diff --git a/apps/modeling-commons-frontend/app/components/comment/CommentsSection.vue b/apps/modeling-commons-frontend/app/components/comment/CommentsSection.vue new file mode 100644 index 00000000..642214eb --- /dev/null +++ b/apps/modeling-commons-frontend/app/components/comment/CommentsSection.vue @@ -0,0 +1,190 @@ + + + diff --git a/apps/modeling-commons-frontend/app/composables/comments/useComments.ts b/apps/modeling-commons-frontend/app/composables/comments/useComments.ts new file mode 100644 index 00000000..a3dea7a3 --- /dev/null +++ b/apps/modeling-commons-frontend/app/composables/comments/useComments.ts @@ -0,0 +1,62 @@ +import type { MaybeRefOrGetter } from "vue"; +import { findCommentById } from "~/components/comment/comment-tree"; +import { comments as modelCommentsFixture, deepThread } from "~/components/comment/fixtures"; +import type { Comment, CommentPagination } from "~/components/comment/types"; + +type CommentsSource = { modelId: string } | { commentId: string }; + +type CommentsPayload = { + comments: Array; + pagination: CommentPagination; +}; + +const emptyPayload = (): CommentsPayload => ({ + comments: [], + pagination: { count: 0, lastPage: 0 }, +}); + +// TODO: PLACEHOLDER — there is no comments backend yet. Replace ONLY this +// function's body with the real calls once the endpoints exist: +// { modelId } -> GET /api/v1/models/{id}/comments +// { commentId } -> GET /api/v1/comments/{id}/thread +// Until those routes are in shared/types/api.d.ts, use the repo convention of +// raw `fetch(apiBase + path, { credentials: "include" })` (see +// composables/model/useModelInteractions.ts), then migrate to +// `api.GET(...)` after regenerating types with `yarn generate:types`. +async function fetchComments(source: CommentsSource): Promise { + const fixtureTrees = [...modelCommentsFixture, deepThread]; + + if ("modelId" in source) { + if (!source.modelId) return emptyPayload(); + const comments = structuredClone(modelCommentsFixture); + return { comments, pagination: { count: comments.length, lastPage: 1 } }; + } + + const root = source.commentId ? findCommentById(fixtureTrees, source.commentId) : null; + if (!root) return emptyPayload(); + return { comments: [structuredClone(root)], pagination: { count: 1, lastPage: 1 } }; +} + +export default function useComments(source: MaybeRefOrGetter) { + const resolved = computed(() => toValue(source)); + const key = computed(() => + "modelId" in resolved.value + ? `comments:model:${resolved.value.modelId}` + : `comments:thread:${resolved.value.commentId}`, + ); + + const { data, status, error, refresh } = useAsyncData( + () => key.value, + () => fetchComments(resolved.value), + { watch: [key] }, + ); + + const comments = computed(() => data.value?.comments ?? []); + const pagination = computed( + () => data.value?.pagination ?? { count: 0, lastPage: 0 }, + ); + + return { comments, pagination, status, error, refresh }; +} + +export type { CommentsPayload, CommentsSource }; diff --git a/apps/modeling-commons-frontend/tests/nuxt/composables/useComments.test.ts b/apps/modeling-commons-frontend/tests/nuxt/composables/useComments.test.ts new file mode 100644 index 00000000..49f3ae3b --- /dev/null +++ b/apps/modeling-commons-frontend/tests/nuxt/composables/useComments.test.ts @@ -0,0 +1,96 @@ +import { describe, expect, it, vi } from "vitest"; +import { nextTick, ref } from "vue"; +import { comments as fixtureComments, deepThread } from "~/components/comment/fixtures"; +import useComments from "~/composables/comments/useComments"; +import type { CommentsSource } from "~/composables/comments/useComments"; + +describe("useComments with a modelId source", () => { + it("returns the fixture comments with pagination and a success status", async () => { + const { comments, pagination, status, error, refresh } = useComments({ modelId: "m-1" }); + await refresh(); + + expect(comments.value).toEqual(fixtureComments); + expect(pagination.value).toEqual({ count: fixtureComments.length, lastPage: 1 }); + expect(status.value).toBe("success"); + expect(error.value).toBeFalsy(); + }); + + it("returns deep clones so consumers cannot corrupt the fixtures", async () => { + const { comments, refresh } = useComments({ modelId: "m-2" }); + await refresh(); + + const first = comments.value[0]!; + expect(first).not.toBe(fixtureComments[0]); + expect(first.replies?.[0]).not.toBe(fixtureComments[0]!.replies?.[0]); + + first.content = "mutated"; + first.replies?.splice(0); + await refresh(); + + expect(comments.value[0]?.content).toBe(fixtureComments[0]!.content); + expect(comments.value[0]?.replies).toHaveLength(fixtureComments[0]!.replies!.length); + }); + + it("returns an empty payload for an empty modelId", async () => { + const { comments, pagination, refresh } = useComments({ modelId: "" }); + await refresh(); + + expect(comments.value).toEqual([]); + expect(pagination.value).toEqual({ count: 0, lastPage: 0 }); + }); +}); + +describe("useComments with a commentId source", () => { + it("wraps the found comment as the single root of the thread", async () => { + const { comments, pagination, refresh } = useComments({ commentId: "100" }); + await refresh(); + + expect(comments.value).toHaveLength(1); + expect(comments.value[0]).toEqual(deepThread); + expect(comments.value[0]).not.toBe(deepThread); + expect(pagination.value).toEqual({ count: 1, lastPage: 1 }); + }); + + it("roots the thread at a nested comment", async () => { + const { comments, refresh } = useComments({ commentId: "103" }); + await refresh(); + + expect(comments.value[0]?.id).toBe("103"); + expect(comments.value[0]?.replies?.map((reply) => reply.id)).toEqual(["104", "107"]); + }); + + it("returns an empty payload for an unknown commentId", async () => { + const { comments, pagination, status, refresh } = useComments({ commentId: "nope" }); + await refresh(); + + expect(comments.value).toEqual([]); + expect(pagination.value).toEqual({ count: 0, lastPage: 0 }); + expect(status.value).toBe("success"); + }); +}); + +describe("useComments source reactivity", () => { + it("refetches when the source ref changes", async () => { + const source = ref({ modelId: "m-3" }); + const { comments, refresh } = useComments(source); + await refresh(); + + expect(comments.value).toHaveLength(fixtureComments.length); + + source.value = { commentId: "100" }; + await nextTick(); + + await vi.waitFor(() => { + expect(comments.value).toHaveLength(1); + expect(comments.value[0]?.id).toBe("100"); + }); + }); + + it("accepts a getter source", async () => { + const id = ref("5"); + const { comments, refresh } = useComments(() => ({ commentId: id.value })); + await refresh(); + + expect(comments.value[0]?.id).toBe("5"); + }); +}); From c13bad4841e0eecdb6ef6160e7904e4d5ea0e57b Mon Sep 17 00:00:00 2001 From: Omar Ibrahim Date: Fri, 10 Jul 2026 16:54:38 -0500 Subject: [PATCH 04/51] Feat(commons): route comment threads under models --- .../models/[id]/comments/[commentId].vue | 31 +++++++++++++++++++ apps/modeling-commons-frontend/nuxt.config.ts | 6 ++++ .../tests/nuxt/pages/comment-thread.test.ts | 30 ++++++++++++++++++ 3 files changed, 67 insertions(+) create mode 100644 apps/modeling-commons-frontend/app/pages/models/[id]/comments/[commentId].vue create mode 100644 apps/modeling-commons-frontend/tests/nuxt/pages/comment-thread.test.ts diff --git a/apps/modeling-commons-frontend/app/pages/models/[id]/comments/[commentId].vue b/apps/modeling-commons-frontend/app/pages/models/[id]/comments/[commentId].vue new file mode 100644 index 00000000..b3dcd4d4 --- /dev/null +++ b/apps/modeling-commons-frontend/app/pages/models/[id]/comments/[commentId].vue @@ -0,0 +1,31 @@ + + + diff --git a/apps/modeling-commons-frontend/nuxt.config.ts b/apps/modeling-commons-frontend/nuxt.config.ts index 16d28554..33f04957 100644 --- a/apps/modeling-commons-frontend/nuxt.config.ts +++ b/apps/modeling-commons-frontend/nuxt.config.ts @@ -119,6 +119,12 @@ export default defineNuxtConfig({ path: "/models/:slug/:id/edit", file: "~/pages/models/[id]/edit.vue", }); + + pages.push({ + name: "model-slug-comment-thread", + path: "/models/:slug/:id/comments/:commentId", + file: "~/pages/models/[id]/comments/[commentId].vue", + }); }, }, diff --git a/apps/modeling-commons-frontend/tests/nuxt/pages/comment-thread.test.ts b/apps/modeling-commons-frontend/tests/nuxt/pages/comment-thread.test.ts new file mode 100644 index 00000000..1583a1ea --- /dev/null +++ b/apps/modeling-commons-frontend/tests/nuxt/pages/comment-thread.test.ts @@ -0,0 +1,30 @@ +import { mockNuxtImport, mountSuspended } from "@nuxt/test-utils/runtime"; +import { describe, expect, it } from "vitest"; +import CommentsSection from "~/components/comment/CommentsSection.vue"; +import { useProfileMock, useUserMock } from "~~/tests/helpers"; + +mockNuxtImport("useUser", () => () => useUserMock()); +mockNuxtImport("useProfile", () => () => useProfileMock()); + +mockNuxtImport("useRoute", () => () => ({ + name: "model-comment-thread", + path: "/models/model-demo/comments/100", + fullPath: "/models/model-demo/comments/100", + params: { id: "model-demo", commentId: "100" }, + query: {}, + hash: "", + matched: [], + meta: {}, +})); + +describe("models/[id]/comments/[commentId].vue", () => { + it("renders CommentsSection with the route's commentId and links back to the model", async () => { + const Page = (await import("~/pages/models/[id]/comments/[commentId].vue")).default; + const wrapper = await mountSuspended(Page); + + const section = wrapper.findComponent(CommentsSection); + expect(section.exists()).toBe(true); + expect(section.props("commentId")).toBe("100"); + expect(wrapper.find('a[href="/models/model-demo"]').exists()).toBe(true); + }); +}); From 05802472a7e0d06ffb38c1815a8d54e46bfa26c5 Mon Sep 17 00:00:00 2001 From: Omar Ibrahim Date: Fri, 10 Jul 2026 16:54:50 -0500 Subject: [PATCH 05/51] docs(commons): record comment feature decisions --- .claude/DECISIONS.md | 111 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 .claude/DECISIONS.md diff --git a/.claude/DECISIONS.md b/.claude/DECISIONS.md new file mode 100644 index 00000000..c2f6cebe --- /dev/null +++ b/.claude/DECISIONS.md @@ -0,0 +1,111 @@ +# Decisions + +Critical, non-obvious decisions made while working in this repo. Newest first. + +## 2026-07-10 — Comment threads nested under models: `/models/:id/comments/:commentId` + +**Context:** The standalone `/comments/:id` thread page had no model context. Comments belong to a model on the future backend, so the thread route moved under models. + +**Decisions:** +1. **Route contract:** `/models/:id/comments/:commentId` (`app/pages/models/[id]/comments/[commentId].vue`) plus the slug alias `model-slug-comment-thread` → `/models/:slug/:id/comments/:commentId` pushed in `nuxt.config.ts` `pages:extend`, matching the other slug aliases. This **supersedes** the `/comments/:id` contract recorded below — `app/pages/comments/[id].vue` is deleted. The page's back affordance links to `/models/:id` (works for both route shapes since `route.params.id` is the model id in each). +2. **Link construction depends on `Comment.modelId`** (new optional field, matching the future backend shape): CommentView's at-limit "Continue this thread" link is `/models/${comment.modelId}/comments/${comment.id}`. When `modelId` is missing the affordance hides entirely (no link, no see-more — same rendering as having no replies). Fixtures are stamped recursively with `DEMO_MODEL_ID = "model-demo"` via a `stampModelId` helper in `fixtures.ts`. + +**Context:** The comments feature has no backend yet. A dedicated bug-hunt pass found 16 defects (`app/components/comment/BUGS.md`). + +**Decisions:** +1. **Known bugs are pinned, not patched.** Each testable bug has an `it.fails` test in `app/components/comment/bugs.test.ts` asserting the CORRECT behavior — the suite stays green while the bug exists, and fixing a bug flips its pin loudly, forcing the fixer to promote it to a regular test. Refactoring done alongside was bug-for-bug behavior-preserving; `comment-tree.ts` deliberately reproduces two quirks (BUG-1, BUG-8 residue) with pointer comments. +2. **`/comments/[id]` (`app/pages/comments/[id].vue`) is a thin stub** over `CommentsSection :comment-id` — it is the target of the "Continue this thread" link CommentView renders at the nesting limit. Route shape `/comments/` is now a contract between CommentView and this page; change both together. + +## 2026-07-10 — Comment continue-thread link + collapsing: component-level branching, dumb metadata bar + +**Context:** Two additions to the recursive `CommentView`: a "Continue this thread" affordance at the nesting limit (supersedes the BUG-8 dead-end see-more) and Reddit-style per-comment collapsing. HARD constraints: `comment-tree.ts` untouched (its at-limit full-count quirk is pinned by tests), all 10 `it.fails` pins in `bugs.test.ts` keep failing (notably BUG-9's `@click`-fallthrough see-more wiring stays verbatim where `CommentSeeMore` still renders). + +**Decisions:** +1. **At-limit branching lives in `CommentView`, not `comment-tree.ts`:** `atNestingLimit = maximumNested <= 0` picks between a link-styled `UButton :to="/comments/"` ("Continue this thread (N replies)", N = `remainingReplyCount`, i.e. the full `replyPagination.count` at the limit) and the unchanged `CommentSeeMore` + `@click` → `load`. `load` is never emitted at the limit anymore. `/comments/[id]` is a future thread page — link-only for now. Both affordances share the existing spine-eraser/elbow wrapper so tree drawing is unchanged. +2. **Collapse state is a local `ref(false)` per `CommentView`** (no persistence, no provide/inject); `CommentMetadataBar` stays dumb with additive optional props `collapsible`/`collapsed`/`hiddenReplyCount` + a `toggle-collapse` emit, rendering the chevron toggle (`aria-expanded`, dynamic aria-label) and the muted collapsed summary ("· N replies hidden" / "· collapsed"). Collapsing is a read action: it never emits `write` and ignores `readOnly`. +3. **Collapsed = leaf:** everything below the metadata bar (body/actions/reply-input/replies/see-more) sits in one ` @@ -44,6 +46,7 @@ import type { CommentsPayload, CommentsSource } from "~/composables/comments/use const props = defineProps<{ modelId?: string; commentId?: string; + readOnly?: boolean; }>(); const hasSource = computed(() => Boolean(props.modelId || props.commentId)); @@ -88,6 +91,14 @@ const commentAuthor = computed(() => profile.value ? { name: profile.value.name, image: profile.value.image ?? "" } : null, ); +const user = useUser(); +const isReadOnly = computed(() => props.readOnly || !user.value.isLoggedIn); +const handleWriteAttempt = () => { + if (!user.value.isLoggedIn) { + showRequiresLoginToast("participate in discussions"); + } +}; + let localIdCounter = 0; const makeLocalComment = (content: string, author: CommentAuthor): Comment => ({ id: `local-${++localIdCounter}`, diff --git a/apps/modeling-commons-frontend/app/components/comment/bugs.test.ts b/apps/modeling-commons-frontend/app/components/comment/bugs.test.ts index 383b6a83..62413407 100644 --- a/apps/modeling-commons-frontend/app/components/comment/bugs.test.ts +++ b/apps/modeling-commons-frontend/app/components/comment/bugs.test.ts @@ -15,6 +15,7 @@ import { deepThread, editedComment, longComment, noRepliesComment, shortComment import { mountCommentView, mountCommentsPanel, + mountCommentsSection, resetCommentMocks, useProfileMock, useToastMock, @@ -63,16 +64,6 @@ describe("CommentsPanel bugs", () => { expect(dialog.props("deleting")).toBe(false); }); - // BUG-3 (fixed): the effective read-only state is `props.readOnly || !isLoggedIn` - // (local computed renamed to isReadOnly), so a consumer can force a - // read-only panel for a logged-in user. - it("respects an explicit readOnly prop even when the user is logged in", async () => { - const wrapper = await mountCommentsPanel(flatComments, { readOnly: true }); - - expect(wrapper.findComponent(CommentInput).exists()).toBe(false); - expect(wrapper.findComponent(CommentView).props("readOnly")).toBe(true); - }); - // BUG-12 (fixed): CommentInput no longer clears itself on submit; the panel // clears its top-level composer only after emitting create. (Restore-on-failure // lands with the backend at the CommentsSection runOptimistic seam.) @@ -88,6 +79,18 @@ describe("CommentsPanel bugs", () => { }); }); +describe("CommentsSection bugs", () => { + // BUG-3 (fixed): the effective read-only state (`props.readOnly || !isLoggedIn`) + // now lives on CommentsSection, so a consumer can force a read-only discussion + // for a logged-in user. + it("respects an explicit readOnly prop even when the user is logged in", async () => { + const wrapper = await mountCommentsSection({ modelId: "model-1", readOnly: true }); + + expect(wrapper.findComponent(CommentInput).exists()).toBe(false); + expect(wrapper.findComponent(CommentView).props("readOnly")).toBe(true); + }); +}); + describe("CommentView bugs", () => { // BUG-5 (fixed): the recursive render must pass // maximum-shown-replies-per-level down so every depth uses the configured value. diff --git a/apps/modeling-commons-frontend/tests/helpers/comment.ts b/apps/modeling-commons-frontend/tests/helpers/comment.ts index db68e590..5eb1ee2d 100644 --- a/apps/modeling-commons-frontend/tests/helpers/comment.ts +++ b/apps/modeling-commons-frontend/tests/helpers/comment.ts @@ -1,8 +1,10 @@ import { mountSuspended } from "@nuxt/test-utils/runtime"; +import { flushPromises } from "@vue/test-utils"; import { vi } from "vitest"; -import { computed, ref } from "vue"; +import { computed, nextTick, ref } from "vue"; import CommentView from "~/components/comment/CommentView.vue"; import CommentsPanel from "~/components/comment/CommentsPanel.vue"; +import CommentsSection from "~/components/comment/CommentsSection.vue"; import type { Comment, CommentViewSettings, CommentsPanelProps } from "~/components/comment/types"; import { makeSession, makeUser } from "./fixtures"; @@ -57,3 +59,14 @@ export function mountCommentView(comment: Comment, overrides: Partial Date: Fri, 10 Jul 2026 20:51:58 -0500 Subject: [PATCH 08/51] Feat(commons): highlight a comment from url state until blur --- .claude/DECISIONS.md | 10 +++ .../components/comment/CommentView.test.ts | 77 ++++++++++++++++++- .../app/components/comment/CommentView.vue | 25 +++++- .../app/components/comment/CommentsPanel.vue | 3 + .../comment/CommentsSection.test.ts | 65 ++++++++++++++++ .../components/comment/CommentsSection.vue | 20 +++++ .../app/components/comment/types.ts | 1 + .../tests/helpers/comment.ts | 35 ++++++++- 8 files changed, 233 insertions(+), 3 deletions(-) diff --git a/.claude/DECISIONS.md b/.claude/DECISIONS.md index 8ef6111e..54ac5ce7 100644 --- a/.claude/DECISIONS.md +++ b/.claude/DECISIONS.md @@ -118,3 +118,13 @@ Critical, non-obvious decisions made while working in this repo. Newest first. 1. **`CommentsPanel` is auth-unaware.** Its `readOnly` prop (default `false`) is the only read-only source; it no longer calls `useUser` or shows the login toast, and it re-emits `write` upward. `CommentsSection` owns the session: effective read-only = `props.readOnly || !user.isLoggedIn` (new optional `readOnly` section prop), and it answers the panel's `write` emit with `showRequiresLoginToast("participate in discussions")` when logged out. Direct `CommentsPanel` consumers (the `theme.vue` demo) get no auth gating unless they pass `readOnly` themselves. 2. **The delete-dialog flow (including its success toast) stays in the panel** — it is local UI state, not session awareness; only the auth toast moved up. 3. **The BUG-3 regression pin moved to the section level** (`bugs.test.ts` → "CommentsSection bugs"): explicit `readOnly` respected while logged in, asserted through the mounted section. Panel tests now pin only the prop-driven contract; a shared `mountCommentsSection` helper lives in `tests/helpers/comment.ts`. + +## 2026-07-10 — Comments: URL-driven comment highlight (`?highlightedCommentId=`) + +**Context:** `app/components/comment/` feature. Any page that renders `CommentsSection` gets deep-link highlighting for free. + +**Decisions:** +1. **The highlight lives on `CommentsSection`, seeded ONCE from `route.query.highlightedCommentId` at setup** (arrays and empty strings ignored; later URL changes don't re-arm it). It threads section → panel → recursive `CommentView` as the optional `highlightedCommentId` setting in `CommentViewSettings`. +2. **Dismissal is focus-based and literal:** the highlighted root gets `tabindex="-1"`, is scrolled (`scrollIntoView({ block: "center" })`) and focused (`preventScroll: true`) in `onMounted` (client-only, so SSR-safe); ANY `focusout` of that root — including clicking a button inside the comment — emits `highlight-dismiss`, which bubbles view → panel → section. The section then clears the ref and `router.replace`s the query with only `highlightedCommentId` removed. +3. **Highlight treatment:** primary-tinted `ring`/`bg` plus `p-2 -m-2` so the box gains breathing room without shifting content position. +4. **`CommentView` roots now carry `data-comment-id`** — a stable DOM hook for tests (and future scroll/anchor logic); tests select roots by it instead of Tailwind classes. Mocking `useRouter` via `mockNuxtImport` must include `afterEach` (a `vi.fn()` suffices) or `@nuxt/test-utils`'s own runtime setup crashes the suite. diff --git a/apps/modeling-commons-frontend/app/components/comment/CommentView.test.ts b/apps/modeling-commons-frontend/app/components/comment/CommentView.test.ts index 658ed6fe..966f25c0 100644 --- a/apps/modeling-commons-frontend/app/components/comment/CommentView.test.ts +++ b/apps/modeling-commons-frontend/app/components/comment/CommentView.test.ts @@ -1,5 +1,5 @@ import { mockNuxtImport } from "@nuxt/test-utils/runtime"; -import { describe, expect, it } from "vitest"; +import { describe, expect, it, vi } from "vitest"; import { nextTick } from "vue"; import CommentActions from "./CommentActions.vue"; import CommentInput from "./CommentInput.vue"; @@ -111,6 +111,81 @@ describe("CommentView interactions", () => { }); }); +describe("CommentView highlight", () => { + const rootOf = (wrapper: Awaited>, id: string) => + wrapper.find(`[data-comment-id="${id}"]`); + + it("gives the highlighted comment a distinct, focusable root", async () => { + const plain = await mountCommentView(shortComment); + const highlighted = await mountCommentView(shortComment, { + highlightedCommentId: shortComment.id, + }); + + const plainRoot = rootOf(plain, shortComment.id); + const highlightedRoot = rootOf(highlighted, shortComment.id); + + expect(highlightedRoot.attributes("tabindex")).toBe("-1"); + expect(plainRoot.attributes("tabindex")).toBeUndefined(); + expect(highlightedRoot.attributes("class")).not.toBe(plainRoot.attributes("class")); + }); + + it("applies no highlight treatment for a non-matching id", async () => { + const plain = await mountCommentView(shortComment); + const nonMatching = await mountCommentView(shortComment, { + highlightedCommentId: "someone-else", + }); + + expect(rootOf(nonMatching, shortComment.id).attributes("tabindex")).toBeUndefined(); + expect(rootOf(nonMatching, shortComment.id).attributes("class")).toBe( + rootOf(plain, shortComment.id).attributes("class"), + ); + }); + + it("scrolls to and focuses the highlighted root on mount only", async () => { + const scrollSpy = vi.spyOn(Element.prototype, "scrollIntoView").mockImplementation(() => {}); + const focusSpy = vi.spyOn(HTMLElement.prototype, "focus").mockImplementation(() => {}); + + try { + await mountCommentView(shortComment); + expect(scrollSpy).not.toHaveBeenCalled(); + + await mountCommentView(shortComment, { highlightedCommentId: shortComment.id }); + expect(scrollSpy).toHaveBeenCalledWith({ block: "center" }); + expect(focusSpy).toHaveBeenCalledWith({ preventScroll: true }); + } finally { + scrollSpy.mockRestore(); + focusSpy.mockRestore(); + } + }); + + it("emits highlight-dismiss on focusout only while highlighted", async () => { + const highlighted = await mountCommentView(shortComment, { + highlightedCommentId: shortComment.id, + }); + await rootOf(highlighted, shortComment.id).trigger("focusout"); + expect(highlighted.emitted("highlight-dismiss")).toHaveLength(1); + + const plain = await mountCommentView(shortComment); + await rootOf(plain, shortComment.id).trigger("focusout"); + expect(plain.emitted("highlight-dismiss")).toBeUndefined(); + }); + + it("threads the highlight to nested replies and bubbles the dismiss", async () => { + const wrapper = await mountCommentView(longComment, { highlightedCommentId: "3" }); + + const nested = wrapper + .findAllComponents(CommentView) + .find((view) => view.props("comment")?.id === "3"); + expect(nested).toBeDefined(); + expect(nested!.props("highlightedCommentId")).toBe("3"); + expect(nested!.attributes("tabindex")).toBe("-1"); + expect(rootOf(wrapper, longComment.id).attributes("tabindex")).toBeUndefined(); + + await nested!.trigger("focusout"); + expect(wrapper.emitted("highlight-dismiss")).toHaveLength(1); + }); +}); + describe("CommentView read-only", () => { it("emits write instead of opening the reply input", async () => { const wrapper = await mountCommentView(shortComment, { readOnly: true }); diff --git a/apps/modeling-commons-frontend/app/components/comment/CommentView.vue b/apps/modeling-commons-frontend/app/components/comment/CommentView.vue index 7483eeac..4b3b1f93 100644 --- a/apps/modeling-commons-frontend/app/components/comment/CommentView.vue +++ b/apps/modeling-commons-frontend/app/components/comment/CommentView.vue @@ -1,5 +1,12 @@ @@ -99,6 +101,24 @@ const handleWriteAttempt = () => { } }; +const route = useRoute(); +const router = useRouter(); + +const initialHighlightId = route.query.highlightedCommentId; +const highlightedCommentId = ref( + typeof initialHighlightId === "string" && initialHighlightId !== "" + ? initialHighlightId + : undefined, +); + +const dismissHighlight = () => { + if (!highlightedCommentId.value) return; + highlightedCommentId.value = undefined; + const query = { ...route.query }; + delete query.highlightedCommentId; + router.replace({ query }); +}; + let localIdCounter = 0; const makeLocalComment = (content: string, author: CommentAuthor): Comment => ({ id: `local-${++localIdCounter}`, diff --git a/apps/modeling-commons-frontend/app/components/comment/types.ts b/apps/modeling-commons-frontend/app/components/comment/types.ts index 03344af6..bc831ee2 100644 --- a/apps/modeling-commons-frontend/app/components/comment/types.ts +++ b/apps/modeling-commons-frontend/app/components/comment/types.ts @@ -50,6 +50,7 @@ export type CommentViewSettings = { parentHasSeeMoreReplies?: boolean; isLastSibling?: boolean; readOnly?: boolean; + highlightedCommentId?: string; }; export type CommentViewProps = { diff --git a/apps/modeling-commons-frontend/tests/helpers/comment.ts b/apps/modeling-commons-frontend/tests/helpers/comment.ts index 5eb1ee2d..064edc85 100644 --- a/apps/modeling-commons-frontend/tests/helpers/comment.ts +++ b/apps/modeling-commons-frontend/tests/helpers/comment.ts @@ -1,7 +1,7 @@ import { mountSuspended } from "@nuxt/test-utils/runtime"; import { flushPromises } from "@vue/test-utils"; import { vi } from "vitest"; -import { computed, nextTick, ref } from "vue"; +import { computed, nextTick, reactive, ref } from "vue"; import CommentView from "~/components/comment/CommentView.vue"; import CommentsPanel from "~/components/comment/CommentsPanel.vue"; import CommentsSection from "~/components/comment/CommentsSection.vue"; @@ -36,9 +36,42 @@ export function setLoggedIn(value: boolean) { loggedIn.value = value; } +const routeQuery = ref>({}); +export const routerReplaceMock = vi.fn(); + +export function setRouteQuery(query: Record) { + routeQuery.value = query; +} + +export function useRouteMock() { + return reactive({ + name: "test-route", + path: "/", + fullPath: "/", + params: {}, + query: routeQuery, + hash: "", + matched: [], + meta: {}, + }); +} + +export function useRouterMock() { + return { + replace: routerReplaceMock, + push: vi.fn(), + afterEach: vi.fn(), + beforeEach: vi.fn(), + beforeResolve: vi.fn(), + resolve: vi.fn(), + }; +} + export function resetCommentMocks() { loggedIn.value = true; toastAddMock.mockClear(); + routeQuery.value = {}; + routerReplaceMock.mockClear(); } export function mountCommentsPanel( From a8caff5292902dccdad52ff9bdabf6539a9249d4 Mon Sep 17 00:00:00 2001 From: Omar Ibrahim Date: Fri, 10 Jul 2026 21:01:12 -0500 Subject: [PATCH 09/51] Feat(commons): add parent-thread navigation and specific copy to the thread page --- .claude/DECISIONS.md | 9 +++ .../components/comment/comment-tree.test.ts | 14 ++++ .../app/components/comment/fixtures.ts | 15 +++-- .../app/components/comment/types.ts | 1 + .../models/[id]/comments/[commentId].vue | 51 +++++++++++--- .../nuxt/composables/useComments.test.ts | 17 +++++ .../tests/nuxt/pages/comment-thread.test.ts | 67 +++++++++++++++++-- 7 files changed, 150 insertions(+), 24 deletions(-) diff --git a/.claude/DECISIONS.md b/.claude/DECISIONS.md index 54ac5ce7..0acea371 100644 --- a/.claude/DECISIONS.md +++ b/.claude/DECISIONS.md @@ -128,3 +128,12 @@ Critical, non-obvious decisions made while working in this repo. Newest first. 2. **Dismissal is focus-based and literal:** the highlighted root gets `tabindex="-1"`, is scrolled (`scrollIntoView({ block: "center" })`) and focused (`preventScroll: true`) in `onMounted` (client-only, so SSR-safe); ANY `focusout` of that root — including clicking a button inside the comment — emits `highlight-dismiss`, which bubbles view → panel → section. The section then clears the ref and `router.replace`s the query with only `highlightedCommentId` removed. 3. **Highlight treatment:** primary-tinted `ring`/`bg` plus `p-2 -m-2` so the box gains breathing room without shifting content position. 4. **`CommentView` roots now carry `data-comment-id`** — a stable DOM hook for tests (and future scroll/anchor logic); tests select roots by it instead of Tailwind classes. Mocking `useRouter` via `mockNuxtImport` must include `afterEach` (a `vi.fn()` suffices) or `@nuxt/test-utils`'s own runtime setup crashes the suite. + +## 2026-07-10 — Comments: thread page parent-thread navigation + root-derived copy + +**Context:** `app/pages/models/[id]/comments/[commentId].vue`. Replies now carry `parentId?: string` on `Comment` (future backend shape; fixtures stamp it recursively via `stampTree`, roots stay undefined). + +**Decisions:** +1. **The page reads the thread root by calling `useComments({ commentId })` itself** instead of having `CommentsSection` expose it via emit/slot. Both callers resolve to the same `useAsyncData` key (`comments:thread:`), so Nuxt dedupes them into one shared fetch — verified under the nuxt test runtime with no duplicate-key warnings. When the real backend lands, only `fetchComments` changes and both consumers keep working. +2. **"See parent thread" renders only when the loaded root has a `parentId`** and links to `/models//comments/` using the route's model id (consistent with "Back to model"). +3. **The subtitle derives from the loaded root** ("The full conversation under 's comment.") with the old generic sentence as the loading/absent fallback. `data-testid` hooks (`parent-thread-link`, `thread-subtitle`) keep tests off copy and classes. diff --git a/apps/modeling-commons-frontend/app/components/comment/comment-tree.test.ts b/apps/modeling-commons-frontend/app/components/comment/comment-tree.test.ts index 5bfcba00..0109df2f 100644 --- a/apps/modeling-commons-frontend/app/components/comment/comment-tree.test.ts +++ b/apps/modeling-commons-frontend/app/components/comment/comment-tree.test.ts @@ -158,6 +158,20 @@ const makeComment = (id: string, overrides: Partial = {}): Comment => ( ...overrides, }); +describe("fixture parent stamping", () => { + it("stamps every nested reply with its parent's id", () => { + expect(findCommentById([deepThread], "103")?.parentId).toBe("102"); + expect(findCommentById([deepThread], "106")?.parentId).toBe("105"); + expect(findCommentById([longComment], "3")?.parentId).toBe("2"); + }); + + it("leaves fixture roots without a parentId", () => { + expect(deepThread.parentId).toBeUndefined(); + expect(longComment.parentId).toBeUndefined(); + expect(shortComment.parentId).toBeUndefined(); + }); +}); + describe("findCommentById", () => { it("finds a top-level comment", () => { expect(findCommentById([deepThread], "100")).toBe(deepThread); diff --git a/apps/modeling-commons-frontend/app/components/comment/fixtures.ts b/apps/modeling-commons-frontend/app/components/comment/fixtures.ts index f032c77c..4973fac9 100644 --- a/apps/modeling-commons-frontend/app/components/comment/fixtures.ts +++ b/apps/modeling-commons-frontend/app/components/comment/fixtures.ts @@ -12,13 +12,14 @@ const [omar, jane, john] = authors; export const DEMO_MODEL_ID = "model-demo"; -const stampModelId = (comment: Comment, modelId: string = DEMO_MODEL_ID): Comment => ({ +const stampTree = (comment: Comment, parentId?: string, modelId: string = DEMO_MODEL_ID): Comment => ({ ...comment, modelId, - replies: comment.replies?.map((reply) => stampModelId(reply, modelId)), + parentId, + replies: comment.replies?.map((reply) => stampTree(reply, comment.id, modelId)), }); -export const longComment: Comment = stampModelId({ +export const longComment: Comment = stampTree({ id: "1", author: omar, content: commentText, @@ -77,7 +78,7 @@ export const longComment: Comment = stampModelId({ ], }); -export const deepThread: Comment = stampModelId({ +export const deepThread: Comment = stampTree({ id: "100", author: omar, content: "Hot take: tabs are objectively better than spaces. Fight me.", @@ -310,7 +311,7 @@ export const deepThread: Comment = stampModelId({ ], }); -export const shortComment: Comment = stampModelId({ +export const shortComment: Comment = stampTree({ id: "5", author: jane, content: "Nice.", @@ -324,7 +325,7 @@ export const shortComment: Comment = stampModelId({ replies: [], }); -export const editedComment: Comment = stampModelId({ +export const editedComment: Comment = stampTree({ id: "6", author: omar, content: "I fixed a typo in this one.", @@ -339,7 +340,7 @@ export const editedComment: Comment = stampModelId({ replies: [], }); -export const noRepliesComment: Comment = stampModelId({ +export const noRepliesComment: Comment = stampTree({ id: "7", author: john, content: "Standalone comment with no thread.", diff --git a/apps/modeling-commons-frontend/app/components/comment/types.ts b/apps/modeling-commons-frontend/app/components/comment/types.ts index bc831ee2..74b02cf4 100644 --- a/apps/modeling-commons-frontend/app/components/comment/types.ts +++ b/apps/modeling-commons-frontend/app/components/comment/types.ts @@ -26,6 +26,7 @@ export type CommentPermissions = { export type Comment = { id: string; modelId?: string; + parentId?: string; author: CommentAuthor; /* Plaintext string */ content: string; diff --git a/apps/modeling-commons-frontend/app/pages/models/[id]/comments/[commentId].vue b/apps/modeling-commons-frontend/app/pages/models/[id]/comments/[commentId].vue index b3dcd4d4..464bdc20 100644 --- a/apps/modeling-commons-frontend/app/pages/models/[id]/comments/[commentId].vue +++ b/apps/modeling-commons-frontend/app/pages/models/[id]/comments/[commentId].vue @@ -2,18 +2,32 @@
- - Back to model - +
+ + Back to model + + + See parent thread + +

Comment thread

-

A single discussion thread, shown in full.

+

{{ subtitle }}

@@ -28,4 +42,19 @@ useSeoMeta({ const route = useRoute(); const modelId = computed(() => String(route.params.id ?? "")); const commentId = computed(() => String(route.params.commentId ?? "")); + +const { comments } = useComments(() => ({ commentId: commentId.value })); +const threadRoot = computed(() => comments.value[0]); + +const parentThreadLink = computed(() => + threadRoot.value?.parentId + ? `/models/${modelId.value}/comments/${threadRoot.value.parentId}` + : null, +); + +const subtitle = computed(() => + threadRoot.value + ? `The full conversation under ${threadRoot.value.author.name}'s comment.` + : "A single discussion thread, shown in full.", +); diff --git a/apps/modeling-commons-frontend/tests/nuxt/composables/useComments.test.ts b/apps/modeling-commons-frontend/tests/nuxt/composables/useComments.test.ts index 49f3ae3b..634400dd 100644 --- a/apps/modeling-commons-frontend/tests/nuxt/composables/useComments.test.ts +++ b/apps/modeling-commons-frontend/tests/nuxt/composables/useComments.test.ts @@ -31,6 +31,14 @@ describe("useComments with a modelId source", () => { expect(comments.value[0]?.replies).toHaveLength(fixtureComments[0]!.replies!.length); }); + it("returns roots without a parentId", async () => { + const { comments, refresh } = useComments({ modelId: "m-parents" }); + await refresh(); + + expect(comments.value.length).toBeGreaterThan(0); + expect(comments.value.every((comment) => comment.parentId === undefined)).toBe(true); + }); + it("returns an empty payload for an empty modelId", async () => { const { comments, pagination, refresh } = useComments({ modelId: "" }); await refresh(); @@ -59,6 +67,15 @@ describe("useComments with a commentId source", () => { expect(comments.value[0]?.replies?.map((reply) => reply.id)).toEqual(["104", "107"]); }); + it("carries the stamped parentId through the thread fetch", async () => { + const { comments, refresh } = useComments({ commentId: "103" }); + await refresh(); + + expect(comments.value[0]?.parentId).toBe("102"); + expect(comments.value[0]?.replies?.length).toBeGreaterThan(0); + expect(comments.value[0]?.replies?.every((reply) => reply.parentId === "103")).toBe(true); + }); + it("returns an empty payload for an unknown commentId", async () => { const { comments, pagination, status, refresh } = useComments({ commentId: "nope" }); await refresh(); diff --git a/apps/modeling-commons-frontend/tests/nuxt/pages/comment-thread.test.ts b/apps/modeling-commons-frontend/tests/nuxt/pages/comment-thread.test.ts index 1583a1ea..9be58517 100644 --- a/apps/modeling-commons-frontend/tests/nuxt/pages/comment-thread.test.ts +++ b/apps/modeling-commons-frontend/tests/nuxt/pages/comment-thread.test.ts @@ -1,30 +1,85 @@ import { mockNuxtImport, mountSuspended } from "@nuxt/test-utils/runtime"; -import { describe, expect, it } from "vitest"; +import { flushPromises } from "@vue/test-utils"; +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { nextTick } from "vue"; import CommentsSection from "~/components/comment/CommentsSection.vue"; +import { findCommentById } from "~/components/comment/comment-tree"; +import { deepThread } from "~/components/comment/fixtures"; import { useProfileMock, useUserMock } from "~~/tests/helpers"; mockNuxtImport("useUser", () => () => useUserMock()); mockNuxtImport("useProfile", () => () => useProfileMock()); +const routeState = vi.hoisted(() => ({ commentId: "100" })); + mockNuxtImport("useRoute", () => () => ({ name: "model-comment-thread", - path: "/models/model-demo/comments/100", - fullPath: "/models/model-demo/comments/100", - params: { id: "model-demo", commentId: "100" }, + path: `/models/model-demo/comments/${routeState.commentId}`, + fullPath: `/models/model-demo/comments/${routeState.commentId}`, + params: { id: "model-demo", commentId: routeState.commentId }, query: {}, hash: "", matched: [], meta: {}, })); +beforeEach(() => { + routeState.commentId = "100"; +}); + +async function mountThreadPage(commentId: string) { + routeState.commentId = commentId; + const Page = (await import("~/pages/models/[id]/comments/[commentId].vue")).default; + const wrapper = await mountSuspended(Page); + await flushPromises(); + await nextTick(); + return wrapper; +} + +const parentLink = (wrapper: Awaited>) => + wrapper.find('[data-testid="parent-thread-link"]'); + describe("models/[id]/comments/[commentId].vue", () => { it("renders CommentsSection with the route's commentId and links back to the model", async () => { - const Page = (await import("~/pages/models/[id]/comments/[commentId].vue")).default; - const wrapper = await mountSuspended(Page); + const wrapper = await mountThreadPage("100"); const section = wrapper.findComponent(CommentsSection); expect(section.exists()).toBe(true); expect(section.props("commentId")).toBe("100"); expect(wrapper.find('a[href="/models/model-demo"]').exists()).toBe(true); }); + + it("links to the parent thread when the root is a nested reply", async () => { + const nested = findCommentById([deepThread], "103"); + expect(nested?.parentId).toBeDefined(); + + const wrapper = await mountThreadPage("103"); + + const link = parentLink(wrapper); + expect(link.exists()).toBe(true); + expect(link.attributes("href")).toBe(`/models/model-demo/comments/${nested!.parentId}`); + }); + + it("shows no parent-thread link for a top-level comment", async () => { + const wrapper = await mountThreadPage("100"); + + expect(parentLink(wrapper).exists()).toBe(false); + }); + + it("names the thread root's author in the subtitle", async () => { + const nested = findCommentById([deepThread], "103"); + const wrapper = await mountThreadPage("103"); + + expect(wrapper.find('[data-testid="thread-subtitle"]').text()).toContain( + nested!.author.name, + ); + }); + + it("falls back to a generic subtitle when the thread cannot be found", async () => { + const wrapper = await mountThreadPage("does-not-exist"); + + const subtitle = wrapper.find('[data-testid="thread-subtitle"]'); + expect(subtitle.exists()).toBe(true); + expect(subtitle.text()).not.toBe(""); + }); }); From a2c97d623a590069a15b9acb67660a28bcd13a13 Mon Sep 17 00:00:00 2001 From: Omar Ibrahim Date: Fri, 10 Jul 2026 21:05:12 -0500 Subject: [PATCH 10/51] Feat(commons): link comment dates and authors to their pages --- .claude/DECISIONS.md | 10 ++++ .../comment/CommentMetadataBar.test.ts | 59 +++++++++++++++++++ .../components/comment/CommentMetadataBar.vue | 9 ++- .../components/comment/CommentView.test.ts | 31 ++++++++++ .../comment/CommentView.thread-link.test.ts | 56 +++++++++++++++--- .../app/components/comment/CommentView.vue | 11 ++++ .../app/components/comment/fixtures.ts | 6 +- .../app/components/comment/types.ts | 1 + 8 files changed, 170 insertions(+), 13 deletions(-) create mode 100644 apps/modeling-commons-frontend/app/components/comment/CommentMetadataBar.test.ts diff --git a/.claude/DECISIONS.md b/.claude/DECISIONS.md index 0acea371..69ad4ab5 100644 --- a/.claude/DECISIONS.md +++ b/.claude/DECISIONS.md @@ -137,3 +137,13 @@ Critical, non-obvious decisions made while working in this repo. Newest first. 1. **The page reads the thread root by calling `useComments({ commentId })` itself** instead of having `CommentsSection` expose it via emit/slot. Both callers resolve to the same `useAsyncData` key (`comments:thread:`), so Nuxt dedupes them into one shared fetch — verified under the nuxt test runtime with no duplicate-key warnings. When the real backend lands, only `fetchComments` changes and both consumers keep working. 2. **"See parent thread" renders only when the loaded root has a `parentId`** and links to `/models//comments/` using the route's model id (consistent with "Back to model"). 3. **The subtitle derives from the loaded root** ("The full conversation under 's comment.") with the old generic sentence as the loading/absent fallback. `data-testid` hooks (`parent-thread-link`, `thread-subtitle`) keep tests off copy and classes. + +## 2026-07-10 — Comments: dates and authors link out + +**Context:** `app/components/comment/`. Every comment's relative date now links to its thread page and avatars/names link to author profiles. + +**Decisions:** +1. **`CommentMetadataBar` stays presentation-only:** it takes an optional `threadLink?: string` prop (rendered as a muted `NuxtLink` with a hover affordance, plain span otherwise) instead of deriving the URL itself; `CommentView` passes its existing `threadLink` computed, so the "no modelId → no link" rule lives in one place. +2. **The avatar's profile link wrapper is `class="contents"`** so the anchor is layout-inert: the `UAvatar` keeps its own positioning classes and continues to anchor the spine/elbow drawing exactly as before. +3. **Fixture authors carry `/users/user-` profile urls** matching the `/users/:id` route convention; the metadata bar's pre-existing name link lights up from the same `author.url`. +4. **The continue-thread button now carries `data-testid="continue-thread-link"`** — date links share the `/models//comments/` href shape, so href-only selectors no longer disambiguate it in tests. diff --git a/apps/modeling-commons-frontend/app/components/comment/CommentMetadataBar.test.ts b/apps/modeling-commons-frontend/app/components/comment/CommentMetadataBar.test.ts new file mode 100644 index 00000000..efd7f89b --- /dev/null +++ b/apps/modeling-commons-frontend/app/components/comment/CommentMetadataBar.test.ts @@ -0,0 +1,59 @@ +import { mountSuspended } from "@nuxt/test-utils/runtime"; +import { describe, expect, it } from "vitest"; +import CommentMetadataBar from "./CommentMetadataBar.vue"; +import type { CommentMetadataBarProps } from "./types"; + +const linkedAuthor = { + name: "Jane Doe", + image: "https://i.pravatar.cc/150?u=jane", + url: "/users/user-jane", +}; +const plainAuthor = { name: "Jane Doe", image: "https://i.pravatar.cc/150?u=jane" }; + +const mountBar = (props: Partial = {}) => + mountSuspended(CommentMetadataBar, { + props: { author: plainAuthor, createdAt: "2024-03-11T15:30:00", ...props }, + }); + +describe("CommentMetadataBar author link", () => { + it("links the author name to their profile url", async () => { + const wrapper = await mountBar({ author: linkedAuthor }); + + const link = wrapper.find(`a[href="${linkedAuthor.url}"]`); + expect(link.exists()).toBe(true); + expect(link.text()).toContain(linkedAuthor.name); + }); + + it("renders the name without a link when the author has no url", async () => { + const wrapper = await mountBar(); + + expect(wrapper.text()).toContain(plainAuthor.name); + expect(wrapper.find("a").exists()).toBe(false); + }); +}); + +describe("CommentMetadataBar date link", () => { + const threadLink = "/models/model-demo/comments/42"; + + it("links the date to the thread page when threadLink is set", async () => { + const wrapper = await mountBar({ threadLink }); + + const link = wrapper.find(`a[href="${threadLink}"]`); + expect(link.exists()).toBe(true); + expect(link.text()).not.toBe(""); + }); + + it("renders the date as plain text without a threadLink", async () => { + const wrapper = await mountBar(); + + expect(wrapper.find("a").exists()).toBe(false); + }); + + it("keeps the author and date links distinct", async () => { + const wrapper = await mountBar({ author: linkedAuthor, threadLink }); + + const hrefs = wrapper.findAll("a").map((link) => link.attributes("href")); + expect(hrefs).toContain(linkedAuthor.url); + expect(hrefs).toContain(threadLink); + }); +}); diff --git a/apps/modeling-commons-frontend/app/components/comment/CommentMetadataBar.vue b/apps/modeling-commons-frontend/app/components/comment/CommentMetadataBar.vue index 6d704ec6..99e6997d 100644 --- a/apps/modeling-commons-frontend/app/components/comment/CommentMetadataBar.vue +++ b/apps/modeling-commons-frontend/app/components/comment/CommentMetadataBar.vue @@ -13,7 +13,14 @@ /> {{ author.name }} {{ author.name }} - {{ formatRelativeDate(createdAt) }} + + {{ formatRelativeDate(createdAt) }} + + {{ formatRelativeDate(createdAt) }} (edited) {{ collapsedSummary }} diff --git a/apps/modeling-commons-frontend/app/components/comment/CommentView.test.ts b/apps/modeling-commons-frontend/app/components/comment/CommentView.test.ts index 966f25c0..191646c6 100644 --- a/apps/modeling-commons-frontend/app/components/comment/CommentView.test.ts +++ b/apps/modeling-commons-frontend/app/components/comment/CommentView.test.ts @@ -1,6 +1,7 @@ import { mockNuxtImport } from "@nuxt/test-utils/runtime"; import { describe, expect, it, vi } from "vitest"; import { nextTick } from "vue"; +import { UAvatar } from "#components"; import CommentActions from "./CommentActions.vue"; import CommentInput from "./CommentInput.vue"; import CommentView from "./CommentView.vue"; @@ -32,6 +33,36 @@ describe("CommentView rendering", () => { }); }); +describe("CommentView author profile links", () => { + it("wraps the avatar in a link to the author's profile", async () => { + const wrapper = await mountCommentView(shortComment); + + const avatar = wrapper.findComponent(UAvatar); + expect(avatar.exists()).toBe(true); + expect(avatar.element.closest("a")?.getAttribute("href")).toBe(shortComment.author.url); + }); + + it("leaves the avatar unwrapped when the author has no profile url", async () => { + const wrapper = await mountCommentView({ + ...shortComment, + author: { ...shortComment.author, url: undefined }, + }); + + const avatar = wrapper.findComponent(UAvatar); + expect(avatar.exists()).toBe(true); + expect(avatar.element.closest("a")).toBeNull(); + }); + + it("links the author name to the same profile url", async () => { + const wrapper = await mountCommentView(shortComment); + + const nameLink = wrapper + .findAll(`a[href="${shortComment.author.url}"]`) + .find((link) => link.text().includes(shortComment.author.name)); + expect(nameLink).toBeDefined(); + }); +}); + describe("CommentView interactions", () => { it("emits like with its own id when not yet liked", async () => { const wrapper = await mountCommentView(shortComment); diff --git a/apps/modeling-commons-frontend/app/components/comment/CommentView.thread-link.test.ts b/apps/modeling-commons-frontend/app/components/comment/CommentView.thread-link.test.ts index 27ec33d3..239af472 100644 --- a/apps/modeling-commons-frontend/app/components/comment/CommentView.thread-link.test.ts +++ b/apps/modeling-commons-frontend/app/components/comment/CommentView.thread-link.test.ts @@ -1,5 +1,6 @@ import { mockNuxtImport } from "@nuxt/test-utils/runtime"; import { beforeEach, describe, expect, it } from "vitest"; +import CommentMetadataBar from "./CommentMetadataBar.vue"; import CommentSeeMore from "./CommentSeeMore.vue"; import { DEMO_MODEL_ID, deepThread, longComment, shortComment } from "./fixtures"; import type { Comment } from "./types"; @@ -20,20 +21,24 @@ const withoutModelId = (comment: Comment): Comment => ({ replies: comment.replies?.map(withoutModelId), }); +type Wrapper = Awaited>; +const continueLinks = (wrapper: Wrapper) => wrapper.findAll('[data-testid="continue-thread-link"]'); + describe("continue-thread link at the nesting limit", () => { it("renders a model-scoped thread link instead of the see-more button", async () => { const wrapper = await mountCommentView(deepThread, { maximumNested: 0 }); - const link = wrapper.find(`a[href="${threadHref(deepThread)}"]`); - expect(link.exists()).toBe(true); - expect(link.text()).toContain(String(deepThread.replyPagination?.count)); + const links = continueLinks(wrapper); + expect(links).toHaveLength(1); + expect(links[0]!.attributes("href")).toBe(threadHref(deepThread)); + expect(links[0]!.text()).toContain(String(deepThread.replyPagination?.count)); expect(wrapper.findComponent(CommentSeeMore).exists()).toBe(false); }); it("does not emit load when the thread link is clicked", async () => { const wrapper = await mountCommentView(deepThread, { maximumNested: 0 }); - await wrapper.find(`a[href="${threadHref(deepThread)}"]`).trigger("click"); + await continueLinks(wrapper)[0]!.trigger("click"); expect(wrapper.emitted("load")).toBeUndefined(); }); @@ -44,8 +49,9 @@ describe("continue-thread link at the nesting limit", () => { maximumShownRepliesPerLevel: 5, }); - expect(wrapper.find(`a[href="/models/${DEMO_MODEL_ID}/comments/101"]`).exists()).toBe(true); - expect(wrapper.find(`a[href="/models/${DEMO_MODEL_ID}/comments/111"]`).exists()).toBe(true); + const hrefs = continueLinks(wrapper).map((link) => link.attributes("href")); + expect(hrefs).toContain(`/models/${DEMO_MODEL_ID}/comments/101`); + expect(hrefs).toContain(`/models/${DEMO_MODEL_ID}/comments/111`); expect(wrapper.findComponent(CommentSeeMore).exists()).toBe(false); }); @@ -53,20 +59,52 @@ describe("continue-thread link at the nesting limit", () => { const wrapper = await mountCommentView(longComment, { maximumShownRepliesPerLevel: 1 }); expect(wrapper.findComponent(CommentSeeMore).exists()).toBe(true); - expect(wrapper.find('a[href*="/comments/"]').exists()).toBe(false); + expect(continueLinks(wrapper)).toHaveLength(0); }); it("renders neither link nor see-more at the limit with zero replies", async () => { const wrapper = await mountCommentView(shortComment, { maximumNested: 0 }); - expect(wrapper.find('a[href*="/comments/"]').exists()).toBe(false); + expect(continueLinks(wrapper)).toHaveLength(0); expect(wrapper.findComponent(CommentSeeMore).exists()).toBe(false); }); it("hides the affordance entirely at the limit when modelId is missing", async () => { const wrapper = await mountCommentView(withoutModelId(deepThread), { maximumNested: 0 }); - expect(wrapper.find('a[href*="/comments/"]').exists()).toBe(false); + expect(continueLinks(wrapper)).toHaveLength(0); expect(wrapper.findComponent(CommentSeeMore).exists()).toBe(false); }); }); + +describe("date link to the thread page", () => { + it("passes the model-scoped thread link to the metadata bar", async () => { + const wrapper = await mountCommentView(shortComment); + + expect(wrapper.getComponent(CommentMetadataBar).props("threadLink")).toBe( + threadHref(shortComment), + ); + }); + + it("passes no thread link when modelId is missing", async () => { + const wrapper = await mountCommentView(withoutModelId(shortComment)); + + expect(wrapper.getComponent(CommentMetadataBar).props("threadLink")).toBeUndefined(); + }); + + it("renders the date as a link to the comment's thread page", async () => { + const wrapper = await mountCommentView(shortComment); + + expect(wrapper.find(`a[href="${threadHref(shortComment)}"]`).exists()).toBe(true); + }); + + it("gives every rendered comment a date link to its own thread", async () => { + const wrapper = await mountCommentView(longComment); + + for (const id of ["1", "2", "3"]) { + expect(wrapper.find(`a[href="/models/${DEMO_MODEL_ID}/comments/${id}"]`).exists()).toBe( + true, + ); + } + }); +}); diff --git a/apps/modeling-commons-frontend/app/components/comment/CommentView.vue b/apps/modeling-commons-frontend/app/components/comment/CommentView.vue index 4b3b1f93..281ec5c4 100644 --- a/apps/modeling-commons-frontend/app/components/comment/CommentView.vue +++ b/apps/modeling-commons-frontend/app/components/comment/CommentView.vue @@ -17,7 +17,16 @@ + + + ; const [omar, jane, john] = authors; diff --git a/apps/modeling-commons-frontend/app/components/comment/types.ts b/apps/modeling-commons-frontend/app/components/comment/types.ts index 74b02cf4..d78cf4d3 100644 --- a/apps/modeling-commons-frontend/app/components/comment/types.ts +++ b/apps/modeling-commons-frontend/app/components/comment/types.ts @@ -7,6 +7,7 @@ export type CommentAuthor = { export type CommentMetadataBarProps = { author: CommentAuthor; createdAt: string; + threadLink?: string; edited?: boolean; collapsible?: boolean; collapsed?: boolean; From 047f4e7fe94ad380e269a92349c27d674b5da568 Mon Sep 17 00:00:00 2001 From: Omar Ibrahim Date: Sun, 12 Jul 2026 15:53:33 -0500 Subject: [PATCH 11/51] Minor(commons): Put reply count in small parenthesis --- .../app/components/comment/CommentActions.vue | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/modeling-commons-frontend/app/components/comment/CommentActions.vue b/apps/modeling-commons-frontend/app/components/comment/CommentActions.vue index f43ab76a..800247df 100644 --- a/apps/modeling-commons-frontend/app/components/comment/CommentActions.vue +++ b/apps/modeling-commons-frontend/app/components/comment/CommentActions.vue @@ -19,7 +19,7 @@ title="Reply" @click="$emit('reply')" > - {{ replyLabel }} + Reply ({{ replyCount }}) (); -const replyLabel = computed(() => (props.replyCount ? `Reply · ${props.replyCount}` : "Reply")); - const dropdownActions = computed>(() => { const actions: DropdownMenuItem[] = []; From 84df63b197908b60f503319e2b38d32d8c265128 Mon Sep 17 00:00:00 2001 From: Omar Ibrahim Date: Sun, 12 Jul 2026 15:53:56 -0500 Subject: [PATCH 12/51] dev(commons): Add comments to theme dev page --- .../app/pages/theme.vue | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/apps/modeling-commons-frontend/app/pages/theme.vue b/apps/modeling-commons-frontend/app/pages/theme.vue index a3098da8..5205b16e 100644 --- a/apps/modeling-commons-frontend/app/pages/theme.vue +++ b/apps/modeling-commons-frontend/app/pages/theme.vue @@ -235,17 +235,10 @@

Theme

- - Night - + +
+

Comments

+
@@ -255,6 +248,8 @@ if (!import.meta.dev) { navigateTo("/"); } +const commentsFixtures = await import("@/components/comment/fixtures"); + const USD = ref("USD"); const TRUE = ref(true); From ffa12a7ed169bba920858d086905187595726371 Mon Sep 17 00:00:00 2001 From: Omar Ibrahim Date: Sun, 12 Jul 2026 15:55:03 -0500 Subject: [PATCH 13/51] refactor(commons): remove BUGS.md and references to it --- .../app/components/comment/BUGS.md | 245 ------------------ .../app/components/comment/bugs.test.ts | 34 ++- 2 files changed, 15 insertions(+), 264 deletions(-) delete mode 100644 apps/modeling-commons-frontend/app/components/comment/BUGS.md diff --git a/apps/modeling-commons-frontend/app/components/comment/BUGS.md b/apps/modeling-commons-frontend/app/components/comment/BUGS.md deleted file mode 100644 index 6da8c67e..00000000 --- a/apps/modeling-commons-frontend/app/components/comment/BUGS.md +++ /dev/null @@ -1,245 +0,0 @@ -# Nested-comments feature — bug report (historical) - -Found by a dedicated bug-hunt pass on `app/components/comment/`. All 16 bugs below have since been -fixed — every entry carries a **FIXED** line describing what closed it. The former `it.fails` pins -now live on as regular regression tests in `bugs.test.ts`; BUG numbers below match the `// BUG-` -comments in that file. - -## 1. Load-more comments button can never appear — HIGH - -- **Where:** `CommentsPanel.vue:80` -- **What happens:** `remainingComments` reads `props.count`, but no `count` prop exists — the data - lives in `props.pagination.count`. `props.count` is always `undefined`, so `remainingComments` - is always `0 - comments.length <= 0` and the "Load N more comments" button never renders - (verified against the only production usage, `pages/theme.vue`, which passes - `pagination: { count: 100 }` with 4 comments and shows no button). This is also a TS error - hiding in the pre-existing check-types noise. -- **Expected:** button shows `pagination.count - comments.length` when positive. -- **Test:** `bugs.test.ts` — "shows a load-more button when pagination.count exceeds the shown comments" -- **Fix direction:** use `props.pagination.count` (and clamp at 0). -- **FIXED (2026-07-10):** `remainingCommentCount` in `comment-tree.ts` now takes `(comments, pagination)` and returns `max(0, pagination.count - comments.length)`; the panel passes `props.pagination`. The quirk-pinning test in `comment-tree.test.ts` was rewritten to the corrected semantics. - -## 2. Delete-confirm dialog gets stuck open and permanently loading — HIGH - -- **Where:** `CommentsPanel.vue:53-64` -- **What happens:** `handleDelete` sets `deleting = true` and never resets it, and never sets - `deleteOpen = false`. After confirming a delete, the modal stays open with a spinning, disabled - Delete button forever. If the user escapes via Cancel/ESC, `deleting` is still `true`, so every - future delete dialog opens already-spinning and unusable. -- **Expected:** on confirm, close the dialog and reset `deleting` (in a `finally`). -- **Test:** `bugs.test.ts` — "closes the delete dialog and resets the loading state after confirm" -- **Fix direction:** `deleteOpen.value = false; deleting.value = false` (finally-block) in `handleDelete`. -- **FIXED (2026-07-10):** `handleDelete` closes the dialog and resets `deleting` in a `finally`, and also clears `deleteTarget` after a confirmed delete. - -## 3. Panel's `readOnly` prop is silently ignored — MEDIUM - -- **Where:** `CommentsPanel.vue:48` (vs. the `readOnly` declared via `CommentsPanelProps & CommentViewSettings`) -- **What happens:** the local `const readOnly = computed(() => !user.value.isLoggedIn)` shadows the - declared `readOnly` prop in the template. A consumer passing `read-only` (e.g. archived/locked - thread) still gets a composer and interactive comments for logged-in users. -- **Expected:** effective read-only = `props.readOnly || !isLoggedIn`. -- **Test:** `bugs.test.ts` — "respects an explicit readOnly prop even when the user is logged in" -- **Fix direction:** rename the computed and OR in the prop; also stop relying on prop shadowing. -- **FIXED (2026-07-10):** the local computed is renamed `isReadOnly` and computes `props.readOnly || !user.isLoggedIn`, so consumers can force a read-only panel for logged-in users. - -## 4. Panel emits nothing — all interactions are silently discarded — MEDIUM - -- **Where:** `CommentsPanel.vue:9-21` (and `defineEmits` absent) -- **What happens:** the panel listens only to `@delete`/`@write` from `CommentView` and declares no - emits of its own. `reply`, `edit`, `like`, `unlike`, and `load` events from the comment tree die - at the panel; the top-level `` has no `@submit`/`@cancel` handlers at all, so a - posted top-level comment is discarded while `CommentInput` clears the textarea (user-visible data - loss). `handleDelete`/`loadMoreComments` are TODO stubs and `deleteTarget` is write-only. -- **Expected:** the panel (or a future container per task #4) forwards/handles every interaction. -- **Test:** report-only (pending backend/container design; would pin the wrong contract now). -- **Fix direction:** wire `@submit` on the composer and forward tree events (or handle them) when - the data layer lands; make `handleDelete` use `deleteTarget`. -- **Addendum (boundary refactor):** the panel now re-emits `create`/`reply`/`edit`/`like`/`unlike`/ - `load`/`load-more` and emits `delete` (with `deleteTarget`) on confirm — but nothing consumes them - yet, so there is still no backend call or optimistic UI and interactions have no persistent effect. -- **FIXED (2026-07-10):** closed by container consumption — `CommentsSection` consumes every panel - emit (`create`/`reply`/`edit`/`like`/`unlike`/`delete`/`load`/`load-more`) with optimistic tree - mutations, and the panel's `handleDelete` emits `delete` with the stored `deleteTarget`. - `load`/`load-more` remain inert seams pending the backend. - -## 5. `maximumShownRepliesPerLevel` is not propagated to nested levels — MEDIUM - -- **Where:** `CommentView.vue:54-70` -- **What happens:** the recursive `` passes `maximum-nested`, `is-nested`, etc., but - not `maximum-shown-replies-per-level`. Every level below the first reverts to the component - default (5). With the panel's default of 2, one tree mixes both limits. -- **Expected:** the configured value applies at every depth. -- **Test:** `bugs.test.ts` — "propagates maximumShownRepliesPerLevel to nested reply levels" -- **Fix direction:** add `:maximum-shown-replies-per-level="maximumShownRepliesPerLevel"` to the - recursive call (see also bug 16 on default drift). -- **FIXED (2026-07-10):** the recursive `` now binds `:maximum-shown-replies-per-level`, so the configured value applies at every depth. - -## 6. Closing an input emits a spurious `write` event — LOW - -- **Where:** `CommentView.vue:142-153` (also 155-163) -- **What happens:** `toggleReplyInput`/`toggleEditMode` emit `write` unconditionally, so cancelling - a reply/edit — and the internal toggle call inside `submitCommentEdit`/`submitCommentReply` — - fires a second `write` that is not a write attempt. Harmless today (the panel only toasts when - logged out, and inputs never open while logged out), but any consumer counting/gating on `write` - will misbehave, and each submit double-emits it. -- **Expected:** `write` fires only on actual write attempts (opening an input, like, delete). -- **Test:** `bugs.test.ts` — "does not emit write when the reply input is closed via cancel" -- **Fix direction:** emit `write` only when opening/acting, not when closing; don't route submit - through the toggle helpers. -- **FIXED (2026-07-10):** the toggles emit `write` only on the open transition; cancel and submit go through dedicated `closeReplyInput`/`closeEditMode` helpers that emit nothing. - -## 7. Open input becomes unclosable if `readOnly` flips while open — LOW - -- **Where:** `CommentView.vue:142-153` -- **What happens:** the toggles `return` early when `props.readOnly` is true — before flipping the - ref. If `readOnly` becomes true while a reply/edit input is open (e.g. session expiry refreshes - auth state), Cancel and Submit can no longer close the input; every attempt just re-emits `write` - (login toast) with the input stuck open. -- **Expected:** closing is always allowed; only opening is gated. -- **Test:** `bugs.test.ts` — "still allows closing an open reply input after readOnly becomes true" -- **Fix direction:** gate only the open transition (`if (readOnly && !isReplyInputVisible) return`), - or split open/close functions. -- **FIXED (2026-07-10):** `readOnly` now gates only the open transition; the close path (toggle-while-open, cancel, submit) always works. - -## 8. See-more at the nesting limit is a dead end — MEDIUM - -- **Where:** `CommentView.vue:134-139` with the see-more block at 74-88 -- **What happens:** when `maximumNested <= 0`, `hasVisibleReplies` is permanently false, yet - `remainingReplies` is the full `replyPagination.count`, so "See N more replies" renders and - clicking emits `load`. Even if a future handler loads the replies, they can never be displayed at - that depth — the button can never do anything visible. Related smell at visible depths: - `remainingReplies = count - shownReplies.length` counts locally-loaded-but-hidden replies - (hidden by `maximumShownRepliesPerLevel`) as needing a `load` fetch; a naive page-append handler - would duplicate them. (The lead's other claim — `maximumNested - 1` decrementing below 0 — was - cleared: children are not rendered at `maximumNested <= 0`, so negative values never occur.) -- **Expected:** at the limit, see-more should navigate to a thread view or expand in place - (pending task #5); the `load` emit should mean "fetch what isn't loaded", not "reveal". -- **Test:** report-only (correct behavior is a pending design decision). -- **Fix direction:** distinct affordance at the nesting limit; separate "reveal hidden loaded - replies" from "load more from server". -- **Addendum (continue-thread):** the dead end is superseded — at the limit `CommentView` now - renders a "Continue this thread (N replies)" link to `/comments/` instead of - `CommentSeeMore`, so the `load`-emit-at-limit no longer occurs. Remaining: the `/comments/[id]` - thread page is a stub pending implementation, and the visible-depth smell above (counting - locally-loaded-but-hidden replies as needing a `load` fetch) still stands. -- **FIXED (2026-07-10):** residual reveal-vs-load — `CommentView` keeps a per-node `revealedCount` ref (seeded from `maximumShownRepliesPerLevel`); see-more first reveals all locally loaded replies, then shows only the clamped server remainder (`hiddenLoadedReplyCount`/`serverRemainingReplyCount` in `comment-tree.ts`) and only then emits `load`; the nesting-limit branch still reports the full count for the continue-thread link. - -## 9. `CommentSeeMore`'s declared emit is dead API; wiring relies on attr fallthrough — LOW - -- **Where:** `CommentSeeMore.vue:21-23` vs `CommentView.vue:84-87` -- **What happens:** `CommentSeeMore` declares and emits `see-more-replies`, but `CommentView` - listens for `@click`. Because `click` is not a declared emit, the listener falls through as an - attribute to the root `UButton`, so clicking happens to work (proved by the passing companion - test) — but the component's declared API does nothing, and the wiring breaks the moment someone - adds `click` to `defineEmits` or a wrapper element. -- **Expected:** parent consumes the declared event. -- **Test:** `bugs.test.ts` — "emits load when CommentSeeMore fires its declared see-more-replies event" - (+ passing companion "see-more button click reaches the parent only via attribute fallthrough"). - Note: the pinned expectation is `@see-more-replies` → `load`; if the fix instead deletes the - custom emit and standardizes on `click`, retire this test alongside. -- **Fix direction:** `@see-more-replies="emit('load', ...)"` in CommentView (or drop the custom emit). -- **FIXED (2026-07-10):** `CommentView` now listens to the declared `@see-more-replies` event (routed through the bug-8 reveal-then-load handler); the attribute-fallthrough companion test was retired with the `@click` wiring it proved. - -## 10. Dismissing the delete dialog via ESC/overlay skips `cancel` — LOW - -- **Where:** `ConfirmDeleteCommentDialog.vue:36-42` -- **What happens:** only the Cancel button emits `cancel`; when `UModal` closes itself (ESC, - overlay click), the `open` model just flips to false. The panel's `@cancel="cleanupDeleteEvent"` - never runs, so `deleteTarget` stays stale. Low impact today (it is overwritten on the next - `confirmDelete` and currently unread — see bug 4), but the cleanup contract is broken. -- **Expected:** any dismissal path other than confirm behaves as cancel. -- **Test:** `bugs.test.ts` — "emits cancel when the modal is dismissed via ESC/overlay" -- **Fix direction:** watch the model for `true → false` transitions (or `@update:open`) and emit - `cancel` when not confirming. -- **FIXED (2026-07-10):** the dialog watches its `open` model and emits `cancel` on any `true → false` transition that is not confirm-driven; a `confirming` flag set by the Delete button suppresses a spurious cancel on confirm-close, and the Cancel button now just closes and lets the watcher emit. - -## 11. Enter always submits — a newline is impossible to type — MEDIUM - -- **Where:** `CommentInput.vue:28` -- **What happens:** `@keydown.enter.prevent="handleSubmit"` fires for every Enter, including - Shift+Enter (no `.exact`/modifier check), so multi-line comments cannot be typed at all despite - the control being a textarea with autoresize. -- **Expected:** Enter submits, Shift+Enter inserts a newline (or the reverse — but one path must - produce a newline). -- **Test:** `bugs.test.ts` — "inserts a newline instead of submitting on Shift+Enter" -- **Fix direction:** guard the handler: `if (event.shiftKey) return` before `preventDefault`, e.g. - a method receiving the event or `@keydown.enter.exact.prevent`. -- **FIXED (2026-07-10):** `@keydown.enter.exact.prevent` — plain Enter submits; Shift+Enter (or any modified Enter) skips the handler and its `preventDefault`, so the textarea inserts a newline and `autoresize` grows it. - -## 12. Input clears optimistically on submit — text lost if the parent rejects — LOW - -- **Where:** `CommentInput.vue:108-112` -- **What happens:** `handleSubmit` emits and immediately sets `comment.value = ""`. Once a backend - exists, any failed/rejected submission loses the user's text with no recovery. -- **Expected:** clear only after the parent accepts (async callback/prop) or restore on failure. -- **Test:** report-only (correct contract depends on the future async submit API). -- **Fix direction:** let the parent own clearing (e.g. `submit` handler returns a promise, or a - `clear()` expose). -- **FIXED (2026-07-10):** `CommentInput` no longer clears itself on submit; it `defineExpose`s `clear()` and parents own clearing. The panel's top-level composer calls `clear()` via a template ref after emitting `create` — acceptance is synchronous-optimistic today; restore-on-failure lands with the backend at the `CommentsSection` `runOptimistic` seam. `CommentView`'s reply/edit inputs unmount on close (`v-if`) and re-seed from `initialText` on reopen, so they need no `clear()` call. - -## 13. "Edit your reply to X" placeholder branch is unreachable — LOW - -- **Where:** `CommentInput.vue:96-99` vs the edit usage in `CommentView.vue:19-26` -- **What happens:** the placeholder branch for `isEditing && target` exists, but `CommentView` - never passes `:target` together with `is-editing`, and no other call site does. Dead branch in - production; only the isolated component test exercises it. -- **Expected:** either pass `:target="author.name"`-equivalent when editing a reply, or drop the branch. -- **Test:** report-only (which side is wrong is a product decision). -- **Fix direction:** pass the replied-to author when editing nested replies, or delete the branch. -- **FIXED (2026-07-10):** the recursive `CommentView` passes `:parent-author-name="comment.author.name"` down (new optional `parentAuthorName` prop) and the edit input receives `:target="parentAuthorName"`, so editing a nested reply shows "Edit your reply to X" while top-level edits keep the plain placeholder. The reply input's `:target` (own author) is unchanged. - -## 14. `CommentActions.replyCount` is a dead prop — LOW - -- **Where:** `CommentActions.vue:40` (fed from `CommentView.vue:33`) -- **What happens:** `CommentView` passes `:reply-count="replyPagination.count"` and the prop is - declared, but the template/computed never use it — the Reply button shows no count. -- **Expected:** show the count next to Reply, or remove the prop. -- **Test:** report-only (dead API, no behavior to pin). -- **Fix direction:** render it (`Reply · {{ replyCount }}`) or delete prop + binding. -- **FIXED (2026-07-10):** the Reply button now renders `Reply · N` when the count is positive (plain `Reply` at zero), mirroring how the Like button renders `likes`. - -## 15. URLs with balanced parentheses get their closing paren stripped — LOW - -- **Where:** `CommentTextRepresentation.vue:8-11` (`splitTrailingPunct`) -- **What happens:** the trailing-punctuation stripper unconditionally removes `)` (and friends) - from the end of a URL, so `https://en.wikipedia.org/wiki/Rust_(programming_language)` links to - `.../Rust_(programming_language` — a broken page. Stripping is only correct when the paren is - unbalanced (the `(see https://example.com).` case the baseline test covers). -- **Expected:** keep trailing `)`/`]` when balanced by an opener inside the URL. -- **Test:** `bugs.test.ts` — "keeps the closing paren of a URL with balanced parentheses" -- **Fix direction:** standard linkifier heuristic — count openers/closers in the URL and re-attach - closers up to balance. -- **FIXED (2026-07-10):** `splitTrailingPunct` re-attaches trailing `)`/`]`/`}` closers while the URL contains more matching openers than closers; unbalanced closers are still stripped, keeping the baseline `(see https://example.com).` behavior. - -## 16. Default settings drift: panel (3 / 2) vs view (4 / 5) — LOW - -- **Where:** `CommentsPanel.vue:38-43` vs `CommentView.vue:96-110` -- **What happens:** `maximumNested`/`maximumShownRepliesPerLevel` default to 3/2 on the panel but - 4/5 on the view. A standalone `CommentView` behaves differently from a panel-hosted one, and - combined with bug 5 a single panel-hosted tree currently mixes 2 (top level) with 5 (below). -- **Expected:** one source of truth for defaults. -- **Test:** report-only (fold into the bug-5 fix). -- **Fix direction:** export shared default constants from `types.ts` and use them in both components. -- **FIXED (2026-07-10):** `COMMENT_TREE_DEFAULTS` (3 / 2 — the panel's values) exported from `types.ts` and used by both `CommentsPanel` and `CommentView` `withDefaults`. - ---- - -## Leads investigated and CLEARED (not bugs) - -- **`maximumNested - 1` going below 0** — children are only rendered when `maximumNested > 0`, so - a value below 0 is never instantiated; no functional effect. -- **Recursive `v-bind="reply"` prop leakage** — `Comment` keys don't overlap `CommentViewSettings` - keys, and the explicit bindings after `v-bind` take precedence; no clash between a reply's own - `replyPagination`/`permissions` and panel-level settings. -- **`remainingReplies` double-count at visible depths** — `count - shownReplies.length` is - arithmetically consistent with fixtures where `replyPagination.count` equals total replies - (the label "See N more replies" matches what is hidden); the residual `load`-semantics concern - is captured under bug 8. -- **Edit mode showing stale content after a prop update** — the edit `CommentInput` is `v-if`- - mounted per editing session, so it re-reads `content` each time it opens; while open, ignoring - external content changes is acceptable editing behavior. -- **`formatRelativeDate` edge cases** — future dates (all diffs negative) fall through every - `> 0` check and return "just now", which degrades gracefully; boundaries are already covered by - `app/utils/formatters.test.ts`. -- **Double-`write` on the panel toast path for logged-out users** — inputs never open while - read-only, so the submit/cancel double-emit (bug 6) cannot cause double toasts today. diff --git a/apps/modeling-commons-frontend/app/components/comment/bugs.test.ts b/apps/modeling-commons-frontend/app/components/comment/bugs.test.ts index 62413407..a7184a25 100644 --- a/apps/modeling-commons-frontend/app/components/comment/bugs.test.ts +++ b/apps/modeling-commons-frontend/app/components/comment/bugs.test.ts @@ -33,20 +33,18 @@ beforeEach(() => { }); describe("CommentsPanel bugs", () => { - // BUG-1 (fixed): remainingComments reads pagination.count (clamped at 0), + // remainingComments reads pagination.count (clamped at 0), // so the load-more button renders whenever the server holds more comments. it("shows a load-more button when pagination.count exceeds the shown comments", async () => { const wrapper = await mountCommentsPanel(flatComments, { pagination: { count: 12, lastPage: 4 }, }); - const loadMore = wrapper - .findAll("button") - .find((button) => button.text().includes("9")); + const loadMore = wrapper.findAll("button").find((button) => button.text().includes("9")); expect(loadMore, "expected a load-more button showing the 9-comment remainder").toBeDefined(); }); - // BUG-2 (fixed): handleDelete closes the dialog and resets `deleting` in a + // handleDelete closes the dialog and resets `deleting` in a // finally, so the dialog cannot get stuck open or permanently loading. it("closes the delete dialog and resets the loading state after confirm", async () => { const wrapper = await mountCommentsPanel(flatComments); @@ -64,7 +62,7 @@ describe("CommentsPanel bugs", () => { expect(dialog.props("deleting")).toBe(false); }); - // BUG-12 (fixed): CommentInput no longer clears itself on submit; the panel + // CommentInput no longer clears itself on submit; the panel // clears its top-level composer only after emitting create. (Restore-on-failure // lands with the backend at the CommentsSection runOptimistic seam.) it("clears the top-level composer after emitting create", async () => { @@ -80,7 +78,7 @@ describe("CommentsPanel bugs", () => { }); describe("CommentsSection bugs", () => { - // BUG-3 (fixed): the effective read-only state (`props.readOnly || !isLoggedIn`) + // the effective read-only state (`props.readOnly || !isLoggedIn`) // now lives on CommentsSection, so a consumer can force a read-only discussion // for a logged-in user. it("respects an explicit readOnly prop even when the user is logged in", async () => { @@ -92,7 +90,7 @@ describe("CommentsSection bugs", () => { }); describe("CommentView bugs", () => { - // BUG-5 (fixed): the recursive render must pass + // the recursive render must pass // maximum-shown-replies-per-level down so every depth uses the configured value. it("propagates maximumShownRepliesPerLevel to nested reply levels", async () => { const wrapper = await mountCommentView(deepThread, { maximumShownRepliesPerLevel: 1 }); @@ -104,7 +102,7 @@ describe("CommentView bugs", () => { expect(nested!.props("maximumShownRepliesPerLevel")).toBe(1); }); - // BUG-6 (fixed): `write` fires only on genuine write attempts — closing or + // `write` fires only on genuine write attempts — closing or // cancelling an input must not emit it. it("does not emit write when the reply input is closed via cancel", async () => { const wrapper = await mountCommentView(shortComment); @@ -119,7 +117,7 @@ describe("CommentView bugs", () => { expect(wrapper.emitted("write")).toHaveLength(1); }); - // BUG-7 (fixed): readOnly gates only the open transition, so an input that is + // readOnly gates only the open transition, so an input that is // already open stays closable if readOnly flips (e.g. session expiry). it("still allows closing an open reply input after readOnly becomes true", async () => { const wrapper = await mountCommentView(shortComment); @@ -137,7 +135,7 @@ describe("CommentView bugs", () => { expect(wrapper.findComponent(CommentInput).exists()).toBe(false); }); - // BUG-9 (fixed): CommentView consumes CommentSeeMore's declared + // CommentView consumes CommentSeeMore's declared // see-more-replies emit. `load` fires once all loaded replies are revealed // and only a server remainder is left. (The former attribute-fallthrough // companion test is retired — the @click wiring it proved no longer exists.) @@ -191,7 +189,7 @@ describe("CommentView bugs", () => { expect(wrapper.emitted("load")).toEqual([[{ commentId: partiallyLoaded.id }]]); }); - // BUG-13 (fixed): the recursive render passes the parent comment's author + // the recursive render passes the parent comment's author // name down, so editing a nested reply names the parent author. it("names the parent author in the edit placeholder of a nested reply", async () => { const wrapper = await mountCommentView(deepThread); @@ -206,12 +204,10 @@ describe("CommentView bugs", () => { await nextTick(); const editInput = nested!.findComponent(CommentInput); - expect(editInput.find("textarea").attributes("placeholder")).toContain( - deepThread.author.name, - ); + expect(editInput.find("textarea").attributes("placeholder")).toContain(deepThread.author.name); }); - // BUG-13 (fixed): top-level comments have no parent, so their edit + // top-level comments have no parent, so their edit // placeholder must not name anyone. it("keeps the edit placeholder free of author names for top-level comments", async () => { const wrapper = await mountCommentView(deepThread); @@ -227,7 +223,7 @@ describe("CommentView bugs", () => { }); describe("ConfirmDeleteCommentDialog bugs", () => { - // BUG-10 (fixed): any close that is not confirm-driven (ESC, overlay click, + // any close that is not confirm-driven (ESC, overlay click, // Cancel button) emits `cancel` so the parent's cleanup always runs. it("emits cancel when the modal is dismissed via ESC/overlay", async () => { const wrapper = await mountSuspended(ConfirmDeleteCommentDialog, { @@ -267,7 +263,7 @@ describe("ConfirmDeleteCommentDialog bugs", () => { }); describe("CommentInput bugs", () => { - // BUG-11 (fixed): Enter submits via `.exact`, so Shift+Enter falls through + // Enter submits via `.exact`, so Shift+Enter falls through // to the textarea and inserts a newline. it("inserts a newline instead of submitting on Shift+Enter", async () => { const wrapper = await mountSuspended(CommentInput, { props: {} }); @@ -281,7 +277,7 @@ describe("CommentInput bugs", () => { }); describe("CommentTextRepresentation bugs", () => { - // BUG-15 (fixed): trailing closers balanced by an opener inside the URL are + // trailing closers balanced by an opener inside the URL are // kept (e.g. Wikipedia links); unbalanced ones are still stripped. it("keeps the closing paren of a URL with balanced parentheses", async () => { const url = "https://en.wikipedia.org/wiki/Rust_(programming_language)"; From 263e8d7274589a08328931d3c726952568b532f0 Mon Sep 17 00:00:00 2001 From: Omar Ibrahim Date: Mon, 20 Jul 2026 13:37:51 -0500 Subject: [PATCH 14/51] Feat(commons): add ModelComment and ModelCommentLike schema --- .../generated/prisma/edge.js | 31 +- .../generated/prisma/index-browser.js | 23 + .../generated/prisma/index.d.ts | 6766 ++++++++++++++--- .../generated/prisma/index.js | 31 +- .../generated/prisma/package.json | 2 +- .../generated/prisma/schema.prisma | 45 + .../migration.sql | 56 + .../prisma/schema.prisma | 45 + 8 files changed, 5930 insertions(+), 1069 deletions(-) create mode 100644 apps/modeling-commons-backend/prisma/migrations/20260720000000_add_model_comment/migration.sql diff --git a/apps/modeling-commons-backend/generated/prisma/edge.js b/apps/modeling-commons-backend/generated/prisma/edge.js index 0c816b6b..e0e62511 100644 --- a/apps/modeling-commons-backend/generated/prisma/edge.js +++ b/apps/modeling-commons-backend/generated/prisma/edge.js @@ -271,6 +271,27 @@ exports.Prisma.ModelDraftScalarFieldEnum = { updatedAt: 'updatedAt' }; +exports.Prisma.ModelCommentScalarFieldEnum = { + id: 'id', + legacyId: 'legacyId', + parentId: 'parentId', + userId: 'userId', + modelId: 'modelId', + versionNumber: 'versionNumber', + content: 'content', + likesCount: 'likesCount', + createdAt: 'createdAt', + updatedAt: 'updatedAt', + editedAt: 'editedAt', + deletedAt: 'deletedAt' +}; + +exports.Prisma.ModelCommentLikeScalarFieldEnum = { + modelCommentId: 'modelCommentId', + userId: 'userId', + createdAt: 'createdAt' +}; + exports.Prisma.EventScalarFieldEnum = { id: 'id', type: 'type', @@ -369,6 +390,8 @@ exports.Prisma.ModelName = { ModelLike: 'ModelLike', ModelInteraction: 'ModelInteraction', ModelDraft: 'ModelDraft', + ModelComment: 'ModelComment', + ModelCommentLike: 'ModelCommentLike', Event: 'Event' }; /** @@ -379,14 +402,14 @@ const config = { "clientVersion": "7.8.0", "engineVersion": "3c6e192761c0362d496ed980de936e2f3cebcd3a", "activeProvider": "postgresql", - "inlineSchema": "generator client {\n provider = \"prisma-client-js\"\n output = \"../generated/prisma\"\n}\n\ndatasource db {\n provider = \"postgresql\"\n}\n\n// Enums\n\nenum ModelVisibility {\n public\n private\n unlisted\n}\n\nenum SystemRole {\n admin\n moderator\n user\n}\n\nenum UserKind {\n student\n teacher\n researcher\n other\n}\n\nenum AuthorRole {\n owner\n contributor\n}\n\nenum PermissionLevel {\n read\n write\n admin\n}\n\nenum ModelInteractionKind {\n view\n run\n download\n share\n}\n\nenum ModelFileKind {\n model\n additional\n}\n\n// Better Auth core tables\n\nmodel User {\n id String @id @default(uuid())\n name String?\n email String? @unique\n emailVerified Boolean @default(false)\n image String?\n createdAt DateTime @default(now()) @db.Timestamptz(3)\n updatedAt DateTime @updatedAt @db.Timestamptz(3)\n\n // Extended fields\n systemRole SystemRole @default(user)\n userKind UserKind @default(other)\n isProfilePublic Boolean @default(false)\n deletedAt DateTime?\n\n // User profile fields\n bio String?\n country String?\n socialLinks Json? // e.g. [{ platform: 'twitter', url: '...' }]\n dob DateTime? @db.Date\n affiliation String?\n\n // Better Auth relations\n accounts Account[]\n sessions Session[]\n verifications Verification[]\n\n // Domain relations\n authoredModels ModelAuthor[]\n grantedPermissions ModelPermission[]\n events Event[]\n modelLikes ModelLike[]\n modelInteractions ModelInteraction[]\n modelDrafts ModelDraft[]\n\n // Better Auth Admin plugin\n role String?\n banned Boolean?\n banReason String?\n banExpires DateTime? @db.Timestamptz(3)\n\n // Application behavior\n onboardedAt DateTime? @db.Timestamptz(3)\n legacyId Int? @unique\n\n // Passkey relations\n passkeys Passkey[]\n}\n\nmodel Account {\n id String @id @default(uuid())\n userId String\n accountId String\n providerId String\n accessToken String?\n refreshToken String?\n accessTokenExpiresAt DateTime? @db.Timestamptz(3)\n refreshTokenExpiresAt DateTime? @db.Timestamptz(3)\n scope String?\n idToken String?\n password String?\n createdAt DateTime @default(now()) @db.Timestamptz(3)\n updatedAt DateTime @updatedAt @db.Timestamptz(3)\n\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n\n @@index([userId])\n}\n\nmodel Session {\n id String @id @default(uuid())\n userId String\n expiresAt DateTime\n token String @unique\n ipAddress String?\n userAgent String?\n createdAt DateTime @default(now()) @db.Timestamptz(3)\n updatedAt DateTime @updatedAt @db.Timestamptz(3)\n\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n\n // Better Auth Admin plugin fields\n impersonatedBy String?\n\n @@index([userId])\n}\n\nmodel Verification {\n id String @id @default(uuid())\n identifier String\n value String\n expiresAt DateTime\n createdAt DateTime? @default(now()) @db.Timestamptz(3)\n updatedAt DateTime? @updatedAt @db.Timestamptz(3)\n\n user User? @relation(fields: [userId], references: [id], onDelete: Cascade)\n userId String?\n\n @@index([userId])\n}\n\nmodel Passkey {\n id String @id @default(uuid())\n name String?\n publicKey String\n userId String\n credentialID String\n counter Int\n deviceType String\n backedUp Boolean\n transports String?\n createdAt DateTime? @default(now()) @db.Timestamptz(3)\n aaguid String?\n\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n}\n\n// Domain models\n\nmodel Model {\n id String @id @default(uuid())\n legacyId Int? @unique\n latestVersionNumber Int?\n parentModelId String?\n parentVersionNumber Int?\n visibility ModelVisibility @default(public)\n isEndorsed Boolean @default(false)\n isLibraryModel Boolean @default(false)\n viewCount Int @default(0)\n runCount Int @default(0)\n downloadCount Int @default(0)\n shareCount Int @default(0)\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n deletedAt DateTime?\n\n latestVersion ModelVersion? @relation(\"LatestVersion\", fields: [id, latestVersionNumber], references: [modelId, versionNumber])\n parentModel Model? @relation(\"ModelParent\", fields: [parentModelId], references: [id])\n childModels Model[] @relation(\"ModelParent\")\n parentVersion ModelVersion? @relation(\"ParentVersion\", fields: [parentModelId, parentVersionNumber], references: [modelId, versionNumber])\n\n versions ModelVersion[] @relation(\"ModelVersions\")\n authors ModelAuthor[]\n permissions ModelPermission[]\n additionalFiles ModelAdditionalFile[]\n likes ModelLike[]\n interactions ModelInteraction[]\n drafts ModelDraft[]\n\n @@unique([id, latestVersionNumber])\n @@index([parentModelId])\n @@index([parentModelId, parentVersionNumber])\n @@index([viewCount])\n @@index([runCount])\n @@index([downloadCount])\n}\n\nmodel ModelVersion {\n modelId String\n versionNumber Int\n title String\n description String?\n changeSummary String?\n previewImageFileKey String?\n netlogoFileKey String\n netlogoVersion String?\n infoTab String?\n createdAt DateTime @default(now())\n finalizedAt DateTime?\n\n model Model @relation(\"ModelVersions\", fields: [modelId], references: [id], onDelete: Cascade)\n\n // Reverse relations\n latestOfModel Model? @relation(\"LatestVersion\")\n parentOfModels Model[] @relation(\"ParentVersion\")\n\n tags ModelVersionTag[]\n taggedAdditionalFiles ModelAdditionalFile[]\n\n @@id([modelId, versionNumber])\n @@index([modelId])\n}\n\nmodel ModelVersionTag {\n modelId String\n versionNumber Int\n tagId String\n createdAt DateTime @default(now())\n\n modelVersion ModelVersion @relation(fields: [modelId, versionNumber], references: [modelId, versionNumber], onDelete: Cascade)\n tag Tag @relation(fields: [tagId], references: [id], onDelete: Cascade)\n\n @@id([modelId, versionNumber, tagId])\n @@index([tagId])\n}\n\nmodel ModelAdditionalFile {\n id String @id @default(uuid())\n modelId String\n taggedVersionNumber Int\n fileKey String\n kind ModelFileKind @default(additional)\n createdAt DateTime @default(now())\n\n model Model @relation(fields: [modelId], references: [id], onDelete: Cascade)\n taggedVersion ModelVersion @relation(fields: [modelId, taggedVersionNumber], references: [modelId, versionNumber])\n\n @@index([modelId])\n @@index([modelId, taggedVersionNumber])\n}\n\nmodel Tag {\n id String @id @default(uuid())\n legacyId Int? @unique\n name String @unique\n displayName String?\n createdAt DateTime @default(now())\n\n modelVersions ModelVersionTag[]\n}\n\nmodel ModelAuthor {\n modelId String\n userId String\n role AuthorRole\n createdAt DateTime @default(now())\n\n model Model @relation(fields: [modelId], references: [id], onDelete: Cascade)\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n\n @@id([modelId, userId])\n @@index([userId])\n}\n\nmodel ModelPermission {\n id String @id @default(uuid())\n modelId String\n granteeUserId String?\n permissionLevel PermissionLevel\n createdAt DateTime @default(now())\n\n model Model @relation(fields: [modelId], references: [id], onDelete: Cascade)\n granteeUser User? @relation(fields: [granteeUserId], references: [id], onDelete: Cascade)\n\n @@unique([modelId, granteeUserId])\n @@index([modelId])\n @@index([granteeUserId])\n}\n\nmodel ModelLike {\n modelId String\n userId String\n createdAt DateTime @default(now()) @db.Timestamptz(3)\n\n model Model @relation(fields: [modelId], references: [id], onDelete: Cascade)\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n\n @@id([modelId, userId])\n @@index([userId])\n @@index([modelId, createdAt])\n}\n\nmodel ModelInteraction {\n id String @id @default(uuid())\n modelId String\n versionNumber Int?\n kind ModelInteractionKind\n userId String?\n sessionId String?\n ipHash String?\n userAgent String?\n referer String?\n geo Json?\n cookie String?\n\n createdAt DateTime @default(now()) @db.Timestamptz(3)\n\n model Model @relation(fields: [modelId], references: [id], onDelete: Cascade)\n user User? @relation(fields: [userId], references: [id], onDelete: SetNull)\n\n @@index([modelId, kind, createdAt])\n @@index([modelId, kind, userId])\n @@index([userId, createdAt])\n @@index([createdAt])\n}\n\nmodel ModelDraft {\n id String @id @default(cuid())\n userId String\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n\n modelId String?\n model Model? @relation(fields: [modelId], references: [id], onDelete: Cascade)\n\n schemaVersion Int\n data Json\n\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n @@index([userId])\n @@index([modelId])\n}\n\nmodel Event {\n id String @id @default(uuid())\n type String\n actorId String\n resourceType String\n resourceId String\n payload Json\n createdAt DateTime @default(now())\n processedAt DateTime?\n\n actor User @relation(fields: [actorId], references: [id])\n\n @@index([actorId])\n @@index([resourceType, resourceId])\n @@index([type])\n @@index([processedAt])\n}\n" + "inlineSchema": "generator client {\n provider = \"prisma-client-js\"\n output = \"../generated/prisma\"\n}\n\ndatasource db {\n provider = \"postgresql\"\n}\n\n// Enums\n\nenum ModelVisibility {\n public\n private\n unlisted\n}\n\nenum SystemRole {\n admin\n moderator\n user\n}\n\nenum UserKind {\n student\n teacher\n researcher\n other\n}\n\nenum AuthorRole {\n owner\n contributor\n}\n\nenum PermissionLevel {\n read\n write\n admin\n}\n\nenum ModelInteractionKind {\n view\n run\n download\n share\n}\n\nenum ModelFileKind {\n model\n additional\n}\n\n// Better Auth core tables\n\nmodel User {\n id String @id @default(uuid())\n name String?\n email String? @unique\n emailVerified Boolean @default(false)\n image String?\n createdAt DateTime @default(now()) @db.Timestamptz(3)\n updatedAt DateTime @updatedAt @db.Timestamptz(3)\n\n // Extended fields\n systemRole SystemRole @default(user)\n userKind UserKind @default(other)\n isProfilePublic Boolean @default(false)\n deletedAt DateTime?\n\n // User profile fields\n bio String?\n country String?\n socialLinks Json? // e.g. [{ platform: 'twitter', url: '...' }]\n dob DateTime? @db.Date\n affiliation String?\n\n // Better Auth relations\n accounts Account[]\n sessions Session[]\n verifications Verification[]\n\n // Domain relations\n authoredModels ModelAuthor[]\n grantedPermissions ModelPermission[]\n events Event[]\n modelLikes ModelLike[]\n modelInteractions ModelInteraction[]\n modelDrafts ModelDraft[]\n comments ModelComment[]\n commentLikes ModelCommentLike[]\n\n // Better Auth Admin plugin\n role String?\n banned Boolean?\n banReason String?\n banExpires DateTime? @db.Timestamptz(3)\n\n // Application behavior\n onboardedAt DateTime? @db.Timestamptz(3)\n legacyId Int? @unique\n\n // Passkey relations\n passkeys Passkey[]\n}\n\nmodel Account {\n id String @id @default(uuid())\n userId String\n accountId String\n providerId String\n accessToken String?\n refreshToken String?\n accessTokenExpiresAt DateTime? @db.Timestamptz(3)\n refreshTokenExpiresAt DateTime? @db.Timestamptz(3)\n scope String?\n idToken String?\n password String?\n createdAt DateTime @default(now()) @db.Timestamptz(3)\n updatedAt DateTime @updatedAt @db.Timestamptz(3)\n\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n\n @@index([userId])\n}\n\nmodel Session {\n id String @id @default(uuid())\n userId String\n expiresAt DateTime\n token String @unique\n ipAddress String?\n userAgent String?\n createdAt DateTime @default(now()) @db.Timestamptz(3)\n updatedAt DateTime @updatedAt @db.Timestamptz(3)\n\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n\n // Better Auth Admin plugin fields\n impersonatedBy String?\n\n @@index([userId])\n}\n\nmodel Verification {\n id String @id @default(uuid())\n identifier String\n value String\n expiresAt DateTime\n createdAt DateTime? @default(now()) @db.Timestamptz(3)\n updatedAt DateTime? @updatedAt @db.Timestamptz(3)\n\n user User? @relation(fields: [userId], references: [id], onDelete: Cascade)\n userId String?\n\n @@index([userId])\n}\n\nmodel Passkey {\n id String @id @default(uuid())\n name String?\n publicKey String\n userId String\n credentialID String\n counter Int\n deviceType String\n backedUp Boolean\n transports String?\n createdAt DateTime? @default(now()) @db.Timestamptz(3)\n aaguid String?\n\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n}\n\n// Domain models\n\nmodel Model {\n id String @id @default(uuid())\n legacyId Int? @unique\n latestVersionNumber Int?\n parentModelId String?\n parentVersionNumber Int?\n visibility ModelVisibility @default(public)\n isEndorsed Boolean @default(false)\n isLibraryModel Boolean @default(false)\n viewCount Int @default(0)\n runCount Int @default(0)\n downloadCount Int @default(0)\n shareCount Int @default(0)\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n deletedAt DateTime?\n\n latestVersion ModelVersion? @relation(\"LatestVersion\", fields: [id, latestVersionNumber], references: [modelId, versionNumber])\n parentModel Model? @relation(\"ModelParent\", fields: [parentModelId], references: [id])\n childModels Model[] @relation(\"ModelParent\")\n parentVersion ModelVersion? @relation(\"ParentVersion\", fields: [parentModelId, parentVersionNumber], references: [modelId, versionNumber])\n\n versions ModelVersion[] @relation(\"ModelVersions\")\n authors ModelAuthor[]\n permissions ModelPermission[]\n additionalFiles ModelAdditionalFile[]\n likes ModelLike[]\n interactions ModelInteraction[]\n drafts ModelDraft[]\n comments ModelComment[]\n\n @@unique([id, latestVersionNumber])\n @@index([parentModelId])\n @@index([parentModelId, parentVersionNumber])\n @@index([viewCount])\n @@index([runCount])\n @@index([downloadCount])\n}\n\nmodel ModelVersion {\n modelId String\n versionNumber Int\n title String\n description String?\n changeSummary String?\n previewImageFileKey String?\n netlogoFileKey String\n netlogoVersion String?\n infoTab String?\n createdAt DateTime @default(now())\n finalizedAt DateTime?\n\n model Model @relation(\"ModelVersions\", fields: [modelId], references: [id], onDelete: Cascade)\n\n // Reverse relations\n latestOfModel Model? @relation(\"LatestVersion\")\n parentOfModels Model[] @relation(\"ParentVersion\")\n\n tags ModelVersionTag[]\n taggedAdditionalFiles ModelAdditionalFile[]\n\n @@id([modelId, versionNumber])\n @@index([modelId])\n}\n\nmodel ModelVersionTag {\n modelId String\n versionNumber Int\n tagId String\n createdAt DateTime @default(now())\n\n modelVersion ModelVersion @relation(fields: [modelId, versionNumber], references: [modelId, versionNumber], onDelete: Cascade)\n tag Tag @relation(fields: [tagId], references: [id], onDelete: Cascade)\n\n @@id([modelId, versionNumber, tagId])\n @@index([tagId])\n}\n\nmodel ModelAdditionalFile {\n id String @id @default(uuid())\n modelId String\n taggedVersionNumber Int\n fileKey String\n kind ModelFileKind @default(additional)\n createdAt DateTime @default(now())\n\n model Model @relation(fields: [modelId], references: [id], onDelete: Cascade)\n taggedVersion ModelVersion @relation(fields: [modelId, taggedVersionNumber], references: [modelId, versionNumber])\n\n @@index([modelId])\n @@index([modelId, taggedVersionNumber])\n}\n\nmodel Tag {\n id String @id @default(uuid())\n legacyId Int? @unique\n name String @unique\n displayName String?\n createdAt DateTime @default(now())\n\n modelVersions ModelVersionTag[]\n}\n\nmodel ModelAuthor {\n modelId String\n userId String\n role AuthorRole\n createdAt DateTime @default(now())\n\n model Model @relation(fields: [modelId], references: [id], onDelete: Cascade)\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n\n @@id([modelId, userId])\n @@index([userId])\n}\n\nmodel ModelPermission {\n id String @id @default(uuid())\n modelId String\n granteeUserId String?\n permissionLevel PermissionLevel\n createdAt DateTime @default(now())\n\n model Model @relation(fields: [modelId], references: [id], onDelete: Cascade)\n granteeUser User? @relation(fields: [granteeUserId], references: [id], onDelete: Cascade)\n\n @@unique([modelId, granteeUserId])\n @@index([modelId])\n @@index([granteeUserId])\n}\n\nmodel ModelLike {\n modelId String\n userId String\n createdAt DateTime @default(now()) @db.Timestamptz(3)\n\n model Model @relation(fields: [modelId], references: [id], onDelete: Cascade)\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n\n @@id([modelId, userId])\n @@index([userId])\n @@index([modelId, createdAt])\n}\n\nmodel ModelInteraction {\n id String @id @default(uuid())\n modelId String\n versionNumber Int?\n kind ModelInteractionKind\n userId String?\n sessionId String?\n ipHash String?\n userAgent String?\n referer String?\n geo Json?\n cookie String?\n\n createdAt DateTime @default(now()) @db.Timestamptz(3)\n\n model Model @relation(fields: [modelId], references: [id], onDelete: Cascade)\n user User? @relation(fields: [userId], references: [id], onDelete: SetNull)\n\n @@index([modelId, kind, createdAt])\n @@index([modelId, kind, userId])\n @@index([userId, createdAt])\n @@index([createdAt])\n}\n\nmodel ModelDraft {\n id String @id @default(cuid())\n userId String\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n\n modelId String?\n model Model? @relation(fields: [modelId], references: [id], onDelete: Cascade)\n\n schemaVersion Int\n data Json\n\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n @@index([userId])\n @@index([modelId])\n}\n\nmodel ModelComment {\n id String @id @default(uuid())\n legacyId Int? @unique\n\n parentId String?\n userId String?\n\n modelId String\n versionNumber Int?\n\n content String? @db.Text\n likesCount Int @default(0)\n\n createdAt DateTime @default(now()) @db.Timestamptz(3)\n updatedAt DateTime @updatedAt @db.Timestamptz(3)\n editedAt DateTime? @db.Timestamptz(3)\n deletedAt DateTime? @db.Timestamptz(3)\n\n model Model @relation(fields: [modelId], references: [id], onDelete: Cascade)\n user User? @relation(fields: [userId], references: [id], onDelete: SetNull)\n parent ModelComment? @relation(\"CommentReplies\", fields: [parentId], references: [id], onDelete: Cascade)\n replies ModelComment[] @relation(\"CommentReplies\")\n\n likes ModelCommentLike[]\n\n @@index([modelId, parentId, createdAt])\n @@index([parentId])\n @@index([userId])\n}\n\nmodel ModelCommentLike {\n modelCommentId String\n userId String\n createdAt DateTime @default(now()) @db.Timestamptz(3)\n\n modelComment ModelComment @relation(fields: [modelCommentId], references: [id], onDelete: Cascade)\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n\n @@id([modelCommentId, userId])\n @@index([userId])\n}\n\nmodel Event {\n id String @id @default(uuid())\n type String\n actorId String\n resourceType String\n resourceId String\n payload Json\n createdAt DateTime @default(now())\n processedAt DateTime?\n\n actor User @relation(fields: [actorId], references: [id])\n\n @@index([actorId])\n @@index([resourceType, resourceId])\n @@index([type])\n @@index([processedAt])\n}\n" } -config.runtimeDataModel = JSON.parse("{\"models\":{\"User\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"email\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"emailVerified\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"image\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"systemRole\",\"kind\":\"enum\",\"type\":\"SystemRole\"},{\"name\":\"userKind\",\"kind\":\"enum\",\"type\":\"UserKind\"},{\"name\":\"isProfilePublic\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"deletedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"bio\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"country\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"socialLinks\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"dob\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"affiliation\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"accounts\",\"kind\":\"object\",\"type\":\"Account\",\"relationName\":\"AccountToUser\"},{\"name\":\"sessions\",\"kind\":\"object\",\"type\":\"Session\",\"relationName\":\"SessionToUser\"},{\"name\":\"verifications\",\"kind\":\"object\",\"type\":\"Verification\",\"relationName\":\"UserToVerification\"},{\"name\":\"authoredModels\",\"kind\":\"object\",\"type\":\"ModelAuthor\",\"relationName\":\"ModelAuthorToUser\"},{\"name\":\"grantedPermissions\",\"kind\":\"object\",\"type\":\"ModelPermission\",\"relationName\":\"ModelPermissionToUser\"},{\"name\":\"events\",\"kind\":\"object\",\"type\":\"Event\",\"relationName\":\"EventToUser\"},{\"name\":\"modelLikes\",\"kind\":\"object\",\"type\":\"ModelLike\",\"relationName\":\"ModelLikeToUser\"},{\"name\":\"modelInteractions\",\"kind\":\"object\",\"type\":\"ModelInteraction\",\"relationName\":\"ModelInteractionToUser\"},{\"name\":\"modelDrafts\",\"kind\":\"object\",\"type\":\"ModelDraft\",\"relationName\":\"ModelDraftToUser\"},{\"name\":\"role\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"banned\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"banReason\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"banExpires\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"onboardedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"legacyId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"passkeys\",\"kind\":\"object\",\"type\":\"Passkey\",\"relationName\":\"PasskeyToUser\"}],\"dbName\":null},\"Account\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"accountId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"providerId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"accessToken\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"refreshToken\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"accessTokenExpiresAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"refreshTokenExpiresAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"scope\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"idToken\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"password\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"AccountToUser\"}],\"dbName\":null},\"Session\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"expiresAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"token\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"ipAddress\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"userAgent\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"SessionToUser\"},{\"name\":\"impersonatedBy\",\"kind\":\"scalar\",\"type\":\"String\"}],\"dbName\":null},\"Verification\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"identifier\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"value\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"expiresAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"UserToVerification\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"}],\"dbName\":null},\"Passkey\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"publicKey\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"credentialID\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"counter\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"deviceType\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"backedUp\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"transports\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"aaguid\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"PasskeyToUser\"}],\"dbName\":null},\"Model\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"legacyId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"latestVersionNumber\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"parentModelId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"parentVersionNumber\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"visibility\",\"kind\":\"enum\",\"type\":\"ModelVisibility\"},{\"name\":\"isEndorsed\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"isLibraryModel\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"viewCount\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"runCount\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"downloadCount\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"shareCount\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"deletedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"latestVersion\",\"kind\":\"object\",\"type\":\"ModelVersion\",\"relationName\":\"LatestVersion\"},{\"name\":\"parentModel\",\"kind\":\"object\",\"type\":\"Model\",\"relationName\":\"ModelParent\"},{\"name\":\"childModels\",\"kind\":\"object\",\"type\":\"Model\",\"relationName\":\"ModelParent\"},{\"name\":\"parentVersion\",\"kind\":\"object\",\"type\":\"ModelVersion\",\"relationName\":\"ParentVersion\"},{\"name\":\"versions\",\"kind\":\"object\",\"type\":\"ModelVersion\",\"relationName\":\"ModelVersions\"},{\"name\":\"authors\",\"kind\":\"object\",\"type\":\"ModelAuthor\",\"relationName\":\"ModelToModelAuthor\"},{\"name\":\"permissions\",\"kind\":\"object\",\"type\":\"ModelPermission\",\"relationName\":\"ModelToModelPermission\"},{\"name\":\"additionalFiles\",\"kind\":\"object\",\"type\":\"ModelAdditionalFile\",\"relationName\":\"ModelToModelAdditionalFile\"},{\"name\":\"likes\",\"kind\":\"object\",\"type\":\"ModelLike\",\"relationName\":\"ModelToModelLike\"},{\"name\":\"interactions\",\"kind\":\"object\",\"type\":\"ModelInteraction\",\"relationName\":\"ModelToModelInteraction\"},{\"name\":\"drafts\",\"kind\":\"object\",\"type\":\"ModelDraft\",\"relationName\":\"ModelToModelDraft\"}],\"dbName\":null},\"ModelVersion\":{\"fields\":[{\"name\":\"modelId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"versionNumber\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"title\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"description\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"changeSummary\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"previewImageFileKey\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"netlogoFileKey\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"netlogoVersion\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"infoTab\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"finalizedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"model\",\"kind\":\"object\",\"type\":\"Model\",\"relationName\":\"ModelVersions\"},{\"name\":\"latestOfModel\",\"kind\":\"object\",\"type\":\"Model\",\"relationName\":\"LatestVersion\"},{\"name\":\"parentOfModels\",\"kind\":\"object\",\"type\":\"Model\",\"relationName\":\"ParentVersion\"},{\"name\":\"tags\",\"kind\":\"object\",\"type\":\"ModelVersionTag\",\"relationName\":\"ModelVersionToModelVersionTag\"},{\"name\":\"taggedAdditionalFiles\",\"kind\":\"object\",\"type\":\"ModelAdditionalFile\",\"relationName\":\"ModelAdditionalFileToModelVersion\"}],\"dbName\":null},\"ModelVersionTag\":{\"fields\":[{\"name\":\"modelId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"versionNumber\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"tagId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"modelVersion\",\"kind\":\"object\",\"type\":\"ModelVersion\",\"relationName\":\"ModelVersionToModelVersionTag\"},{\"name\":\"tag\",\"kind\":\"object\",\"type\":\"Tag\",\"relationName\":\"ModelVersionTagToTag\"}],\"dbName\":null},\"ModelAdditionalFile\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"modelId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"taggedVersionNumber\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"fileKey\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"kind\",\"kind\":\"enum\",\"type\":\"ModelFileKind\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"model\",\"kind\":\"object\",\"type\":\"Model\",\"relationName\":\"ModelToModelAdditionalFile\"},{\"name\":\"taggedVersion\",\"kind\":\"object\",\"type\":\"ModelVersion\",\"relationName\":\"ModelAdditionalFileToModelVersion\"}],\"dbName\":null},\"Tag\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"legacyId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"displayName\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"modelVersions\",\"kind\":\"object\",\"type\":\"ModelVersionTag\",\"relationName\":\"ModelVersionTagToTag\"}],\"dbName\":null},\"ModelAuthor\":{\"fields\":[{\"name\":\"modelId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"role\",\"kind\":\"enum\",\"type\":\"AuthorRole\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"model\",\"kind\":\"object\",\"type\":\"Model\",\"relationName\":\"ModelToModelAuthor\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"ModelAuthorToUser\"}],\"dbName\":null},\"ModelPermission\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"modelId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"granteeUserId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"permissionLevel\",\"kind\":\"enum\",\"type\":\"PermissionLevel\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"model\",\"kind\":\"object\",\"type\":\"Model\",\"relationName\":\"ModelToModelPermission\"},{\"name\":\"granteeUser\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"ModelPermissionToUser\"}],\"dbName\":null},\"ModelLike\":{\"fields\":[{\"name\":\"modelId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"model\",\"kind\":\"object\",\"type\":\"Model\",\"relationName\":\"ModelToModelLike\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"ModelLikeToUser\"}],\"dbName\":null},\"ModelInteraction\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"modelId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"versionNumber\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"kind\",\"kind\":\"enum\",\"type\":\"ModelInteractionKind\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"sessionId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"ipHash\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"userAgent\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"referer\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"geo\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"cookie\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"model\",\"kind\":\"object\",\"type\":\"Model\",\"relationName\":\"ModelToModelInteraction\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"ModelInteractionToUser\"}],\"dbName\":null},\"ModelDraft\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"ModelDraftToUser\"},{\"name\":\"modelId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"model\",\"kind\":\"object\",\"type\":\"Model\",\"relationName\":\"ModelToModelDraft\"},{\"name\":\"schemaVersion\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"data\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"Event\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"type\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"actorId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"resourceType\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"resourceId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"payload\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"processedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"actor\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"EventToUser\"}],\"dbName\":null}},\"enums\":{},\"types\":{}}") +config.runtimeDataModel = JSON.parse("{\"models\":{\"User\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"email\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"emailVerified\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"image\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"systemRole\",\"kind\":\"enum\",\"type\":\"SystemRole\"},{\"name\":\"userKind\",\"kind\":\"enum\",\"type\":\"UserKind\"},{\"name\":\"isProfilePublic\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"deletedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"bio\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"country\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"socialLinks\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"dob\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"affiliation\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"accounts\",\"kind\":\"object\",\"type\":\"Account\",\"relationName\":\"AccountToUser\"},{\"name\":\"sessions\",\"kind\":\"object\",\"type\":\"Session\",\"relationName\":\"SessionToUser\"},{\"name\":\"verifications\",\"kind\":\"object\",\"type\":\"Verification\",\"relationName\":\"UserToVerification\"},{\"name\":\"authoredModels\",\"kind\":\"object\",\"type\":\"ModelAuthor\",\"relationName\":\"ModelAuthorToUser\"},{\"name\":\"grantedPermissions\",\"kind\":\"object\",\"type\":\"ModelPermission\",\"relationName\":\"ModelPermissionToUser\"},{\"name\":\"events\",\"kind\":\"object\",\"type\":\"Event\",\"relationName\":\"EventToUser\"},{\"name\":\"modelLikes\",\"kind\":\"object\",\"type\":\"ModelLike\",\"relationName\":\"ModelLikeToUser\"},{\"name\":\"modelInteractions\",\"kind\":\"object\",\"type\":\"ModelInteraction\",\"relationName\":\"ModelInteractionToUser\"},{\"name\":\"modelDrafts\",\"kind\":\"object\",\"type\":\"ModelDraft\",\"relationName\":\"ModelDraftToUser\"},{\"name\":\"comments\",\"kind\":\"object\",\"type\":\"ModelComment\",\"relationName\":\"ModelCommentToUser\"},{\"name\":\"commentLikes\",\"kind\":\"object\",\"type\":\"ModelCommentLike\",\"relationName\":\"ModelCommentLikeToUser\"},{\"name\":\"role\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"banned\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"banReason\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"banExpires\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"onboardedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"legacyId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"passkeys\",\"kind\":\"object\",\"type\":\"Passkey\",\"relationName\":\"PasskeyToUser\"}],\"dbName\":null},\"Account\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"accountId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"providerId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"accessToken\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"refreshToken\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"accessTokenExpiresAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"refreshTokenExpiresAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"scope\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"idToken\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"password\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"AccountToUser\"}],\"dbName\":null},\"Session\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"expiresAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"token\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"ipAddress\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"userAgent\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"SessionToUser\"},{\"name\":\"impersonatedBy\",\"kind\":\"scalar\",\"type\":\"String\"}],\"dbName\":null},\"Verification\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"identifier\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"value\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"expiresAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"UserToVerification\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"}],\"dbName\":null},\"Passkey\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"publicKey\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"credentialID\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"counter\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"deviceType\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"backedUp\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"transports\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"aaguid\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"PasskeyToUser\"}],\"dbName\":null},\"Model\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"legacyId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"latestVersionNumber\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"parentModelId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"parentVersionNumber\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"visibility\",\"kind\":\"enum\",\"type\":\"ModelVisibility\"},{\"name\":\"isEndorsed\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"isLibraryModel\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"viewCount\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"runCount\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"downloadCount\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"shareCount\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"deletedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"latestVersion\",\"kind\":\"object\",\"type\":\"ModelVersion\",\"relationName\":\"LatestVersion\"},{\"name\":\"parentModel\",\"kind\":\"object\",\"type\":\"Model\",\"relationName\":\"ModelParent\"},{\"name\":\"childModels\",\"kind\":\"object\",\"type\":\"Model\",\"relationName\":\"ModelParent\"},{\"name\":\"parentVersion\",\"kind\":\"object\",\"type\":\"ModelVersion\",\"relationName\":\"ParentVersion\"},{\"name\":\"versions\",\"kind\":\"object\",\"type\":\"ModelVersion\",\"relationName\":\"ModelVersions\"},{\"name\":\"authors\",\"kind\":\"object\",\"type\":\"ModelAuthor\",\"relationName\":\"ModelToModelAuthor\"},{\"name\":\"permissions\",\"kind\":\"object\",\"type\":\"ModelPermission\",\"relationName\":\"ModelToModelPermission\"},{\"name\":\"additionalFiles\",\"kind\":\"object\",\"type\":\"ModelAdditionalFile\",\"relationName\":\"ModelToModelAdditionalFile\"},{\"name\":\"likes\",\"kind\":\"object\",\"type\":\"ModelLike\",\"relationName\":\"ModelToModelLike\"},{\"name\":\"interactions\",\"kind\":\"object\",\"type\":\"ModelInteraction\",\"relationName\":\"ModelToModelInteraction\"},{\"name\":\"drafts\",\"kind\":\"object\",\"type\":\"ModelDraft\",\"relationName\":\"ModelToModelDraft\"},{\"name\":\"comments\",\"kind\":\"object\",\"type\":\"ModelComment\",\"relationName\":\"ModelToModelComment\"}],\"dbName\":null},\"ModelVersion\":{\"fields\":[{\"name\":\"modelId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"versionNumber\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"title\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"description\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"changeSummary\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"previewImageFileKey\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"netlogoFileKey\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"netlogoVersion\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"infoTab\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"finalizedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"model\",\"kind\":\"object\",\"type\":\"Model\",\"relationName\":\"ModelVersions\"},{\"name\":\"latestOfModel\",\"kind\":\"object\",\"type\":\"Model\",\"relationName\":\"LatestVersion\"},{\"name\":\"parentOfModels\",\"kind\":\"object\",\"type\":\"Model\",\"relationName\":\"ParentVersion\"},{\"name\":\"tags\",\"kind\":\"object\",\"type\":\"ModelVersionTag\",\"relationName\":\"ModelVersionToModelVersionTag\"},{\"name\":\"taggedAdditionalFiles\",\"kind\":\"object\",\"type\":\"ModelAdditionalFile\",\"relationName\":\"ModelAdditionalFileToModelVersion\"}],\"dbName\":null},\"ModelVersionTag\":{\"fields\":[{\"name\":\"modelId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"versionNumber\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"tagId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"modelVersion\",\"kind\":\"object\",\"type\":\"ModelVersion\",\"relationName\":\"ModelVersionToModelVersionTag\"},{\"name\":\"tag\",\"kind\":\"object\",\"type\":\"Tag\",\"relationName\":\"ModelVersionTagToTag\"}],\"dbName\":null},\"ModelAdditionalFile\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"modelId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"taggedVersionNumber\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"fileKey\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"kind\",\"kind\":\"enum\",\"type\":\"ModelFileKind\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"model\",\"kind\":\"object\",\"type\":\"Model\",\"relationName\":\"ModelToModelAdditionalFile\"},{\"name\":\"taggedVersion\",\"kind\":\"object\",\"type\":\"ModelVersion\",\"relationName\":\"ModelAdditionalFileToModelVersion\"}],\"dbName\":null},\"Tag\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"legacyId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"displayName\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"modelVersions\",\"kind\":\"object\",\"type\":\"ModelVersionTag\",\"relationName\":\"ModelVersionTagToTag\"}],\"dbName\":null},\"ModelAuthor\":{\"fields\":[{\"name\":\"modelId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"role\",\"kind\":\"enum\",\"type\":\"AuthorRole\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"model\",\"kind\":\"object\",\"type\":\"Model\",\"relationName\":\"ModelToModelAuthor\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"ModelAuthorToUser\"}],\"dbName\":null},\"ModelPermission\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"modelId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"granteeUserId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"permissionLevel\",\"kind\":\"enum\",\"type\":\"PermissionLevel\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"model\",\"kind\":\"object\",\"type\":\"Model\",\"relationName\":\"ModelToModelPermission\"},{\"name\":\"granteeUser\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"ModelPermissionToUser\"}],\"dbName\":null},\"ModelLike\":{\"fields\":[{\"name\":\"modelId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"model\",\"kind\":\"object\",\"type\":\"Model\",\"relationName\":\"ModelToModelLike\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"ModelLikeToUser\"}],\"dbName\":null},\"ModelInteraction\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"modelId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"versionNumber\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"kind\",\"kind\":\"enum\",\"type\":\"ModelInteractionKind\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"sessionId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"ipHash\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"userAgent\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"referer\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"geo\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"cookie\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"model\",\"kind\":\"object\",\"type\":\"Model\",\"relationName\":\"ModelToModelInteraction\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"ModelInteractionToUser\"}],\"dbName\":null},\"ModelDraft\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"ModelDraftToUser\"},{\"name\":\"modelId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"model\",\"kind\":\"object\",\"type\":\"Model\",\"relationName\":\"ModelToModelDraft\"},{\"name\":\"schemaVersion\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"data\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"ModelComment\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"legacyId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"parentId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"modelId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"versionNumber\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"content\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"likesCount\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"editedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"deletedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"model\",\"kind\":\"object\",\"type\":\"Model\",\"relationName\":\"ModelToModelComment\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"ModelCommentToUser\"},{\"name\":\"parent\",\"kind\":\"object\",\"type\":\"ModelComment\",\"relationName\":\"CommentReplies\"},{\"name\":\"replies\",\"kind\":\"object\",\"type\":\"ModelComment\",\"relationName\":\"CommentReplies\"},{\"name\":\"likes\",\"kind\":\"object\",\"type\":\"ModelCommentLike\",\"relationName\":\"ModelCommentToModelCommentLike\"}],\"dbName\":null},\"ModelCommentLike\":{\"fields\":[{\"name\":\"modelCommentId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"modelComment\",\"kind\":\"object\",\"type\":\"ModelComment\",\"relationName\":\"ModelCommentToModelCommentLike\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"ModelCommentLikeToUser\"}],\"dbName\":null},\"Event\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"type\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"actorId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"resourceType\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"resourceId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"payload\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"processedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"actor\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"EventToUser\"}],\"dbName\":null}},\"enums\":{},\"types\":{}}") defineDmmfProperty(exports.Prisma, config.runtimeDataModel) config.parameterizationSchema = { - strings: JSON.parse("[\"where\",\"orderBy\",\"cursor\",\"user\",\"accounts\",\"sessions\",\"verifications\",\"model\",\"latestOfModel\",\"parentOfModels\",\"modelVersion\",\"modelVersions\",\"_count\",\"tag\",\"tags\",\"taggedVersion\",\"taggedAdditionalFiles\",\"latestVersion\",\"parentModel\",\"childModels\",\"parentVersion\",\"versions\",\"authors\",\"granteeUser\",\"permissions\",\"additionalFiles\",\"likes\",\"interactions\",\"drafts\",\"authoredModels\",\"grantedPermissions\",\"actor\",\"events\",\"modelLikes\",\"modelInteractions\",\"modelDrafts\",\"passkeys\",\"User.findUnique\",\"User.findUniqueOrThrow\",\"User.findFirst\",\"User.findFirstOrThrow\",\"User.findMany\",\"data\",\"User.createOne\",\"User.createMany\",\"User.createManyAndReturn\",\"User.updateOne\",\"User.updateMany\",\"User.updateManyAndReturn\",\"create\",\"update\",\"User.upsertOne\",\"User.deleteOne\",\"User.deleteMany\",\"having\",\"_avg\",\"_sum\",\"_min\",\"_max\",\"User.groupBy\",\"User.aggregate\",\"Account.findUnique\",\"Account.findUniqueOrThrow\",\"Account.findFirst\",\"Account.findFirstOrThrow\",\"Account.findMany\",\"Account.createOne\",\"Account.createMany\",\"Account.createManyAndReturn\",\"Account.updateOne\",\"Account.updateMany\",\"Account.updateManyAndReturn\",\"Account.upsertOne\",\"Account.deleteOne\",\"Account.deleteMany\",\"Account.groupBy\",\"Account.aggregate\",\"Session.findUnique\",\"Session.findUniqueOrThrow\",\"Session.findFirst\",\"Session.findFirstOrThrow\",\"Session.findMany\",\"Session.createOne\",\"Session.createMany\",\"Session.createManyAndReturn\",\"Session.updateOne\",\"Session.updateMany\",\"Session.updateManyAndReturn\",\"Session.upsertOne\",\"Session.deleteOne\",\"Session.deleteMany\",\"Session.groupBy\",\"Session.aggregate\",\"Verification.findUnique\",\"Verification.findUniqueOrThrow\",\"Verification.findFirst\",\"Verification.findFirstOrThrow\",\"Verification.findMany\",\"Verification.createOne\",\"Verification.createMany\",\"Verification.createManyAndReturn\",\"Verification.updateOne\",\"Verification.updateMany\",\"Verification.updateManyAndReturn\",\"Verification.upsertOne\",\"Verification.deleteOne\",\"Verification.deleteMany\",\"Verification.groupBy\",\"Verification.aggregate\",\"Passkey.findUnique\",\"Passkey.findUniqueOrThrow\",\"Passkey.findFirst\",\"Passkey.findFirstOrThrow\",\"Passkey.findMany\",\"Passkey.createOne\",\"Passkey.createMany\",\"Passkey.createManyAndReturn\",\"Passkey.updateOne\",\"Passkey.updateMany\",\"Passkey.updateManyAndReturn\",\"Passkey.upsertOne\",\"Passkey.deleteOne\",\"Passkey.deleteMany\",\"Passkey.groupBy\",\"Passkey.aggregate\",\"Model.findUnique\",\"Model.findUniqueOrThrow\",\"Model.findFirst\",\"Model.findFirstOrThrow\",\"Model.findMany\",\"Model.createOne\",\"Model.createMany\",\"Model.createManyAndReturn\",\"Model.updateOne\",\"Model.updateMany\",\"Model.updateManyAndReturn\",\"Model.upsertOne\",\"Model.deleteOne\",\"Model.deleteMany\",\"Model.groupBy\",\"Model.aggregate\",\"ModelVersion.findUnique\",\"ModelVersion.findUniqueOrThrow\",\"ModelVersion.findFirst\",\"ModelVersion.findFirstOrThrow\",\"ModelVersion.findMany\",\"ModelVersion.createOne\",\"ModelVersion.createMany\",\"ModelVersion.createManyAndReturn\",\"ModelVersion.updateOne\",\"ModelVersion.updateMany\",\"ModelVersion.updateManyAndReturn\",\"ModelVersion.upsertOne\",\"ModelVersion.deleteOne\",\"ModelVersion.deleteMany\",\"ModelVersion.groupBy\",\"ModelVersion.aggregate\",\"ModelVersionTag.findUnique\",\"ModelVersionTag.findUniqueOrThrow\",\"ModelVersionTag.findFirst\",\"ModelVersionTag.findFirstOrThrow\",\"ModelVersionTag.findMany\",\"ModelVersionTag.createOne\",\"ModelVersionTag.createMany\",\"ModelVersionTag.createManyAndReturn\",\"ModelVersionTag.updateOne\",\"ModelVersionTag.updateMany\",\"ModelVersionTag.updateManyAndReturn\",\"ModelVersionTag.upsertOne\",\"ModelVersionTag.deleteOne\",\"ModelVersionTag.deleteMany\",\"ModelVersionTag.groupBy\",\"ModelVersionTag.aggregate\",\"ModelAdditionalFile.findUnique\",\"ModelAdditionalFile.findUniqueOrThrow\",\"ModelAdditionalFile.findFirst\",\"ModelAdditionalFile.findFirstOrThrow\",\"ModelAdditionalFile.findMany\",\"ModelAdditionalFile.createOne\",\"ModelAdditionalFile.createMany\",\"ModelAdditionalFile.createManyAndReturn\",\"ModelAdditionalFile.updateOne\",\"ModelAdditionalFile.updateMany\",\"ModelAdditionalFile.updateManyAndReturn\",\"ModelAdditionalFile.upsertOne\",\"ModelAdditionalFile.deleteOne\",\"ModelAdditionalFile.deleteMany\",\"ModelAdditionalFile.groupBy\",\"ModelAdditionalFile.aggregate\",\"Tag.findUnique\",\"Tag.findUniqueOrThrow\",\"Tag.findFirst\",\"Tag.findFirstOrThrow\",\"Tag.findMany\",\"Tag.createOne\",\"Tag.createMany\",\"Tag.createManyAndReturn\",\"Tag.updateOne\",\"Tag.updateMany\",\"Tag.updateManyAndReturn\",\"Tag.upsertOne\",\"Tag.deleteOne\",\"Tag.deleteMany\",\"Tag.groupBy\",\"Tag.aggregate\",\"ModelAuthor.findUnique\",\"ModelAuthor.findUniqueOrThrow\",\"ModelAuthor.findFirst\",\"ModelAuthor.findFirstOrThrow\",\"ModelAuthor.findMany\",\"ModelAuthor.createOne\",\"ModelAuthor.createMany\",\"ModelAuthor.createManyAndReturn\",\"ModelAuthor.updateOne\",\"ModelAuthor.updateMany\",\"ModelAuthor.updateManyAndReturn\",\"ModelAuthor.upsertOne\",\"ModelAuthor.deleteOne\",\"ModelAuthor.deleteMany\",\"ModelAuthor.groupBy\",\"ModelAuthor.aggregate\",\"ModelPermission.findUnique\",\"ModelPermission.findUniqueOrThrow\",\"ModelPermission.findFirst\",\"ModelPermission.findFirstOrThrow\",\"ModelPermission.findMany\",\"ModelPermission.createOne\",\"ModelPermission.createMany\",\"ModelPermission.createManyAndReturn\",\"ModelPermission.updateOne\",\"ModelPermission.updateMany\",\"ModelPermission.updateManyAndReturn\",\"ModelPermission.upsertOne\",\"ModelPermission.deleteOne\",\"ModelPermission.deleteMany\",\"ModelPermission.groupBy\",\"ModelPermission.aggregate\",\"ModelLike.findUnique\",\"ModelLike.findUniqueOrThrow\",\"ModelLike.findFirst\",\"ModelLike.findFirstOrThrow\",\"ModelLike.findMany\",\"ModelLike.createOne\",\"ModelLike.createMany\",\"ModelLike.createManyAndReturn\",\"ModelLike.updateOne\",\"ModelLike.updateMany\",\"ModelLike.updateManyAndReturn\",\"ModelLike.upsertOne\",\"ModelLike.deleteOne\",\"ModelLike.deleteMany\",\"ModelLike.groupBy\",\"ModelLike.aggregate\",\"ModelInteraction.findUnique\",\"ModelInteraction.findUniqueOrThrow\",\"ModelInteraction.findFirst\",\"ModelInteraction.findFirstOrThrow\",\"ModelInteraction.findMany\",\"ModelInteraction.createOne\",\"ModelInteraction.createMany\",\"ModelInteraction.createManyAndReturn\",\"ModelInteraction.updateOne\",\"ModelInteraction.updateMany\",\"ModelInteraction.updateManyAndReturn\",\"ModelInteraction.upsertOne\",\"ModelInteraction.deleteOne\",\"ModelInteraction.deleteMany\",\"ModelInteraction.groupBy\",\"ModelInteraction.aggregate\",\"ModelDraft.findUnique\",\"ModelDraft.findUniqueOrThrow\",\"ModelDraft.findFirst\",\"ModelDraft.findFirstOrThrow\",\"ModelDraft.findMany\",\"ModelDraft.createOne\",\"ModelDraft.createMany\",\"ModelDraft.createManyAndReturn\",\"ModelDraft.updateOne\",\"ModelDraft.updateMany\",\"ModelDraft.updateManyAndReturn\",\"ModelDraft.upsertOne\",\"ModelDraft.deleteOne\",\"ModelDraft.deleteMany\",\"ModelDraft.groupBy\",\"ModelDraft.aggregate\",\"Event.findUnique\",\"Event.findUniqueOrThrow\",\"Event.findFirst\",\"Event.findFirstOrThrow\",\"Event.findMany\",\"Event.createOne\",\"Event.createMany\",\"Event.createManyAndReturn\",\"Event.updateOne\",\"Event.updateMany\",\"Event.updateManyAndReturn\",\"Event.upsertOne\",\"Event.deleteOne\",\"Event.deleteMany\",\"Event.groupBy\",\"Event.aggregate\",\"AND\",\"OR\",\"NOT\",\"id\",\"type\",\"actorId\",\"resourceType\",\"resourceId\",\"payload\",\"createdAt\",\"processedAt\",\"equals\",\"in\",\"notIn\",\"lt\",\"lte\",\"gt\",\"gte\",\"not\",\"string_contains\",\"string_starts_with\",\"string_ends_with\",\"array_starts_with\",\"array_ends_with\",\"array_contains\",\"contains\",\"startsWith\",\"endsWith\",\"userId\",\"modelId\",\"schemaVersion\",\"updatedAt\",\"versionNumber\",\"ModelInteractionKind\",\"kind\",\"sessionId\",\"ipHash\",\"userAgent\",\"referer\",\"geo\",\"cookie\",\"granteeUserId\",\"PermissionLevel\",\"permissionLevel\",\"AuthorRole\",\"role\",\"legacyId\",\"name\",\"displayName\",\"every\",\"some\",\"none\",\"taggedVersionNumber\",\"fileKey\",\"ModelFileKind\",\"tagId\",\"title\",\"description\",\"changeSummary\",\"previewImageFileKey\",\"netlogoFileKey\",\"netlogoVersion\",\"infoTab\",\"finalizedAt\",\"latestVersionNumber\",\"parentModelId\",\"parentVersionNumber\",\"ModelVisibility\",\"visibility\",\"isEndorsed\",\"isLibraryModel\",\"viewCount\",\"runCount\",\"downloadCount\",\"shareCount\",\"deletedAt\",\"publicKey\",\"credentialID\",\"counter\",\"deviceType\",\"backedUp\",\"transports\",\"aaguid\",\"identifier\",\"value\",\"expiresAt\",\"token\",\"ipAddress\",\"impersonatedBy\",\"accountId\",\"providerId\",\"accessToken\",\"refreshToken\",\"accessTokenExpiresAt\",\"refreshTokenExpiresAt\",\"scope\",\"idToken\",\"password\",\"email\",\"emailVerified\",\"image\",\"SystemRole\",\"systemRole\",\"UserKind\",\"userKind\",\"isProfilePublic\",\"bio\",\"country\",\"socialLinks\",\"dob\",\"affiliation\",\"banned\",\"banReason\",\"banExpires\",\"onboardedAt\",\"modelId_userId\",\"modelId_granteeUserId\",\"modelId_versionNumber\",\"modelId_versionNumber_tagId\",\"id_latestVersionNumber\",\"is\",\"isNot\",\"connectOrCreate\",\"upsert\",\"createMany\",\"set\",\"disconnect\",\"delete\",\"connect\",\"updateMany\",\"deleteMany\",\"increment\",\"decrement\",\"multiply\",\"divide\"]"), - graph: "jwmXAYACIwQAAMoEACAFAADLBAAgBgAAzAQAIB0AAL4EACAeAAC_BAAgIAAAzQQAICEAAMAEACAiAADBBAAgIwAAwgQAICQAAM4EACCtAgAAxgQAMK4CAAAPABCvAgAAxgQAMLACAQAAAAG2AkAA_wMAIcwCQAD_AwAh2gIBAP4DACHbAgIAAAAB3AIBAP4DACH4AkAAnwQAIY8DAQAAAAGQAyAAngQAIZEDAQD-AwAhkwMAAMcEkwMilQMAAMgElQMilgMgAJ4EACGXAwEA_gMAIZgDAQD-AwAhmQMAAKcEACCaA0AAnwQAIZsDAQD-AwAhnAMgAMkEACGdAwEA_gMAIZ4DQACfBAAhnwNAAJ8EACEBAAAAAQAgEQMAAKAEACCtAgAA0QQAMK4CAAADABCvAgAA0QQAMLACAQD8AwAhtgJAAP8DACHJAgEA_AMAIcwCQAD_AwAhhgMBAPwDACGHAwEA_AMAIYgDAQD-AwAhiQMBAP4DACGKA0AAnwQAIYsDQACfBAAhjAMBAP4DACGNAwEA_gMAIY4DAQD-AwAhCAMAAIAIACCIAwAA0gQAIIkDAADSBAAgigMAANIEACCLAwAA0gQAIIwDAADSBAAgjQMAANIEACCOAwAA0gQAIBEDAACgBAAgrQIAANEEADCuAgAAAwAQrwIAANEEADCwAgEAAAABtgJAAP8DACHJAgEA_AMAIcwCQAD_AwAhhgMBAPwDACGHAwEA_AMAIYgDAQD-AwAhiQMBAP4DACGKA0AAnwQAIYsDQACfBAAhjAMBAP4DACGNAwEA_gMAIY4DAQD-AwAhAwAAAAMAIAEAAAQAMAIAAAUAIA0DAACgBAAgrQIAANAEADCuAgAABwAQrwIAANAEADCwAgEA_AMAIbYCQAD_AwAhyQIBAPwDACHMAkAA_wMAIdICAQD-AwAhggNAAP8DACGDAwEA_AMAIYQDAQD-AwAhhQMBAP4DACEEAwAAgAgAINICAADSBAAghAMAANIEACCFAwAA0gQAIA0DAACgBAAgrQIAANAEADCuAgAABwAQrwIAANAEADCwAgEAAAABtgJAAP8DACHJAgEA_AMAIcwCQAD_AwAh0gIBAP4DACGCA0AA_wMAIYMDAQAAAAGEAwEA_gMAIYUDAQD-AwAhAwAAAAcAIAEAAAgAMAIAAAkAIAsDAACpBAAgrQIAAM8EADCuAgAACwAQrwIAAM8EADCwAgEA_AMAIbYCQACfBAAhyQIBAP4DACHMAkAAnwQAIYADAQD8AwAhgQMBAPwDACGCA0AA_wMAIQQDAACACAAgtgIAANIEACDJAgAA0gQAIMwCAADSBAAgCwMAAKkEACCtAgAAzwQAMK4CAAALABCvAgAAzwQAMLACAQAAAAG2AkAAnwQAIckCAQD-AwAhzAJAAJ8EACGAAwEA_AMAIYEDAQD8AwAhggNAAP8DACEDAAAACwAgAQAADAAwAgAADQAgIwQAAMoEACAFAADLBAAgBgAAzAQAIB0AAL4EACAeAAC_BAAgIAAAzQQAICEAAMAEACAiAADBBAAgIwAAwgQAICQAAM4EACCtAgAAxgQAMK4CAAAPABCvAgAAxgQAMLACAQD8AwAhtgJAAP8DACHMAkAA_wMAIdoCAQD-AwAh2wICAP0DACHcAgEA_gMAIfgCQACfBAAhjwMBAP4DACGQAyAAngQAIZEDAQD-AwAhkwMAAMcEkwMilQMAAMgElQMilgMgAJ4EACGXAwEA_gMAIZgDAQD-AwAhmQMAAKcEACCaA0AAnwQAIZsDAQD-AwAhnAMgAMkEACGdAwEA_gMAIZ4DQACfBAAhnwNAAJ8EACEBAAAADwAgCQMAAKAEACAHAACoBAAgrQIAAMQEADCuAgAAEQAQrwIAAMQEADC2AkAA_wMAIckCAQD8AwAhygIBAPwDACHaAgAAxQTaAiICAwAAgAgAIAcAAIEIACAKAwAAoAQAIAcAAKgEACCtAgAAxAQAMK4CAAARABCvAgAAxAQAMLYCQAD_AwAhyQIBAPwDACHKAgEA_AMAIdoCAADFBNoCIqADAADDBAAgAwAAABEAIAEAABIAMAIAABMAIBMHAACoBAAgCAAApAQAIAkAALEEACAOAACABAAgEAAAsgQAIK0CAACwBAAwrgIAABUAEK8CAACwBAAwtgJAAP8DACHKAgEA_AMAIc0CAgCdBAAh5QIBAPwDACHmAgEA_gMAIecCAQD-AwAh6AIBAP4DACHpAgEA_AMAIeoCAQD-AwAh6wIBAP4DACHsAkAAnwQAIQEAAAAVACAdEQAAvAQAIBIAAKQEACATAACxBAAgFAAAvAQAIBUAAL0EACAWAAC-BAAgGAAAvwQAIBkAALIEACAaAADABAAgGwAAwQQAIBwAAMIEACCtAgAAugQAMK4CAAAXABCvAgAAugQAMLACAQD8AwAhtgJAAP8DACHMAkAA_wMAIdsCAgD9AwAh7QICAP0DACHuAgEA_gMAIe8CAgD9AwAh8QIAALsE8QIi8gIgAJ4EACHzAiAAngQAIfQCAgCdBAAh9QICAJ0EACH2AgIAnQQAIfcCAgCdBAAh-AJAAJ8EACEBAAAAFwAgEBEAAIQIACASAACBCAAgEwAAgggAIBQAAIQIACAVAACGCAAgFgAA-QcAIBgAAPoHACAZAACDCAAgGgAA_AcAIBsAAP0HACAcAAD-BwAg2wIAANIEACDtAgAA0gQAIO4CAADSBAAg7wIAANIEACD4AgAA0gQAIB4RAAC8BAAgEgAApAQAIBMAALEEACAUAAC8BAAgFQAAvQQAIBYAAL4EACAYAAC_BAAgGQAAsgQAIBoAAMAEACAbAADBBAAgHAAAwgQAIK0CAAC6BAAwrgIAABcAEK8CAAC6BAAwsAIBAAAAAbYCQAD_AwAhzAJAAP8DACHbAgIAAAAB7QICAP0DACHuAgEA_gMAIe8CAgD9AwAh8QIAALsE8QIi8gIgAJ4EACHzAiAAngQAIfQCAgCdBAAh9QICAJ0EACH2AgIAnQQAIfcCAgCdBAAh-AJAAJ8EACGkAwAAuQQAIAMAAAAXACABAAAZADACAAAaACAJCgAAtQQAIA0AALgEACCtAgAAtwQAMK4CAAAcABCvAgAAtwQAMLYCQAD_AwAhygIBAPwDACHNAgIAnQQAIeQCAQD8AwAhAgoAAIQIACANAACFCAAgCgoAALUEACANAAC4BAAgrQIAALcEADCuAgAAHAAQrwIAALcEADC2AkAA_wMAIcoCAQD8AwAhzQICAJ0EACHkAgEA_AMAIaMDAAC2BAAgAwAAABwAIAEAAB0AMAIAAB4AIAMAAAAcACABAAAdADACAAAeACABAAAAHAAgCwcAAKgEACAPAAC1BAAgrQIAALMEADCuAgAAIgAQrwIAALMEADCwAgEA_AMAIbYCQAD_AwAhygIBAPwDACHPAgAAtATkAiLhAgIAnQQAIeICAQD8AwAhAgcAAIEIACAPAACECAAgCwcAAKgEACAPAAC1BAAgrQIAALMEADCuAgAAIgAQrwIAALMEADCwAgEAAAABtgJAAP8DACHKAgEA_AMAIc8CAAC0BOQCIuECAgCdBAAh4gIBAPwDACEDAAAAIgAgAQAAIwAwAgAAJAAgAQAAABcAIAEAAAAcACABAAAAIgAgAQAAABcAIAMAAAAXACABAAAZADACAAAaACABAAAAFQAgCwcAAIEIACAIAACBCAAgCQAAgggAIA4AAJ0FACAQAACDCAAg5gIAANIEACDnAgAA0gQAIOgCAADSBAAg6gIAANIEACDrAgAA0gQAIOwCAADSBAAgFAcAAKgEACAIAACkBAAgCQAAsQQAIA4AAIAEACAQAACyBAAgrQIAALAEADCuAgAAFQAQrwIAALAEADC2AkAA_wMAIcoCAQD8AwAhzQICAJ0EACHlAgEA_AMAIeYCAQD-AwAh5wIBAP4DACHoAgEA_gMAIekCAQD8AwAh6gIBAP4DACHrAgEA_gMAIewCQACfBAAhogMAAK8EACADAAAAFQAgAQAALAAwAgAALQAgAwAAABEAIAEAABIAMAIAABMAIAoHAACoBAAgFwAAqQQAIK0CAACtBAAwrgIAADAAEK8CAACtBAAwsAIBAPwDACG2AkAA_wMAIcoCAQD8AwAh1gIBAP4DACHYAgAArgTYAiIDBwAAgQgAIBcAAIAIACDWAgAA0gQAIAsHAACoBAAgFwAAqQQAIK0CAACtBAAwrgIAADAAEK8CAACtBAAwsAIBAAAAAbYCQAD_AwAhygIBAPwDACHWAgEA_gMAIdgCAACuBNgCIqEDAACsBAAgAwAAADAAIAEAADEAMAIAADIAIAEAAAAPACADAAAAIgAgAQAAIwAwAgAAJAAgCAMAAKAEACAHAACoBAAgrQIAAKsEADCuAgAANgAQrwIAAKsEADC2AkAA_wMAIckCAQD8AwAhygIBAPwDACECAwAAgAgAIAcAAIEIACAJAwAAoAQAIAcAAKgEACCtAgAAqwQAMK4CAAA2ABCvAgAAqwQAMLYCQAD_AwAhyQIBAPwDACHKAgEA_AMAIaADAACqBAAgAwAAADYAIAEAADcAMAIAADgAIBEDAACpBAAgBwAAqAQAIK0CAAClBAAwrgIAADoAEK8CAAClBAAwsAIBAPwDACG2AkAA_wMAIckCAQD-AwAhygIBAPwDACHNAgIA_QMAIc8CAACmBM8CItACAQD-AwAh0QIBAP4DACHSAgEA_gMAIdMCAQD-AwAh1AIAAKcEACDVAgEA_gMAIQoDAACACAAgBwAAgQgAIMkCAADSBAAgzQIAANIEACDQAgAA0gQAINECAADSBAAg0gIAANIEACDTAgAA0gQAINQCAADSBAAg1QIAANIEACARAwAAqQQAIAcAAKgEACCtAgAApQQAMK4CAAA6ABCvAgAApQQAMLACAQAAAAG2AkAA_wMAIckCAQD-AwAhygIBAPwDACHNAgIA_QMAIc8CAACmBM8CItACAQD-AwAh0QIBAP4DACHSAgEA_gMAIdMCAQD-AwAh1AIAAKcEACDVAgEA_gMAIQMAAAA6ACABAAA7ADACAAA8ACABAAAADwAgDAMAAKAEACAHAACkBAAgKgAAogQAIK0CAACjBAAwrgIAAD8AEK8CAACjBAAwsAIBAPwDACG2AkAA_wMAIckCAQD8AwAhygIBAP4DACHLAgIAnQQAIcwCQAD_AwAhAwMAAIAIACAHAACBCAAgygIAANIEACAMAwAAoAQAIAcAAKQEACAqAACiBAAgrQIAAKMEADCuAgAAPwAQrwIAAKMEADCwAgEAAAABtgJAAP8DACHJAgEA_AMAIcoCAQD-AwAhywICAJ0EACHMAkAA_wMAIQMAAAA_ACABAABAADACAABBACABAAAAFwAgAQAAABcAIAEAAAAVACABAAAAEQAgAQAAADAAIAEAAAAiACABAAAANgAgAQAAADoAIAEAAAA_ACADAAAAMAAgAQAAMQAwAgAAMgAgDB8AAKAEACCtAgAAoQQAMK4CAABNABCvAgAAoQQAMLACAQD8AwAhsQIBAPwDACGyAgEA_AMAIbMCAQD8AwAhtAIBAPwDACG1AgAAogQAILYCQAD_AwAhtwJAAJ8EACECHwAAgAgAILcCAADSBAAgDB8AAKAEACCtAgAAoQQAMK4CAABNABCvAgAAoQQAMLACAQAAAAGxAgEA_AMAIbICAQD8AwAhswIBAPwDACG0AgEA_AMAIbUCAACiBAAgtgJAAP8DACG3AkAAnwQAIQMAAABNACABAABOADACAABPACADAAAANgAgAQAANwAwAgAAOAAgAwAAADoAIAEAADsAMAIAADwAIAMAAAA_ACABAABAADACAABBACAPAwAAoAQAIK0CAACcBAAwrgIAAFQAEK8CAACcBAAwsAIBAPwDACG2AkAAnwQAIckCAQD8AwAh3AIBAP4DACH5AgEA_AMAIfoCAQD8AwAh-wICAJ0EACH8AgEA_AMAIf0CIACeBAAh_gIBAP4DACH_AgEA_gMAIQUDAACACAAgtgIAANIEACDcAgAA0gQAIP4CAADSBAAg_wIAANIEACAPAwAAoAQAIK0CAACcBAAwrgIAAFQAEK8CAACcBAAwsAIBAAAAAbYCQACfBAAhyQIBAPwDACHcAgEA_gMAIfkCAQD8AwAh-gIBAPwDACH7AgIAnQQAIfwCAQD8AwAh_QIgAJ4EACH-AgEA_gMAIf8CAQD-AwAhAwAAAFQAIAEAAFUAMAIAAFYAIAEAAAADACABAAAABwAgAQAAAAsAIAEAAAARACABAAAAMAAgAQAAAE0AIAEAAAA2ACABAAAAOgAgAQAAAD8AIAEAAABUACABAAAAAQAgGQQAAPYHACAFAAD3BwAgBgAA-AcAIB0AAPkHACAeAAD6BwAgIAAA-wcAICEAAPwHACAiAAD9BwAgIwAA_gcAICQAAP8HACDaAgAA0gQAINsCAADSBAAg3AIAANIEACD4AgAA0gQAII8DAADSBAAgkQMAANIEACCXAwAA0gQAIJgDAADSBAAgmQMAANIEACCaAwAA0gQAIJsDAADSBAAgnAMAANIEACCdAwAA0gQAIJ4DAADSBAAgnwMAANIEACADAAAADwAgAQAAYwAwAgAAAQAgAwAAAA8AIAEAAGMAMAIAAAEAIAMAAAAPACABAABjADACAAABACAgBAAA7AcAIAUAAO0HACAGAADuBwAgHQAA7wcAIB4AAPAHACAgAADxBwAgIQAA8gcAICIAAPMHACAjAAD0BwAgJAAA9QcAILACAQAAAAG2AkAAAAABzAJAAAAAAdoCAQAAAAHbAgIAAAAB3AIBAAAAAfgCQAAAAAGPAwEAAAABkAMgAAAAAZEDAQAAAAGTAwAAAJMDApUDAAAAlQMClgMgAAAAAZcDAQAAAAGYAwEAAAABmQOAAAAAAZoDQAAAAAGbAwEAAAABnAMgAAAAAZ0DAQAAAAGeA0AAAAABnwNAAAAAAQEqAABnACAWsAIBAAAAAbYCQAAAAAHMAkAAAAAB2gIBAAAAAdsCAgAAAAHcAgEAAAAB-AJAAAAAAY8DAQAAAAGQAyAAAAABkQMBAAAAAZMDAAAAkwMClQMAAACVAwKWAyAAAAABlwMBAAAAAZgDAQAAAAGZA4AAAAABmgNAAAAAAZsDAQAAAAGcAyAAAAABnQMBAAAAAZ4DQAAAAAGfA0AAAAABASoAAGkAMAEqAABpADAgBAAA-QYAIAUAAPoGACAGAAD7BgAgHQAA_AYAIB4AAP0GACAgAAD-BgAgIQAA_wYAICIAAIAHACAjAACBBwAgJAAAggcAILACAQDWBAAhtgJAANcEACHMAkAA1wQAIdoCAQDhBAAh2wICAOsEACHcAgEA4QQAIfgCQADYBAAhjwMBAOEEACGQAyAA2QUAIZEDAQDhBAAhkwMAAPYGkwMilQMAAPcGlQMilgMgANkFACGXAwEA4QQAIZgDAQDhBAAhmQOAAAAAAZoDQADYBAAhmwMBAOEEACGcAyAA-AYAIZ0DAQDhBAAhngNAANgEACGfA0AA2AQAIQIAAAABACAqAABsACAWsAIBANYEACG2AkAA1wQAIcwCQADXBAAh2gIBAOEEACHbAgIA6wQAIdwCAQDhBAAh-AJAANgEACGPAwEA4QQAIZADIADZBQAhkQMBAOEEACGTAwAA9gaTAyKVAwAA9waVAyKWAyAA2QUAIZcDAQDhBAAhmAMBAOEEACGZA4AAAAABmgNAANgEACGbAwEA4QQAIZwDIAD4BgAhnQMBAOEEACGeA0AA2AQAIZ8DQADYBAAhAgAAAA8AICoAAG4AIAIAAAAPACAqAABuACADAAAAAQAgMQAAZwAgMgAAbAAgAQAAAAEAIAEAAAAPACAUDAAA8QYAIDcAAPIGACA4AAD1BgAgOQAA9AYAIDoAAPMGACDaAgAA0gQAINsCAADSBAAg3AIAANIEACD4AgAA0gQAII8DAADSBAAgkQMAANIEACCXAwAA0gQAIJgDAADSBAAgmQMAANIEACCaAwAA0gQAIJsDAADSBAAgnAMAANIEACCdAwAA0gQAIJ4DAADSBAAgnwMAANIEACAZrQIAAJIEADCuAgAAdQAQrwIAAJIEADCwAgEA1AMAIbYCQADWAwAhzAJAANYDACHaAgEA4gMAIdsCAgDpAwAh3AIBAOIDACH4AkAA1wMAIY8DAQDiAwAhkAMgAIkEACGRAwEA4gMAIZMDAACTBJMDIpUDAACUBJUDIpYDIACJBAAhlwMBAOIDACGYAwEA4gMAIZkDAADrAwAgmgNAANcDACGbAwEA4gMAIZwDIACVBAAhnQMBAOIDACGeA0AA1wMAIZ8DQADXAwAhAwAAAA8AIAEAAHQAMDYAAHUAIAMAAAAPACABAABjADACAAABACABAAAABQAgAQAAAAUAIAMAAAADACABAAAEADACAAAFACADAAAAAwAgAQAABAAwAgAABQAgAwAAAAMAIAEAAAQAMAIAAAUAIA4DAADwBgAgsAIBAAAAAbYCQAAAAAHJAgEAAAABzAJAAAAAAYYDAQAAAAGHAwEAAAABiAMBAAAAAYkDAQAAAAGKA0AAAAABiwNAAAAAAYwDAQAAAAGNAwEAAAABjgMBAAAAAQEqAAB9ACANsAIBAAAAAbYCQAAAAAHJAgEAAAABzAJAAAAAAYYDAQAAAAGHAwEAAAABiAMBAAAAAYkDAQAAAAGKA0AAAAABiwNAAAAAAYwDAQAAAAGNAwEAAAABjgMBAAAAAQEqAAB_ADABKgAAfwAwDgMAAO8GACCwAgEA1gQAIbYCQADXBAAhyQIBANYEACHMAkAA1wQAIYYDAQDWBAAhhwMBANYEACGIAwEA4QQAIYkDAQDhBAAhigNAANgEACGLA0AA2AQAIYwDAQDhBAAhjQMBAOEEACGOAwEA4QQAIQIAAAAFACAqAACCAQAgDbACAQDWBAAhtgJAANcEACHJAgEA1gQAIcwCQADXBAAhhgMBANYEACGHAwEA1gQAIYgDAQDhBAAhiQMBAOEEACGKA0AA2AQAIYsDQADYBAAhjAMBAOEEACGNAwEA4QQAIY4DAQDhBAAhAgAAAAMAICoAAIQBACACAAAAAwAgKgAAhAEAIAMAAAAFACAxAAB9ACAyAACCAQAgAQAAAAUAIAEAAAADACAKDAAA7AYAIDkAAO4GACA6AADtBgAgiAMAANIEACCJAwAA0gQAIIoDAADSBAAgiwMAANIEACCMAwAA0gQAII0DAADSBAAgjgMAANIEACAQrQIAAJEEADCuAgAAiwEAEK8CAACRBAAwsAIBANQDACG2AkAA1gMAIckCAQDUAwAhzAJAANYDACGGAwEA1AMAIYcDAQDUAwAhiAMBAOIDACGJAwEA4gMAIYoDQADXAwAhiwNAANcDACGMAwEA4gMAIY0DAQDiAwAhjgMBAOIDACEDAAAAAwAgAQAAigEAMDYAAIsBACADAAAAAwAgAQAABAAwAgAABQAgAQAAAAkAIAEAAAAJACADAAAABwAgAQAACAAwAgAACQAgAwAAAAcAIAEAAAgAMAIAAAkAIAMAAAAHACABAAAIADACAAAJACAKAwAA6wYAILACAQAAAAG2AkAAAAAByQIBAAAAAcwCQAAAAAHSAgEAAAABggNAAAAAAYMDAQAAAAGEAwEAAAABhQMBAAAAAQEqAACTAQAgCbACAQAAAAG2AkAAAAAByQIBAAAAAcwCQAAAAAHSAgEAAAABggNAAAAAAYMDAQAAAAGEAwEAAAABhQMBAAAAAQEqAACVAQAwASoAAJUBADAKAwAA6gYAILACAQDWBAAhtgJAANcEACHJAgEA1gQAIcwCQADXBAAh0gIBAOEEACGCA0AA1wQAIYMDAQDWBAAhhAMBAOEEACGFAwEA4QQAIQIAAAAJACAqAACYAQAgCbACAQDWBAAhtgJAANcEACHJAgEA1gQAIcwCQADXBAAh0gIBAOEEACGCA0AA1wQAIYMDAQDWBAAhhAMBAOEEACGFAwEA4QQAIQIAAAAHACAqAACaAQAgAgAAAAcAICoAAJoBACADAAAACQAgMQAAkwEAIDIAAJgBACABAAAACQAgAQAAAAcAIAYMAADnBgAgOQAA6QYAIDoAAOgGACDSAgAA0gQAIIQDAADSBAAghQMAANIEACAMrQIAAJAEADCuAgAAoQEAEK8CAACQBAAwsAIBANQDACG2AkAA1gMAIckCAQDUAwAhzAJAANYDACHSAgEA4gMAIYIDQADWAwAhgwMBANQDACGEAwEA4gMAIYUDAQDiAwAhAwAAAAcAIAEAAKABADA2AAChAQAgAwAAAAcAIAEAAAgAMAIAAAkAIAEAAAANACABAAAADQAgAwAAAAsAIAEAAAwAMAIAAA0AIAMAAAALACABAAAMADACAAANACADAAAACwAgAQAADAAwAgAADQAgCAMAAOYGACCwAgEAAAABtgJAAAAAAckCAQAAAAHMAkAAAAABgAMBAAAAAYEDAQAAAAGCA0AAAAABASoAAKkBACAHsAIBAAAAAbYCQAAAAAHJAgEAAAABzAJAAAAAAYADAQAAAAGBAwEAAAABggNAAAAAAQEqAACrAQAwASoAAKsBADABAAAADwAgCAMAAOUGACCwAgEA1gQAIbYCQADYBAAhyQIBAOEEACHMAkAA2AQAIYADAQDWBAAhgQMBANYEACGCA0AA1wQAIQIAAAANACAqAACvAQAgB7ACAQDWBAAhtgJAANgEACHJAgEA4QQAIcwCQADYBAAhgAMBANYEACGBAwEA1gQAIYIDQADXBAAhAgAAAAsAICoAALEBACACAAAACwAgKgAAsQEAIAEAAAAPACADAAAADQAgMQAAqQEAIDIAAK8BACABAAAADQAgAQAAAAsAIAYMAADiBgAgOQAA5AYAIDoAAOMGACC2AgAA0gQAIMkCAADSBAAgzAIAANIEACAKrQIAAI8EADCuAgAAuQEAEK8CAACPBAAwsAIBANQDACG2AkAA1wMAIckCAQDiAwAhzAJAANcDACGAAwEA1AMAIYEDAQDUAwAhggNAANYDACEDAAAACwAgAQAAuAEAMDYAALkBACADAAAACwAgAQAADAAwAgAADQAgAQAAAFYAIAEAAABWACADAAAAVAAgAQAAVQAwAgAAVgAgAwAAAFQAIAEAAFUAMAIAAFYAIAMAAABUACABAABVADACAABWACAMAwAA4QYAILACAQAAAAG2AkAAAAAByQIBAAAAAdwCAQAAAAH5AgEAAAAB-gIBAAAAAfsCAgAAAAH8AgEAAAAB_QIgAAAAAf4CAQAAAAH_AgEAAAABASoAAMEBACALsAIBAAAAAbYCQAAAAAHJAgEAAAAB3AIBAAAAAfkCAQAAAAH6AgEAAAAB-wICAAAAAfwCAQAAAAH9AiAAAAAB_gIBAAAAAf8CAQAAAAEBKgAAwwEAMAEqAADDAQAwDAMAAOAGACCwAgEA1gQAIbYCQADYBAAhyQIBANYEACHcAgEA4QQAIfkCAQDWBAAh-gIBANYEACH7AgIA4AQAIfwCAQDWBAAh_QIgANkFACH-AgEA4QQAIf8CAQDhBAAhAgAAAFYAICoAAMYBACALsAIBANYEACG2AkAA2AQAIckCAQDWBAAh3AIBAOEEACH5AgEA1gQAIfoCAQDWBAAh-wICAOAEACH8AgEA1gQAIf0CIADZBQAh_gIBAOEEACH_AgEA4QQAIQIAAABUACAqAADIAQAgAgAAAFQAICoAAMgBACADAAAAVgAgMQAAwQEAIDIAAMYBACABAAAAVgAgAQAAAFQAIAkMAADbBgAgNwAA3AYAIDgAAN8GACA5AADeBgAgOgAA3QYAILYCAADSBAAg3AIAANIEACD-AgAA0gQAIP8CAADSBAAgDq0CAACOBAAwrgIAAM8BABCvAgAAjgQAMLACAQDUAwAhtgJAANcDACHJAgEA1AMAIdwCAQDiAwAh-QIBANQDACH6AgEA1AMAIfsCAgDjAwAh_AIBANQDACH9AiAAiQQAIf4CAQDiAwAh_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_AwAh2wICAAAAAdwCAQAAAAHdAgEA_gMAIQEAAACwAgAgAQAAALACACAJCwAAgAQAIK0CAAD7AwAwrgIAALMCABCvAgAA-wMAMLACAQD8AwAhtgJAAP8DACHbAgIA_QMAIdwCAQD8AwAh3QIBAP4DACEDCwAAnQUAINsCAADSBAAg3QIAANIEACADAAAAswIAIAEAALQCADACAACwAgAgAwAAALMCACABAAC0AgAwAgAAsAIAIAMAAACzAgAgAQAAtAIAMAIAALACACAGCwAAnAUAILACAQAAAAG2AkAAAAAB2wICAAAAAdwCAQAAAAHdAgEAAAABASoAALgCACAFsAIBAAAAAbYCQAAAAAHbAgIAAAAB3AIBAAAAAd0CAQAAAAEBKgAAugIAMAEqAAC6AgAwBgsAAI0FACCwAgEA1gQAIbYCQADXBAAh2wICAOsEACHcAgEA1gQAId0CAQDhBAAhAgAAALACACAqAAC9AgAgBbACAQDWBAAhtgJAANcEACHbAgIA6wQAIdwCAQDWBAAh3QIBAOEEACECAAAAswIAICoAAL8CACACAAAAswIAICoAAL8CACADAAAAsAIAIDEAALgCACAyAAC9AgAgAQAAALACACABAAAAswIAIAcMAACIBQAgNwAAiQUAIDgAAIwFACA5AACLBQAgOgAAigUAINsCAADSBAAg3QIAANIEACAIrQIAAPoDADCuAgAAxgIAEK8CAAD6AwAwsAIBANQDACG2AkAA1gMAIdsCAgDpAwAh3AIBANQDACHdAgEA4gMAIQMAAACzAgAgAQAAxQIAMDYAAMYCACADAAAAswIAIAEAALQCADACAACwAgAgAQAAABMAIAEAAAATACADAAAAEQAgAQAAEgAwAgAAEwAgAwAAABEAIAEAABIAMAIAABMAIAMAAAARACABAAASADACAAATACAGAwAAhwUAIAcAAIYFACC2AkAAAAAByQIBAAAAAcoCAQAAAAHaAgAAANoCAgEqAADOAgAgBLYCQAAAAAHJAgEAAAABygIBAAAAAdoCAAAA2gICASoAANACADABKgAA0AIAMAYDAACFBQAgBwAAhAUAILYCQADXBAAhyQIBANYEACHKAgEA1gQAIdoCAACDBdoCIgIAAAATACAqAADTAgAgBLYCQADXBAAhyQIBANYEACHKAgEA1gQAIdoCAACDBdoCIgIAAAARACAqAADVAgAgAgAAABEAICoAANUCACADAAAAEwAgMQAAzgIAIDIAANMCACABAAAAEwAgAQAAABEAIAMMAACABQAgOQAAggUAIDoAAIEFACAHrQIAAPYDADCuAgAA3AIAEK8CAAD2AwAwtgJAANYDACHJAgEA1AMAIcoCAQDUAwAh2gIAAPcD2gIiAwAAABEAIAEAANsCADA2AADcAgAgAwAAABEAIAEAABIAMAIAABMAIAEAAAAyACABAAAAMgAgAwAAADAAIAEAADEAMAIAADIAIAMAAAAwACABAAAxADACAAAyACADAAAAMAAgAQAAMQAwAgAAMgAgBwcAAP4EACAXAAD_BAAgsAIBAAAAAbYCQAAAAAHKAgEAAAAB1gIBAAAAAdgCAAAA2AICASoAAOQCACAFsAIBAAAAAbYCQAAAAAHKAgEAAAAB1gIBAAAAAdgCAAAA2AICASoAAOYCADABKgAA5gIAMAEAAAAPACAHBwAA_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_ACABAABAADACAABBACADAAAAPwAgAQAAQAAwAgAAQQAgCQMAAOQEACAHAADlBAAgKoAAAAABsAIBAAAAAbYCQAAAAAHJAgEAAAABygIBAAAAAcsCAgAAAAHMAkAAAAABASoAAKoDACAHKoAAAAABsAIBAAAAAbYCQAAAAAHJAgEAAAABygIBAAAAAcsCAgAAAAHMAkAAAAABASoAAKwDADABKgAArAMAMAEAAAAXACAJAwAA4gQAIAcAAOMEACAqgAAAAAGwAgEA1gQAIbYCQADXBAAhyQIBANYEACHKAgEA4QQAIcsCAgDgBAAhzAJAANcEACECAAAAQQAgKgAAsAMAIAcqgAAAAAGwAgEA1gQAIbYCQADXBAAhyQIBANYEACHKAgEA4QQAIcsCAgDgBAAhzAJAANcEACECAAAAPwAgKgAAsgMAIAIAAAA_ACAqAACyAwAgAQAAABcAIAMAAABBACAxAACqAwAgMgAAsAMAIAEAAABBACABAAAAPwAgBgwAANsEACA3AADcBAAgOAAA3wQAIDkAAN4EACA6AADdBAAgygIAANIEACAKKgAA1QMAIK0CAADhAwAwrgIAALoDABCvAgAA4QMAMLACAQDUAwAhtgJAANYDACHJAgEA1AMAIcoCAQDiAwAhywICAOMDACHMAkAA1gMAIQMAAAA_ACABAAC5AwAwNgAAugMAIAMAAAA_ACABAABAADACAABBACABAAAATwAgAQAAAE8AIAMAAABNACABAABOADACAABPACADAAAATQAgAQAATgAwAgAATwAgAwAAAE0AIAEAAE4AMAIAAE8AIAkfAADaBAAgsAIBAAAAAbECAQAAAAGyAgEAAAABswIBAAAAAbQCAQAAAAG1AoAAAAABtgJAAAAAAbcCQAAAAAEBKgAAwgMAIAiwAgEAAAABsQIBAAAAAbICAQAAAAGzAgEAAAABtAIBAAAAAbUCgAAAAAG2AkAAAAABtwJAAAAAAQEqAADEAwAwASoAAMQDADAJHwAA2QQAILACAQDWBAAhsQIBANYEACGyAgEA1gQAIbMCAQDWBAAhtAIBANYEACG1AoAAAAABtgJAANcEACG3AkAA2AQAIQIAAABPACAqAADHAwAgCLACAQDWBAAhsQIBANYEACGyAgEA1gQAIbMCAQDWBAAhtAIBANYEACG1AoAAAAABtgJAANcEACG3AkAA2AQAIQIAAABNACAqAADJAwAgAgAAAE0AICoAAMkDACADAAAATwAgMQAAwgMAIDIAAMcDACABAAAATwAgAQAAAE0AIAQMAADTBAAgOQAA1QQAIDoAANQEACC3AgAA0gQAIAutAgAA0wMAMK4CAADQAwAQrwIAANMDADCwAgEA1AMAIbECAQDUAwAhsgIBANQDACGzAgEA1AMAIbQCAQDUAwAhtQIAANUDACC2AkAA1gMAIbcCQADXAwAhAwAAAE0AIAEAAM8DADA2AADQAwAgAwAAAE0AIAEAAE4AMAIAAE8AIAutAgAA0wMAMK4CAADQAwAQrwIAANMDADCwAgEA1AMAIbECAQDUAwAhsgIBANQDACGzAgEA1AMAIbQCAQDUAwAhtQIAANUDACC2AkAA1gMAIbcCQADXAwAhDgwAANwDACA5AADgAwAgOgAA4AMAILgCAQAAAAG5AgEAAAAEugIBAAAABLsCAQAAAAG8AgEAAAABvQIBAAAAAb4CAQAAAAG_AgEA3wMAIcYCAQAAAAHHAgEAAAAByAIBAAAAAQ8MAADcAwAgOQAA3gMAIDoAAN4DACC4AoAAAAABuwKAAAAAAbwCgAAAAAG9AoAAAAABvgKAAAAAAb8CgAAAAAHAAgEAAAABwQIBAAAAAcICAQAAAAHDAoAAAAABxAKAAAAAAcUCgAAAAAELDAAA3AMAIDkAAN0DACA6AADdAwAguAJAAAAAAbkCQAAAAAS6AkAAAAAEuwJAAAAAAbwCQAAAAAG9AkAAAAABvgJAAAAAAb8CQADbAwAhCwwAANkDACA5AADaAwAgOgAA2gMAILgCQAAAAAG5AkAAAAAFugJAAAAABbsCQAAAAAG8AkAAAAABvQJAAAAAAb4CQAAAAAG_AkAA2AMAIQsMAADZAwAgOQAA2gMAIDoAANoDACC4AkAAAAABuQJAAAAABboCQAAAAAW7AkAAAAABvAJAAAAAAb0CQAAAAAG-AkAAAAABvwJAANgDACEIuAICAAAAAbkCAgAAAAW6AgIAAAAFuwICAAAAAbwCAgAAAAG9AgIAAAABvgICAAAAAb8CAgDZAwAhCLgCQAAAAAG5AkAAAAAFugJAAAAABbsCQAAAAAG8AkAAAAABvQJAAAAAAb4CQAAAAAG_AkAA2gMAIQsMAADcAwAgOQAA3QMAIDoAAN0DACC4AkAAAAABuQJAAAAABLoCQAAAAAS7AkAAAAABvAJAAAAAAb0CQAAAAAG-AkAAAAABvwJAANsDACEIuAICAAAAAbkCAgAAAAS6AgIAAAAEuwICAAAAAbwCAgAAAAG9AgIAAAABvgICAAAAAb8CAgDcAwAhCLgCQAAAAAG5AkAAAAAEugJAAAAABLsCQAAAAAG8AkAAAAABvQJAAAAAAb4CQAAAAAG_AkAA3QMAIQy4AoAAAAABuwKAAAAAAbwCgAAAAAG9AoAAAAABvgKAAAAAAb8CgAAAAAHAAgEAAAABwQIBAAAAAcICAQAAAAHDAoAAAAABxAKAAAAAAcUCgAAAAAEODAAA3AMAIDkAAOADACA6AADgAwAguAIBAAAAAbkCAQAAAAS6AgEAAAAEuwIBAAAAAbwCAQAAAAG9AgEAAAABvgIBAAAAAb8CAQDfAwAhxgIBAAAAAccCAQAAAAHIAgEAAAABC7gCAQAAAAG5AgEAAAAEugIBAAAABLsCAQAAAAG8AgEAAAABvQIBAAAAAb4CAQAAAAG_AgEA4AMAIcYCAQAAAAHHAgEAAAAByAIBAAAAAQoqAADVAwAgrQIAAOEDADCuAgAAugMAEK8CAADhAwAwsAIBANQDACG2AkAA1gMAIckCAQDUAwAhygIBAOIDACHLAgIA4wMAIcwCQADWAwAhDgwAANkDACA5AADnAwAgOgAA5wMAILgCAQAAAAG5AgEAAAAFugIBAAAABbsCAQAAAAG8AgEAAAABvQIBAAAAAb4CAQAAAAG_AgEA5gMAIcYCAQAAAAHHAgEAAAAByAIBAAAAAQ0MAADcAwAgNwAA5QMAIDgAANwDACA5AADcAwAgOgAA3AMAILgCAgAAAAG5AgIAAAAEugICAAAABLsCAgAAAAG8AgIAAAABvQICAAAAAb4CAgAAAAG_AgIA5AMAIQ0MAADcAwAgNwAA5QMAIDgAANwDACA5AADcAwAgOgAA3AMAILgCAgAAAAG5AgIAAAAEugICAAAABLsCAgAAAAG8AgIAAAABvQICAAAAAb4CAgAAAAG_AgIA5AMAIQi4AggAAAABuQIIAAAABLoCCAAAAAS7AggAAAABvAIIAAAAAb0CCAAAAAG-AggAAAABvwIIAOUDACEODAAA2QMAIDkAAOcDACA6AADnAwAguAIBAAAAAbkCAQAAAAW6AgEAAAAFuwIBAAAAAbwCAQAAAAG9AgEAAAABvgIBAAAAAb8CAQDmAwAhxgIBAAAAAccCAQAAAAHIAgEAAAABC7gCAQAAAAG5AgEAAAAFugIBAAAABbsCAQAAAAG8AgEAAAABvQIBAAAAAb4CAQAAAAG_AgEA5wMAIcYCAQAAAAHHAgEAAAAByAIBAAAAAQ-tAgAA6AMAMK4CAACiAwAQrwIAAOgDADCwAgEA1AMAIbYCQADWAwAhyQIBAOIDACHKAgEA1AMAIc0CAgDpAwAhzwIAAOoDzwIi0AIBAOIDACHRAgEA4gMAIdICAQDiAwAh0wIBAOIDACHUAgAA6wMAINUCAQDiAwAhDQwAANkDACA3AADwAwAgOAAA2QMAIDkAANkDACA6AADZAwAguAICAAAAAbkCAgAAAAW6AgIAAAAFuwICAAAAAbwCAgAAAAG9AgIAAAABvgICAAAAAb8CAgDvAwAhBwwAANwDACA5AADuAwAgOgAA7gMAILgCAAAAzwICuQIAAADPAgi6AgAAAM8CCL8CAADtA88CIg8MAADZAwAgOQAA7AMAIDoAAOwDACC4AoAAAAABuwKAAAAAAbwCgAAAAAG9AoAAAAABvgKAAAAAAb8CgAAAAAHAAgEAAAABwQIBAAAAAcICAQAAAAHDAoAAAAABxAKAAAAAAcUCgAAAAAEMuAKAAAAAAbsCgAAAAAG8AoAAAAABvQKAAAAAAb4CgAAAAAG_AoAAAAABwAIBAAAAAcECAQAAAAHCAgEAAAABwwKAAAAAAcQCgAAAAAHFAoAAAAABBwwAANwDACA5AADuAwAgOgAA7gMAILgCAAAAzwICuQIAAADPAgi6AgAAAM8CCL8CAADtA88CIgS4AgAAAM8CArkCAAAAzwIIugIAAADPAgi_AgAA7gPPAiINDAAA2QMAIDcAAPADACA4AADZAwAgOQAA2QMAIDoAANkDACC4AgIAAAABuQICAAAABboCAgAAAAW7AgIAAAABvAICAAAAAb0CAgAAAAG-AgIAAAABvwICAO8DACEIuAIIAAAAAbkCCAAAAAW6AggAAAAFuwIIAAAAAbwCCAAAAAG9AggAAAABvgIIAAAAAb8CCADwAwAhBq0CAADxAwAwrgIAAIoDABCvAgAA8QMAMLYCQADWAwAhyQIBANQDACHKAgEA1AMAIQitAgAA8gMAMK4CAAD0AgAQrwIAAPIDADCwAgEA1AMAIbYCQADWAwAhygIBANQDACHWAgEA4gMAIdgCAADzA9gCIgcMAADcAwAgOQAA9QMAIDoAAPUDACC4AgAAANgCArkCAAAA2AIIugIAAADYAgi_AgAA9APYAiIHDAAA3AMAIDkAAPUDACA6AAD1AwAguAIAAADYAgK5AgAAANgCCLoCAAAA2AIIvwIAAPQD2AIiBLgCAAAA2AICuQIAAADYAgi6AgAAANgCCL8CAAD1A9gCIgetAgAA9gMAMK4CAADcAgAQrwIAAPYDADC2AkAA1gMAIckCAQDUAwAhygIBANQDACHaAgAA9wPaAiIHDAAA3AMAIDkAAPkDACA6AAD5AwAguAIAAADaAgK5AgAAANoCCLoCAAAA2gIIvwIAAPgD2gIiBwwAANwDACA5AAD5AwAgOgAA-QMAILgCAAAA2gICuQIAAADaAgi6AgAAANoCCL8CAAD4A9oCIgS4AgAAANoCArkCAAAA2gIIugIAAADaAgi_AgAA-QPaAiIIrQIAAPoDADCuAgAAxgIAEK8CAAD6AwAwsAIBANQDACG2AkAA1gMAIdsCAgDpAwAh3AIBANQDACHdAgEA4gMAIQkLAACABAAgrQIAAPsDADCuAgAAswIAEK8CAAD7AwAwsAIBAPwDACG2AkAA_wMAIdsCAgD9AwAh3AIBAPwDACHdAgEA_gMAIQu4AgEAAAABuQIBAAAABLoCAQAAAAS7AgEAAAABvAIBAAAAAb0CAQAAAAG-AgEAAAABvwIBAOADACHGAgEAAAABxwIBAAAAAcgCAQAAAAEIuAICAAAAAbkCAgAAAAW6AgIAAAAFuwICAAAAAbwCAgAAAAG9AgIAAAABvgICAAAAAb8CAgDZAwAhC7gCAQAAAAG5AgEAAAAFugIBAAAABbsCAQAAAAG8AgEAAAABvQIBAAAAAb4CAQAAAAG_AgEA5wMAIcYCAQAAAAHHAgEAAAAByAIBAAAAAQi4AkAAAAABuQJAAAAABLoCQAAAAAS7AkAAAAABvAJAAAAAAb0CQAAAAAG-AkAAAAABvwJAAN0DACED3gIAABwAIN8CAAAcACDgAgAAHAAgCa0CAACBBAAwrgIAAK0CABCvAgAAgQQAMLACAQDUAwAhtgJAANYDACHKAgEA1AMAIc8CAACCBOQCIuECAgDjAwAh4gIBANQDACEHDAAA3AMAIDkAAIQEACA6AACEBAAguAIAAADkAgK5AgAAAOQCCLoCAAAA5AIIvwIAAIME5AIiBwwAANwDACA5AACEBAAgOgAAhAQAILgCAAAA5AICuQIAAADkAgi6AgAAAOQCCL8CAACDBOQCIgS4AgAAAOQCArkCAAAA5AIIugIAAADkAgi_AgAAhATkAiIHrQIAAIUEADCuAgAAlwIAEK8CAACFBAAwtgJAANYDACHKAgEA1AMAIc0CAgDjAwAh5AIBANQDACEOrQIAAIYEADCuAgAAgQIAEK8CAACGBAAwtgJAANYDACHKAgEA1AMAIc0CAgDjAwAh5QIBANQDACHmAgEA4gMAIecCAQDiAwAh6AIBAOIDACHpAgEA1AMAIeoCAQDiAwAh6wIBAOIDACHsAkAA1wMAIRKtAgAAhwQAMK4CAADrAQAQrwIAAIcEADCwAgEA1AMAIbYCQADWAwAhzAJAANYDACHbAgIA6QMAIe0CAgDpAwAh7gIBAOIDACHvAgIA6QMAIfECAACIBPECIvICIACJBAAh8wIgAIkEACH0AgIA4wMAIfUCAgDjAwAh9gICAOMDACH3AgIA4wMAIfgCQADXAwAhBwwAANwDACA5AACNBAAgOgAAjQQAILgCAAAA8QICuQIAAADxAgi6AgAAAPECCL8CAACMBPECIgUMAADcAwAgOQAAiwQAIDoAAIsEACC4AiAAAAABvwIgAIoEACEFDAAA3AMAIDkAAIsEACA6AACLBAAguAIgAAAAAb8CIACKBAAhArgCIAAAAAG_AiAAiwQAIQcMAADcAwAgOQAAjQQAIDoAAI0EACC4AgAAAPECArkCAAAA8QIIugIAAADxAgi_AgAAjATxAiIEuAIAAADxAgK5AgAAAPECCLoCAAAA8QIIvwIAAI0E8QIiDq0CAACOBAAwrgIAAM8BABCvAgAAjgQAMLACAQDUAwAhtgJAANcDACHJAgEA1AMAIdwCAQDiAwAh-QIBANQDACH6AgEA1AMAIfsCAgDjAwAh_AIBANQDACH9AiAAiQQAIf4CAQDiAwAh_wIBAOIDACEKrQIAAI8EADCuAgAAuQEAEK8CAACPBAAwsAIBANQDACG2AkAA1wMAIckCAQDiAwAhzAJAANcDACGAAwEA1AMAIYEDAQDUAwAhggNAANYDACEMrQIAAJAEADCuAgAAoQEAEK8CAACQBAAwsAIBANQDACG2AkAA1gMAIckCAQDUAwAhzAJAANYDACHSAgEA4gMAIYIDQADWAwAhgwMBANQDACGEAwEA4gMAIYUDAQDiAwAhEK0CAACRBAAwrgIAAIsBABCvAgAAkQQAMLACAQDUAwAhtgJAANYDACHJAgEA1AMAIcwCQADWAwAhhgMBANQDACGHAwEA1AMAIYgDAQDiAwAhiQMBAOIDACGKA0AA1wMAIYsDQADXAwAhjAMBAOIDACGNAwEA4gMAIY4DAQDiAwAhGa0CAACSBAAwrgIAAHUAEK8CAACSBAAwsAIBANQDACG2AkAA1gMAIcwCQADWAwAh2gIBAOIDACHbAgIA6QMAIdwCAQDiAwAh-AJAANcDACGPAwEA4gMAIZADIACJBAAhkQMBAOIDACGTAwAAkwSTAyKVAwAAlASVAyKWAyAAiQQAIZcDAQDiAwAhmAMBAOIDACGZAwAA6wMAIJoDQADXAwAhmwMBAOIDACGcAyAAlQQAIZ0DAQDiAwAhngNAANcDACGfA0AA1wMAIQcMAADcAwAgOQAAmwQAIDoAAJsEACC4AgAAAJMDArkCAAAAkwMIugIAAACTAwi_AgAAmgSTAyIHDAAA3AMAIDkAAJkEACA6AACZBAAguAIAAACVAwK5AgAAAJUDCLoCAAAAlQMIvwIAAJgElQMiBQwAANkDACA5AACXBAAgOgAAlwQAILgCIAAAAAG_AiAAlgQAIQUMAADZAwAgOQAAlwQAIDoAAJcEACC4AiAAAAABvwIgAJYEACECuAIgAAAAAb8CIACXBAAhBwwAANwDACA5AACZBAAgOgAAmQQAILgCAAAAlQMCuQIAAACVAwi6AgAAAJUDCL8CAACYBJUDIgS4AgAAAJUDArkCAAAAlQMIugIAAACVAwi_AgAAmQSVAyIHDAAA3AMAIDkAAJsEACA6AACbBAAguAIAAACTAwK5AgAAAJMDCLoCAAAAkwMIvwIAAJoEkwMiBLgCAAAAkwMCuQIAAACTAwi6AgAAAJMDCL8CAACbBJMDIg8DAACgBAAgrQIAAJwEADCuAgAAVAAQrwIAAJwEADCwAgEA_AMAIbYCQACfBAAhyQIBAPwDACHcAgEA_gMAIfkCAQD8AwAh-gIBAPwDACH7AgIAnQQAIfwCAQD8AwAh_QIgAJ4EACH-AgEA_gMAIf8CAQD-AwAhCLgCAgAAAAG5AgIAAAAEugICAAAABLsCAgAAAAG8AgIAAAABvQICAAAAAb4CAgAAAAG_AgIA3AMAIQK4AiAAAAABvwIgAIsEACEIuAJAAAAAAbkCQAAAAAW6AkAAAAAFuwJAAAAAAbwCQAAAAAG9AkAAAAABvgJAAAAAAb8CQADaAwAhJQQAAMoEACAFAADLBAAgBgAAzAQAIB0AAL4EACAeAAC_BAAgIAAAzQQAICEAAMAEACAiAADBBAAgIwAAwgQAICQAAM4EACCtAgAAxgQAMK4CAAAPABCvAgAAxgQAMLACAQD8AwAhtgJAAP8DACHMAkAA_wMAIdoCAQD-AwAh2wICAP0DACHcAgEA_gMAIfgCQACfBAAhjwMBAP4DACGQAyAAngQAIZEDAQD-AwAhkwMAAMcEkwMilQMAAMgElQMilgMgAJ4EACGXAwEA_gMAIZgDAQD-AwAhmQMAAKcEACCaA0AAnwQAIZsDAQD-AwAhnAMgAMkEACGdAwEA_gMAIZ4DQACfBAAhnwNAAJ8EACGlAwAADwAgpgMAAA8AIAwfAACgBAAgrQIAAKEEADCuAgAATQAQrwIAAKEEADCwAgEA_AMAIbECAQD8AwAhsgIBAPwDACGzAgEA_AMAIbQCAQD8AwAhtQIAAKIEACC2AkAA_wMAIbcCQACfBAAhDLgCgAAAAAG7AoAAAAABvAKAAAAAAb0CgAAAAAG-AoAAAAABvwKAAAAAAcACAQAAAAHBAgEAAAABwgIBAAAAAcMCgAAAAAHEAoAAAAABxQKAAAAAAQwDAACgBAAgBwAApAQAICoAAKIEACCtAgAAowQAMK4CAAA_ABCvAgAAowQAMLACAQD8AwAhtgJAAP8DACHJAgEA_AMAIcoCAQD-AwAhywICAJ0EACHMAkAA_wMAIR8RAAC8BAAgEgAApAQAIBMAALEEACAUAAC8BAAgFQAAvQQAIBYAAL4EACAYAAC_BAAgGQAAsgQAIBoAAMAEACAbAADBBAAgHAAAwgQAIK0CAAC6BAAwrgIAABcAEK8CAAC6BAAwsAIBAPwDACG2AkAA_wMAIcwCQAD_AwAh2wICAP0DACHtAgIA_QMAIe4CAQD-AwAh7wICAP0DACHxAgAAuwTxAiLyAiAAngQAIfMCIACeBAAh9AICAJ0EACH1AgIAnQQAIfYCAgCdBAAh9wICAJ0EACH4AkAAnwQAIaUDAAAXACCmAwAAFwAgEQMAAKkEACAHAACoBAAgrQIAAKUEADCuAgAAOgAQrwIAAKUEADCwAgEA_AMAIbYCQAD_AwAhyQIBAP4DACHKAgEA_AMAIc0CAgD9AwAhzwIAAKYEzwIi0AIBAP4DACHRAgEA_gMAIdICAQD-AwAh0wIBAP4DACHUAgAApwQAINUCAQD-AwAhBLgCAAAAzwICuQIAAADPAgi6AgAAAM8CCL8CAADuA88CIgy4AoAAAAABuwKAAAAAAbwCgAAAAAG9AoAAAAABvgKAAAAAAb8CgAAAAAHAAgEAAAABwQIBAAAAAcICAQAAAAHDAoAAAAABxAKAAAAAAcUCgAAAAAEfEQAAvAQAIBIAAKQEACATAACxBAAgFAAAvAQAIBUAAL0EACAWAAC-BAAgGAAAvwQAIBkAALIEACAaAADABAAgGwAAwQQAIBwAAMIEACCtAgAAugQAMK4CAAAXABCvAgAAugQAMLACAQD8AwAhtgJAAP8DACHMAkAA_wMAIdsCAgD9AwAh7QICAP0DACHuAgEA_gMAIe8CAgD9AwAh8QIAALsE8QIi8gIgAJ4EACHzAiAAngQAIfQCAgCdBAAh9QICAJ0EACH2AgIAnQQAIfcCAgCdBAAh-AJAAJ8EACGlAwAAFwAgpgMAABcAICUEAADKBAAgBQAAywQAIAYAAMwEACAdAAC-BAAgHgAAvwQAICAAAM0EACAhAADABAAgIgAAwQQAICMAAMIEACAkAADOBAAgrQIAAMYEADCuAgAADwAQrwIAAMYEADCwAgEA_AMAIbYCQAD_AwAhzAJAAP8DACHaAgEA_gMAIdsCAgD9AwAh3AIBAP4DACH4AkAAnwQAIY8DAQD-AwAhkAMgAJ4EACGRAwEA_gMAIZMDAADHBJMDIpUDAADIBJUDIpYDIACeBAAhlwMBAP4DACGYAwEA_gMAIZkDAACnBAAgmgNAAJ8EACGbAwEA_gMAIZwDIADJBAAhnQMBAP4DACGeA0AAnwQAIZ8DQACfBAAhpQMAAA8AIKYDAAAPACACyQIBAAAAAcoCAQAAAAEIAwAAoAQAIAcAAKgEACCtAgAAqwQAMK4CAAA2ABCvAgAAqwQAMLYCQAD_AwAhyQIBAPwDACHKAgEA_AMAIQLKAgEAAAAB1gIBAAAAAQoHAACoBAAgFwAAqQQAIK0CAACtBAAwrgIAADAAEK8CAACtBAAwsAIBAPwDACG2AkAA_wMAIcoCAQD8AwAh1gIBAP4DACHYAgAArgTYAiIEuAIAAADYAgK5AgAAANgCCLoCAAAA2AIIvwIAAPUD2AIiAsoCAQAAAAHNAgIAAAABEwcAAKgEACAIAACkBAAgCQAAsQQAIA4AAIAEACAQAACyBAAgrQIAALAEADCuAgAAFQAQrwIAALAEADC2AkAA_wMAIcoCAQD8AwAhzQICAJ0EACHlAgEA_AMAIeYCAQD-AwAh5wIBAP4DACHoAgEA_gMAIekCAQD8AwAh6gIBAP4DACHrAgEA_gMAIewCQACfBAAhA94CAAAXACDfAgAAFwAg4AIAABcAIAPeAgAAIgAg3wIAACIAIOACAAAiACALBwAAqAQAIA8AALUEACCtAgAAswQAMK4CAAAiABCvAgAAswQAMLACAQD8AwAhtgJAAP8DACHKAgEA_AMAIc8CAAC0BOQCIuECAgCdBAAh4gIBAPwDACEEuAIAAADkAgK5AgAAAOQCCLoCAAAA5AIIvwIAAIQE5AIiFQcAAKgEACAIAACkBAAgCQAAsQQAIA4AAIAEACAQAACyBAAgrQIAALAEADCuAgAAFQAQrwIAALAEADC2AkAA_wMAIcoCAQD8AwAhzQICAJ0EACHlAgEA_AMAIeYCAQD-AwAh5wIBAP4DACHoAgEA_gMAIekCAQD8AwAh6gIBAP4DACHrAgEA_gMAIewCQACfBAAhpQMAABUAIKYDAAAVACADygIBAAAAAc0CAgAAAAHkAgEAAAABCQoAALUEACANAAC4BAAgrQIAALcEADCuAgAAHAAQrwIAALcEADC2AkAA_wMAIcoCAQD8AwAhzQICAJ0EACHkAgEA_AMAIQsLAACABAAgrQIAAPsDADCuAgAAswIAEK8CAAD7AwAwsAIBAPwDACG2AkAA_wMAIdsCAgD9AwAh3AIBAPwDACHdAgEA_gMAIaUDAACzAgAgpgMAALMCACACsAIBAAAAAe0CAgAAAAEdEQAAvAQAIBIAAKQEACATAACxBAAgFAAAvAQAIBUAAL0EACAWAAC-BAAgGAAAvwQAIBkAALIEACAaAADABAAgGwAAwQQAIBwAAMIEACCtAgAAugQAMK4CAAAXABCvAgAAugQAMLACAQD8AwAhtgJAAP8DACHMAkAA_wMAIdsCAgD9AwAh7QICAP0DACHuAgEA_gMAIe8CAgD9AwAh8QIAALsE8QIi8gIgAJ4EACHzAiAAngQAIfQCAgCdBAAh9QICAJ0EACH2AgIAnQQAIfcCAgCdBAAh-AJAAJ8EACEEuAIAAADxAgK5AgAAAPECCLoCAAAA8QIIvwIAAI0E8QIiFQcAAKgEACAIAACkBAAgCQAAsQQAIA4AAIAEACAQAACyBAAgrQIAALAEADCuAgAAFQAQrwIAALAEADC2AkAA_wMAIcoCAQD8AwAhzQICAJ0EACHlAgEA_AMAIeYCAQD-AwAh5wIBAP4DACHoAgEA_gMAIekCAQD8AwAh6gIBAP4DACHrAgEA_gMAIewCQACfBAAhpQMAABUAIKYDAAAVACAD3gIAABUAIN8CAAAVACDgAgAAFQAgA94CAAARACDfAgAAEQAg4AIAABEAIAPeAgAAMAAg3wIAADAAIOACAAAwACAD3gIAADYAIN8CAAA2ACDgAgAANgAgA94CAAA6ACDfAgAAOgAg4AIAADoAIAPeAgAAPwAg3wIAAD8AIOACAAA_ACACyQIBAAAAAcoCAQAAAAEJAwAAoAQAIAcAAKgEACCtAgAAxAQAMK4CAAARABCvAgAAxAQAMLYCQAD_AwAhyQIBAPwDACHKAgEA_AMAIdoCAADFBNoCIgS4AgAAANoCArkCAAAA2gIIugIAAADaAgi_AgAA-QPaAiIjBAAAygQAIAUAAMsEACAGAADMBAAgHQAAvgQAIB4AAL8EACAgAADNBAAgIQAAwAQAICIAAMEEACAjAADCBAAgJAAAzgQAIK0CAADGBAAwrgIAAA8AEK8CAADGBAAwsAIBAPwDACG2AkAA_wMAIcwCQAD_AwAh2gIBAP4DACHbAgIA_QMAIdwCAQD-AwAh-AJAAJ8EACGPAwEA_gMAIZADIACeBAAhkQMBAP4DACGTAwAAxwSTAyKVAwAAyASVAyKWAyAAngQAIZcDAQD-AwAhmAMBAP4DACGZAwAApwQAIJoDQACfBAAhmwMBAP4DACGcAyAAyQQAIZ0DAQD-AwAhngNAAJ8EACGfA0AAnwQAIQS4AgAAAJMDArkCAAAAkwMIugIAAACTAwi_AgAAmwSTAyIEuAIAAACVAwK5AgAAAJUDCLoCAAAAlQMIvwIAAJkElQMiArgCIAAAAAG_AiAAlwQAIQPeAgAAAwAg3wIAAAMAIOACAAADACAD3gIAAAcAIN8CAAAHACDgAgAABwAgA94CAAALACDfAgAACwAg4AIAAAsAIAPeAgAATQAg3wIAAE0AIOACAABNACAD3gIAAFQAIN8CAABUACDgAgAAVAAgCwMAAKkEACCtAgAAzwQAMK4CAAALABCvAgAAzwQAMLACAQD8AwAhtgJAAJ8EACHJAgEA_gMAIcwCQACfBAAhgAMBAPwDACGBAwEA_AMAIYIDQAD_AwAhDQMAAKAEACCtAgAA0AQAMK4CAAAHABCvAgAA0AQAMLACAQD8AwAhtgJAAP8DACHJAgEA_AMAIcwCQAD_AwAh0gIBAP4DACGCA0AA_wMAIYMDAQD8AwAhhAMBAP4DACGFAwEA_gMAIREDAACgBAAgrQIAANEEADCuAgAAAwAQrwIAANEEADCwAgEA_AMAIbYCQAD_AwAhyQIBAPwDACHMAkAA_wMAIYYDAQD8AwAhhwMBAPwDACGIAwEA_gMAIYkDAQD-AwAhigNAAJ8EACGLA0AAnwQAIYwDAQD-AwAhjQMBAP4DACGOAwEA_gMAIQAAAAABqgMBAAAAAQGqA0AAAAABAaoDQAAAAAEFMQAAiwkAIDIAAI4JACCnAwAAjAkAIKgDAACNCQAgrQMAAAEAIAMxAACLCQAgpwMAAIwJACCtAwAAAQAgAAAAAAAFqgMCAAAAAbADAgAAAAGxAwIAAAABsgMCAAAAAbMDAgAAAAEBqgMBAAAAAQUxAACDCQAgMgAAiQkAIKcDAACECQAgqAMAAIgJACCtAwAAAQAgBzEAAIEJACAyAACGCQAgpwMAAIIJACCoAwAAhQkAIKsDAAAXACCsAwAAFwAgrQMAABoAIAMxAACDCQAgpwMAAIQJACCtAwAAAQAgAzEAAIEJACCnAwAAggkAIK0DAAAaACAAAAAAAAWqAwIAAAABsAMCAAAAAbEDAgAAAAGyAwIAAAABswMCAAAAAQGqAwAAAM8CAgUxAAD5CAAgMgAA_wgAIKcDAAD6CAAgqAMAAP4IACCtAwAAGgAgBzEAAPcIACAyAAD8CAAgpwMAAPgIACCoAwAA-wgAIKsDAAAPACCsAwAADwAgrQMAAAEAIAMxAAD5CAAgpwMAAPoIACCtAwAAGgAgAzEAAPcIACCnAwAA-AgAIK0DAAABACAAAAAFMQAA7wgAIDIAAPUIACCnAwAA8AgAIKgDAAD0CAAgrQMAABoAIAUxAADtCAAgMgAA8ggAIKcDAADuCAAgqAMAAPEIACCtAwAAAQAgAzEAAO8IACCnAwAA8AgAIK0DAAAaACADMQAA7QgAIKcDAADuCAAgrQMAAAEAIAAAAAGqAwAAANgCAgUxAADlCAAgMgAA6wgAIKcDAADmCAAgqAMAAOoIACCtAwAAGgAgBzEAAOMIACAyAADoCAAgpwMAAOQIACCoAwAA5wgAIKsDAAAPACCsAwAADwAgrQMAAAEAIAMxAADlCAAgpwMAAOYIACCtAwAAGgAgAzEAAOMIACCnAwAA5AgAIK0DAAABACAAAAABqgMAAADaAgIFMQAA2wgAIDIAAOEIACCnAwAA3AgAIKgDAADgCAAgrQMAABoAIAUxAADZCAAgMgAA3ggAIKcDAADaCAAgqAMAAN0IACCtAwAAAQAgAzEAANsIACCnAwAA3AgAIK0DAAAaACADMQAA2QgAIKcDAADaCAAgrQMAAAEAIAAAAAAACzEAAI4FADAyAACTBQAwpwMAAI8FADCoAwAAkAUAMKkDAACRBQAgqgMAAJIFADCrAwAAkgUAMKwDAACSBQAwrQMAAJIFADCuAwAAlAUAMK8DAACVBQAwBAoAAJsFACC2AkAAAAABygIBAAAAAc0CAgAAAAECAAAAHgAgMQAAmgUAIAMAAAAeACAxAACaBQAgMgAAmAUAIAEqAADYCAAwCgoAALUEACANAAC4BAAgrQIAALcEADCuAgAAHAAQrwIAALcEADC2AkAA_wMAIcoCAQD8AwAhzQICAJ0EACHkAgEA_AMAIaMDAAC2BAAgAgAAAB4AICoAAJgFACACAAAAlgUAICoAAJcFACAHrQIAAJUFADCuAgAAlgUAEK8CAACVBQAwtgJAAP8DACHKAgEA_AMAIc0CAgCdBAAh5AIBAPwDACEHrQIAAJUFADCuAgAAlgUAEK8CAACVBQAwtgJAAP8DACHKAgEA_AMAIc0CAgCdBAAh5AIBAPwDACEDtgJAANcEACHKAgEA1gQAIc0CAgDgBAAhBAoAAJkFACC2AkAA1wQAIcoCAQDWBAAhzQICAOAEACEFMQAA0wgAIDIAANYIACCnAwAA1AgAIKgDAADVCAAgrQMAAC0AIAQKAACbBQAgtgJAAAAAAcoCAQAAAAHNAgIAAAABAzEAANMIACCnAwAA1AgAIK0DAAAtACAEMQAAjgUAMKcDAACPBQAwqQMAAJEFACCtAwAAkgUAMAAAAAAAAAGqAwAAAOQCAgUxAADLCAAgMgAA0QgAIKcDAADMCAAgqAMAANAIACCtAwAAGgAgBTEAAMkIACAyAADOCAAgpwMAAMoIACCoAwAAzQgAIK0DAAAtACADMQAAywgAIKcDAADMCAAgrQMAABoAIAMxAADJCAAgpwMAAMoIACCtAwAALQAgAAAAAAAFMQAAxAgAIDIAAMcIACCnAwAAxQgAIKgDAADGCAAgrQMAALACACADMQAAxAgAIKcDAADFCAAgrQMAALACACAAAAAAAAUxAAClCAAgMgAAwggAIKcDAACmCAAgqAMAAMEIACCtAwAAGgAgBzEAALoGACAyAADTBgAgpwMAALsGACCoAwAA0gYAIKsDAAAXACCsAwAAFwAgrQMAABoAIAsxAADOBQAwMgAA0wUAMKcDAADPBQAwqAMAANAFADCpAwAA0QUAIKoDAADSBQAwqwMAANIFADCsAwAA0gUAMK0DAADSBQAwrgMAANQFADCvAwAA1QUAMAsxAADFBQAwMgAAyQUAMKcDAADGBQAwqAMAAMcFADCpAwAAyAUAIKoDAACSBQAwqwMAAJIFADCsAwAAkgUAMK0DAACSBQAwrgMAAMoFADCvAwAAlQUAMAsxAAC5BQAwMgAAvgUAMKcDAAC6BQAwqAMAALsFADCpAwAAvAUAIKoDAAC9BQAwqwMAAL0FADCsAwAAvQUAMK0DAAC9BQAwrgMAAL8FADCvAwAAwAUAMAUHAACmBQAgsAIBAAAAAbYCQAAAAAHPAgAAAOQCAuICAQAAAAECAAAAJAAgMQAAxAUAIAMAAAAkACAxAADEBQAgMgAAwwUAIAEqAADACAAwCwcAAKgEACAPAAC1BAAgrQIAALMEADCuAgAAIgAQrwIAALMEADCwAgEAAAABtgJAAP8DACHKAgEA_AMAIc8CAAC0BOQCIuECAgCdBAAh4gIBAPwDACECAAAAJAAgKgAAwwUAIAIAAADBBQAgKgAAwgUAIAmtAgAAwAUAMK4CAADBBQAQrwIAAMAFADCwAgEA_AMAIbYCQAD_AwAhygIBAPwDACHPAgAAtATkAiLhAgIAnQQAIeICAQD8AwAhCa0CAADABQAwrgIAAMEFABCvAgAAwAUAMLACAQD8AwAhtgJAAP8DACHKAgEA_AMAIc8CAAC0BOQCIuECAgCdBAAh4gIBAPwDACEEsAIBANYEACG2AkAA1wQAIc8CAACjBeQCIuICAQDWBAAhBQcAAKQFACCwAgEA1gQAIbYCQADXBAAhzwIAAKMF5AIi4gIBANYEACEFBwAApgUAILACAQAAAAG2AkAAAAABzwIAAADkAgLiAgEAAAABAw0AAK4FACC2AkAAAAAB5AIBAAAAAQIAAAAeACAxAADNBQAgAwAAAB4AIDEAAM0FACAyAADMBQAgASoAAL8IADACAAAAHgAgKgAAzAUAIAIAAACWBQAgKgAAywUAIAK2AkAA1wQAIeQCAQDWBAAhAw0AAK0FACC2AkAA1wQAIeQCAQDWBAAhAw0AAK4FACC2AkAAAAAB5AIBAAAAARcRAADKBgAgEgAAvAYAIBMAAL0GACAVAAC_BgAgFgAAwAYAIBgAAMEGACAZAADCBgAgGgAAwwYAIBsAAMQGACAcAADFBgAgsAIBAAAAAbYCQAAAAAHMAkAAAAAB2wICAAAAAe0CAgAAAAHxAgAAAPECAvICIAAAAAHzAiAAAAAB9AICAAAAAfUCAgAAAAH2AgIAAAAB9wICAAAAAfgCQAAAAAECAAAAGgAgMQAA0QYAIAMAAAAaACAxAADRBgAgMgAA2gUAIAEqAAC-CAAwHhEAALwEACASAACkBAAgEwAAsQQAIBQAALwEACAVAAC9BAAgFgAAvgQAIBgAAL8EACAZAACyBAAgGgAAwAQAIBsAAMEEACAcAADCBAAgrQIAALoEADCuAgAAFwAQrwIAALoEADCwAgEAAAABtgJAAP8DACHMAkAA_wMAIdsCAgAAAAHtAgIA_QMAIe4CAQD-AwAh7wICAP0DACHxAgAAuwTxAiLyAiAAngQAIfMCIACeBAAh9AICAJ0EACH1AgIAnQQAIfYCAgCdBAAh9wICAJ0EACH4AkAAnwQAIaQDAAC5BAAgAgAAABoAICoAANoFACACAAAA1gUAICoAANcFACASrQIAANUFADCuAgAA1gUAEK8CAADVBQAwsAIBAPwDACG2AkAA_wMAIcwCQAD_AwAh2wICAP0DACHtAgIA_QMAIe4CAQD-AwAh7wICAP0DACHxAgAAuwTxAiLyAiAAngQAIfMCIACeBAAh9AICAJ0EACH1AgIAnQQAIfYCAgCdBAAh9wICAJ0EACH4AkAAnwQAIRKtAgAA1QUAMK4CAADWBQAQrwIAANUFADCwAgEA_AMAIbYCQAD_AwAhzAJAAP8DACHbAgIA_QMAIe0CAgD9AwAh7gIBAP4DACHvAgIA_QMAIfECAAC7BPECIvICIACeBAAh8wIgAJ4EACH0AgIAnQQAIfUCAgCdBAAh9gICAJ0EACH3AgIAnQQAIfgCQACfBAAhDbACAQDWBAAhtgJAANcEACHMAkAA1wQAIdsCAgDrBAAh7QICAOsEACHxAgAA2AXxAiLyAiAA2QUAIfMCIADZBQAh9AICAOAEACH1AgIA4AQAIfYCAgDgBAAh9wICAOAEACH4AkAA2AQAIQGqAwAAAPECAgGqAyAAAAABFxEAANsFACASAADcBQAgEwAA3QUAIBUAAN4FACAWAADfBQAgGAAA4AUAIBkAAOEFACAaAADiBQAgGwAA4wUAIBwAAOQFACCwAgEA1gQAIbYCQADXBAAhzAJAANcEACHbAgIA6wQAIe0CAgDrBAAh8QIAANgF8QIi8gIgANkFACHzAiAA2QUAIfQCAgDgBAAh9QICAOAEACH2AgIA4AQAIfcCAgDgBAAh-AJAANgEACEHMQAArAgAIDIAALwIACCnAwAArQgAIKgDAAC7CAAgqwMAABUAIKwDAAAVACCtAwAALQAgBzEAAK8IACAyAAC5CAAgpwMAALAIACCoAwAAuAgAIKsDAAAXACCsAwAAFwAgrQMAABoAIAsxAADGBgAwMgAAzAYAMKcDAADHBgAwqAMAAMsGADCpAwAAyAYAIKoDAADSBQAwqwMAANIFADCsAwAA0gUAMK0DAADSBQAwrgMAAM0GADCvAwAA1QUAMAsxAACqBgAwMgAArwYAMKcDAACrBgAwqAMAAKwGADCpAwAArQYAIKoDAACuBgAwqwMAAK4GADCsAwAArgYAMK0DAACuBgAwrgMAALAGADCvAwAAsQYAMAsxAACeBgAwMgAAowYAMKcDAACfBgAwqAMAAKAGADCpAwAAoQYAIKoDAACiBgAwqwMAAKIGADCsAwAAogYAMK0DAACiBgAwrgMAAKQGADCvAwAApQYAMAsxAACSBgAwMgAAlwYAMKcDAACTBgAwqAMAAJQGADCpAwAAlQYAIKoDAACWBgAwqwMAAJYGADCsAwAAlgYAMK0DAACWBgAwrgMAAJgGADCvAwAAmQYAMAsxAACJBgAwMgAAjQYAMKcDAACKBgAwqAMAAIsGADCpAwAAjAYAIKoDAAC9BQAwqwMAAL0FADCsAwAAvQUAMK0DAAC9BQAwrgMAAI4GADCvAwAAwAUAMAsxAAD9BQAwMgAAggYAMKcDAAD-BQAwqAMAAP8FADCpAwAAgAYAIKoDAACBBgAwqwMAAIEGADCsAwAAgQYAMK0DAACBBgAwrgMAAIMGADCvAwAAhAYAMAsxAADxBQAwMgAA9gUAMKcDAADyBQAwqAMAAPMFADCpAwAA9AUAIKoDAAD1BQAwqwMAAPUFADCsAwAA9QUAMK0DAAD1BQAwrgMAAPcFADCvAwAA-AUAMAsxAADlBQAwMgAA6gUAMKcDAADmBQAwqAMAAOcFADCpAwAA6AUAIKoDAADpBQAwqwMAAOkFADCsAwAA6QUAMK0DAADpBQAwrgMAAOsFADCvAwAA7AUAMAcDAADkBAAgKoAAAAABsAIBAAAAAbYCQAAAAAHJAgEAAAABywICAAAAAcwCQAAAAAECAAAAQQAgMQAA8AUAIAMAAABBACAxAADwBQAgMgAA7wUAIAEqAAC3CAAwDAMAAKAEACAHAACkBAAgKgAAogQAIK0CAACjBAAwrgIAAD8AEK8CAACjBAAwsAIBAAAAAbYCQAD_AwAhyQIBAPwDACHKAgEA_gMAIcsCAgCdBAAhzAJAAP8DACECAAAAQQAgKgAA7wUAIAIAAADtBQAgKgAA7gUAIAoqAACiBAAgrQIAAOwFADCuAgAA7QUAEK8CAADsBQAwsAIBAPwDACG2AkAA_wMAIckCAQD8AwAhygIBAP4DACHLAgIAnQQAIcwCQAD_AwAhCioAAKIEACCtAgAA7AUAMK4CAADtBQAQrwIAAOwFADCwAgEA_AMAIbYCQAD_AwAhyQIBAPwDACHKAgEA_gMAIcsCAgCdBAAhzAJAAP8DACEGKoAAAAABsAIBANYEACG2AkAA1wQAIckCAQDWBAAhywICAOAEACHMAkAA1wQAIQcDAADiBAAgKoAAAAABsAIBANYEACG2AkAA1wQAIckCAQDWBAAhywICAOAEACHMAkAA1wQAIQcDAADkBAAgKoAAAAABsAIBAAAAAbYCQAAAAAHJAgEAAAABywICAAAAAcwCQAAAAAEMAwAA8AQAILACAQAAAAG2AkAAAAAByQIBAAAAAc0CAgAAAAHPAgAAAM8CAtACAQAAAAHRAgEAAAAB0gIBAAAAAdMCAQAAAAHUAoAAAAAB1QIBAAAAAQIAAAA8ACAxAAD8BQAgAwAAADwAIDEAAPwFACAyAAD7BQAgASoAALYIADARAwAAqQQAIAcAAKgEACCtAgAApQQAMK4CAAA6ABCvAgAApQQAMLACAQAAAAG2AkAA_wMAIckCAQD-AwAhygIBAPwDACHNAgIA_QMAIc8CAACmBM8CItACAQD-AwAh0QIBAP4DACHSAgEA_gMAIdMCAQD-AwAh1AIAAKcEACDVAgEA_gMAIQIAAAA8ACAqAAD7BQAgAgAAAPkFACAqAAD6BQAgD60CAAD4BQAwrgIAAPkFABCvAgAA-AUAMLACAQD8AwAhtgJAAP8DACHJAgEA_gMAIcoCAQD8AwAhzQICAP0DACHPAgAApgTPAiLQAgEA_gMAIdECAQD-AwAh0gIBAP4DACHTAgEA_gMAIdQCAACnBAAg1QIBAP4DACEPrQIAAPgFADCuAgAA-QUAEK8CAAD4BQAwsAIBAPwDACG2AkAA_wMAIckCAQD-AwAhygIBAPwDACHNAgIA_QMAIc8CAACmBM8CItACAQD-AwAh0QIBAP4DACHSAgEA_gMAIdMCAQD-AwAh1AIAAKcEACDVAgEA_gMAIQuwAgEA1gQAIbYCQADXBAAhyQIBAOEEACHNAgIA6wQAIc8CAADsBM8CItACAQDhBAAh0QIBAOEEACHSAgEA4QQAIdMCAQDhBAAh1AKAAAAAAdUCAQDhBAAhDAMAAO4EACCwAgEA1gQAIbYCQADXBAAhyQIBAOEEACHNAgIA6wQAIc8CAADsBM8CItACAQDhBAAh0QIBAOEEACHSAgEA4QQAIdMCAQDhBAAh1AKAAAAAAdUCAQDhBAAhDAMAAPAEACCwAgEAAAABtgJAAAAAAckCAQAAAAHNAgIAAAABzwIAAADPAgLQAgEAAAAB0QIBAAAAAdICAQAAAAHTAgEAAAAB1AKAAAAAAdUCAQAAAAEDAwAA9wQAILYCQAAAAAHJAgEAAAABAgAAADgAIDEAAIgGACADAAAAOAAgMQAAiAYAIDIAAIcGACABKgAAtQgAMAkDAACgBAAgBwAAqAQAIK0CAACrBAAwrgIAADYAEK8CAACrBAAwtgJAAP8DACHJAgEA_AMAIcoCAQD8AwAhoAMAAKoEACACAAAAOAAgKgAAhwYAIAIAAACFBgAgKgAAhgYAIAatAgAAhAYAMK4CAACFBgAQrwIAAIQGADC2AkAA_wMAIckCAQD8AwAhygIBAPwDACEGrQIAAIQGADCuAgAAhQYAEK8CAACEBgAwtgJAAP8DACHJAgEA_AMAIcoCAQD8AwAhArYCQADXBAAhyQIBANYEACEDAwAA9QQAILYCQADXBAAhyQIBANYEACEDAwAA9wQAILYCQAAAAAHJAgEAAAABBg8AAKcFACCwAgEAAAABtgJAAAAAAc8CAAAA5AIC4QICAAAAAeICAQAAAAECAAAAJAAgMQAAkQYAIAMAAAAkACAxAACRBgAgMgAAkAYAIAEqAAC0CAAwAgAAACQAICoAAJAGACACAAAAwQUAICoAAI8GACAFsAIBANYEACG2AkAA1wQAIc8CAACjBeQCIuECAgDgBAAh4gIBANYEACEGDwAApQUAILACAQDWBAAhtgJAANcEACHPAgAAowXkAiLhAgIA4AQAIeICAQDWBAAhBg8AAKcFACCwAgEAAAABtgJAAAAAAc8CAAAA5AIC4QICAAAAAeICAQAAAAEFFwAA_wQAILACAQAAAAG2AkAAAAAB1gIBAAAAAdgCAAAA2AICAgAAADIAIDEAAJ0GACADAAAAMgAgMQAAnQYAIDIAAJwGACABKgAAswgAMAsHAACoBAAgFwAAqQQAIK0CAACtBAAwrgIAADAAEK8CAACtBAAwsAIBAAAAAbYCQAD_AwAhygIBAPwDACHWAgEA_gMAIdgCAACuBNgCIqEDAACsBAAgAgAAADIAICoAAJwGACACAAAAmgYAICoAAJsGACAIrQIAAJkGADCuAgAAmgYAEK8CAACZBgAwsAIBAPwDACG2AkAA_wMAIcoCAQD8AwAh1gIBAP4DACHYAgAArgTYAiIIrQIAAJkGADCuAgAAmgYAEK8CAACZBgAwsAIBAPwDACG2AkAA_wMAIcoCAQD8AwAh1gIBAP4DACHYAgAArgTYAiIEsAIBANYEACG2AkAA1wQAIdYCAQDhBAAh2AIAAPsE2AIiBRcAAP0EACCwAgEA1gQAIbYCQADXBAAh1gIBAOEEACHYAgAA-wTYAiIFFwAA_wQAILACAQAAAAG2AkAAAAAB1gIBAAAAAdgCAAAA2AICBAMAAIcFACC2AkAAAAAByQIBAAAAAdoCAAAA2gICAgAAABMAIDEAAKkGACADAAAAEwAgMQAAqQYAIDIAAKgGACABKgAAsggAMAoDAACgBAAgBwAAqAQAIK0CAADEBAAwrgIAABEAEK8CAADEBAAwtgJAAP8DACHJAgEA_AMAIcoCAQD8AwAh2gIAAMUE2gIioAMAAMMEACACAAAAEwAgKgAAqAYAIAIAAACmBgAgKgAApwYAIAetAgAApQYAMK4CAACmBgAQrwIAAKUGADC2AkAA_wMAIckCAQD8AwAhygIBAPwDACHaAgAAxQTaAiIHrQIAAKUGADCuAgAApgYAEK8CAAClBgAwtgJAAP8DACHJAgEA_AMAIcoCAQD8AwAh2gIAAMUE2gIiA7YCQADXBAAhyQIBANYEACHaAgAAgwXaAiIEAwAAhQUAILYCQADXBAAhyQIBANYEACHaAgAAgwXaAiIEAwAAhwUAILYCQAAAAAHJAgEAAAAB2gIAAADaAgIOCAAAtgYAIAkAALcGACAOAAC4BgAgEAAAuQYAILYCQAAAAAHNAgIAAAAB5QIBAAAAAeYCAQAAAAHnAgEAAAAB6AIBAAAAAekCAQAAAAHqAgEAAAAB6wIBAAAAAewCQAAAAAECAAAALQAgMQAAtQYAIAMAAAAtACAxAAC1BgAgMgAAtAYAIAEqAACxCAAwFAcAAKgEACAIAACkBAAgCQAAsQQAIA4AAIAEACAQAACyBAAgrQIAALAEADCuAgAAFQAQrwIAALAEADC2AkAA_wMAIcoCAQD8AwAhzQICAJ0EACHlAgEA_AMAIeYCAQD-AwAh5wIBAP4DACHoAgEA_gMAIekCAQD8AwAh6gIBAP4DACHrAgEA_gMAIewCQACfBAAhogMAAK8EACACAAAALQAgKgAAtAYAIAIAAACyBgAgKgAAswYAIA6tAgAAsQYAMK4CAACyBgAQrwIAALEGADC2AkAA_wMAIcoCAQD8AwAhzQICAJ0EACHlAgEA_AMAIeYCAQD-AwAh5wIBAP4DACHoAgEA_gMAIekCAQD8AwAh6gIBAP4DACHrAgEA_gMAIewCQACfBAAhDq0CAACxBgAwrgIAALIGABCvAgAAsQYAMLYCQAD_AwAhygIBAPwDACHNAgIAnQQAIeUCAQD8AwAh5gIBAP4DACHnAgEA_gMAIegCAQD-AwAh6QIBAPwDACHqAgEA_gMAIesCAQD-AwAh7AJAAJ8EACEKtgJAANcEACHNAgIA4AQAIeUCAQDWBAAh5gIBAOEEACHnAgEA4QQAIegCAQDhBAAh6QIBANYEACHqAgEA4QQAIesCAQDhBAAh7AJAANgEACEOCAAAtQUAIAkAALYFACAOAAC3BQAgEAAAuAUAILYCQADXBAAhzQICAOAEACHlAgEA1gQAIeYCAQDhBAAh5wIBAOEEACHoAgEA4QQAIekCAQDWBAAh6gIBAOEEACHrAgEA4QQAIewCQADYBAAhDggAALYGACAJAAC3BgAgDgAAuAYAIBAAALkGACC2AkAAAAABzQICAAAAAeUCAQAAAAHmAgEAAAAB5wIBAAAAAegCAQAAAAHpAgEAAAAB6gIBAAAAAesCAQAAAAHsAkAAAAABAzEAALoGACCnAwAAuwYAIK0DAAAaACAEMQAAzgUAMKcDAADPBQAwqQMAANEFACCtAwAA0gUAMAQxAADFBQAwpwMAAMYFADCpAwAAyAUAIK0DAACSBQAwBDEAALkFADCnAwAAugUAMKkDAAC8BQAgrQMAAL0FADAXEgAAvAYAIBMAAL0GACAUAAC-BgAgFQAAvwYAIBYAAMAGACAYAADBBgAgGQAAwgYAIBoAAMMGACAbAADEBgAgHAAAxQYAILYCQAAAAAHMAkAAAAAB2wICAAAAAe4CAQAAAAHvAgIAAAAB8QIAAADxAgLyAiAAAAAB8wIgAAAAAfQCAgAAAAH1AgIAAAAB9gICAAAAAfcCAgAAAAH4AkAAAAABAgAAABoAIDEAALoGACADMQAArwgAIKcDAACwCAAgrQMAABoAIAQxAADGBgAwpwMAAMcGADCpAwAAyAYAIK0DAADSBQAwAzEAAKcIACCnAwAAqAgAIK0DAAAtACAEMQAAqgYAMKcDAACrBgAwqQMAAK0GACCtAwAArgYAMAQxAACeBgAwpwMAAJ8GADCpAwAAoQYAIK0DAACiBgAwBDEAAJIGADCnAwAAkwYAMKkDAACVBgAgrQMAAJYGADAEMQAAiQYAMKcDAACKBgAwqQMAAIwGACCtAwAAvQUAMAQxAAD9BQAwpwMAAP4FADCpAwAAgAYAIK0DAACBBgAwBDEAAPEFADCnAwAA8gUAMKkDAAD0BQAgrQMAAPUFADAEMQAA5QUAMKcDAADmBQAwqQMAAOgFACCtAwAA6QUAMBgRAADKBgAgEwAAvQYAIBQAAL4GACAVAAC_BgAgFgAAwAYAIBgAAMEGACAZAADCBgAgGgAAwwYAIBsAAMQGACAcAADFBgAgsAIBAAAAAbYCQAAAAAHMAkAAAAAB2wICAAAAAe0CAgAAAAHvAgIAAAAB8QIAAADxAgLyAiAAAAAB8wIgAAAAAfQCAgAAAAH1AgIAAAAB9gICAAAAAfcCAgAAAAH4AkAAAAABAgAAABoAIDEAAMkGACABKgAArggAMBgRAADKBgAgEwAAvQYAIBQAAL4GACAVAAC_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_BwAwMgAAwwcAMKcDAADABwAwqAMAAMEHADCpAwAAwgcAIKoDAACiBgAwqwMAAKIGADCsAwAAogYAMK0DAACiBgAwrgMAAMQHADCvAwAApQYAMAsxAAC2BwAwMgAAugcAMKcDAAC3BwAwqAMAALgHADCpAwAAuQcAIKoDAACWBgAwqwMAAJYGADCsAwAAlgYAMK0DAACWBgAwrgMAALsHADCvAwAAmQYAMAsxAACqBwAwMgAArwcAMKcDAACrBwAwqAMAAKwHADCpAwAArQcAIKoDAACuBwAwqwMAAK4HADCsAwAArgcAMK0DAACuBwAwrgMAALAHADCvAwAAsQcAMAsxAAChBwAwMgAApQcAMKcDAACiBwAwqAMAAKMHADCpAwAApAcAIKoDAACBBgAwqwMAAIEGADCsAwAAgQYAMK0DAACBBgAwrgMAAKYHADCvAwAAhAYAMAsxAACYBwAwMgAAnAcAMKcDAACZBwAwqAMAAJoHADCpAwAAmwcAIKoDAAD1BQAwqwMAAPUFADCsAwAA9QUAMK0DAAD1BQAwrgMAAJ0HADCvAwAA-AUAMAsxAACPBwAwMgAAkwcAMKcDAACQBwAwqAMAAJEHADCpAwAAkgcAIKoDAADpBQAwqwMAAOkFADCsAwAA6QUAMK0DAADpBQAwrgMAAJQHADCvAwAA7AUAMAsxAACDBwAwMgAAiAcAMKcDAACEBwAwqAMAAIUHADCpAwAAhgcAIKoDAACHBwAwqwMAAIcHADCsAwAAhwcAMK0DAACHBwAwrgMAAIkHADCvAwAAigcAMAqwAgEAAAABtgJAAAAAAdwCAQAAAAH5AgEAAAAB-gIBAAAAAfsCAgAAAAH8AgEAAAAB_QIgAAAAAf4CAQAAAAH_AgEAAAABAgAAAFYAIDEAAI4HACADAAAAVgAgMQAAjgcAIDIAAI0HACABKgAAkAgAMA8DAACgBAAgrQIAAJwEADCuAgAAVAAQrwIAAJwEADCwAgEAAAABtgJAAJ8EACHJAgEA_AMAIdwCAQD-AwAh-QIBAPwDACH6AgEA_AMAIfsCAgCdBAAh_AIBAPwDACH9AiAAngQAIf4CAQD-AwAh_wIBAP4DACECAAAAVgAgKgAAjQcAIAIAAACLBwAgKgAAjAcAIA6tAgAAigcAMK4CAACLBwAQrwIAAIoHADCwAgEA_AMAIbYCQACfBAAhyQIBAPwDACHcAgEA_gMAIfkCAQD8AwAh-gIBAPwDACH7AgIAnQQAIfwCAQD8AwAh_QIgAJ4EACH-AgEA_gMAIf8CAQD-AwAhDq0CAACKBwAwrgIAAIsHABCvAgAAigcAMLACAQD8AwAhtgJAAJ8EACHJAgEA_AMAIdwCAQD-AwAh-QIBAPwDACH6AgEA_AMAIfsCAgCdBAAh_AIBAPwDACH9AiAAngQAIf4CAQD-AwAh_wIBAP4DACEKsAIBANYEACG2AkAA2AQAIdwCAQDhBAAh-QIBANYEACH6AgEA1gQAIfsCAgDgBAAh_AIBANYEACH9AiAA2QUAIf4CAQDhBAAh_wIBAOEEACEKsAIBANYEACG2AkAA2AQAIdwCAQDhBAAh-QIBANYEACH6AgEA1gQAIfsCAgDgBAAh_AIBANYEACH9AiAA2QUAIf4CAQDhBAAh_wIBAOEEACEKsAIBAAAAAbYCQAAAAAHcAgEAAAAB-QIBAAAAAfoCAQAAAAH7AgIAAAAB_AIBAAAAAf0CIAAAAAH-AgEAAAAB_wIBAAAAAQcHAADlBAAgKoAAAAABsAIBAAAAAbYCQAAAAAHKAgEAAAABywICAAAAAcwCQAAAAAECAAAAQQAgMQAAlwcAIAMAAABBACAxAACXBwAgMgAAlgcAIAEqAACPCAAwAgAAAEEAICoAAJYHACACAAAA7QUAICoAAJUHACAGKoAAAAABsAIBANYEACG2AkAA1wQAIcoCAQDhBAAhywICAOAEACHMAkAA1wQAIQcHAADjBAAgKoAAAAABsAIBANYEACG2AkAA1wQAIcoCAQDhBAAhywICAOAEACHMAkAA1wQAIQcHAADlBAAgKoAAAAABsAIBAAAAAbYCQAAAAAHKAgEAAAABywICAAAAAcwCQAAAAAEMBwAA7wQAILACAQAAAAG2AkAAAAABygIBAAAAAc0CAgAAAAHPAgAAAM8CAtACAQAAAAHRAgEAAAAB0gIBAAAAAdMCAQAAAAHUAoAAAAAB1QIBAAAAAQIAAAA8ACAxAACgBwAgAwAAADwAIDEAAKAHACAyAACfBwAgASoAAI4IADACAAAAPAAgKgAAnwcAIAIAAAD5BQAgKgAAngcAIAuwAgEA1gQAIbYCQADXBAAhygIBANYEACHNAgIA6wQAIc8CAADsBM8CItACAQDhBAAh0QIBAOEEACHSAgEA4QQAIdMCAQDhBAAh1AKAAAAAAdUCAQDhBAAhDAcAAO0EACCwAgEA1gQAIbYCQADXBAAhygIBANYEACHNAgIA6wQAIc8CAADsBM8CItACAQDhBAAh0QIBAOEEACHSAgEA4QQAIdMCAQDhBAAh1AKAAAAAAdUCAQDhBAAhDAcAAO8EACCwAgEAAAABtgJAAAAAAcoCAQAAAAHNAgIAAAABzwIAAADPAgLQAgEAAAAB0QIBAAAAAdICAQAAAAHTAgEAAAAB1AKAAAAAAdUCAQAAAAEDBwAA9gQAILYCQAAAAAHKAgEAAAABAgAAADgAIDEAAKkHACADAAAAOAAgMQAAqQcAIDIAAKgHACABKgAAjQgAMAIAAAA4ACAqAACoBwAgAgAAAIUGACAqAACnBwAgArYCQADXBAAhygIBANYEACEDBwAA9AQAILYCQADXBAAhygIBANYEACEDBwAA9gQAILYCQAAAAAHKAgEAAAABB7ACAQAAAAGxAgEAAAABswIBAAAAAbQCAQAAAAG1AoAAAAABtgJAAAAAAbcCQAAAAAECAAAATwAgMQAAtQcAIAMAAABPACAxAAC1BwAgMgAAtAcAIAEqAACMCAAwDB8AAKAEACCtAgAAoQQAMK4CAABNABCvAgAAoQQAMLACAQAAAAGxAgEA_AMAIbICAQD8AwAhswIBAPwDACG0AgEA_AMAIbUCAACiBAAgtgJAAP8DACG3AkAAnwQAIQIAAABPACAqAAC0BwAgAgAAALIHACAqAACzBwAgC60CAACxBwAwrgIAALIHABCvAgAAsQcAMLACAQD8AwAhsQIBAPwDACGyAgEA_AMAIbMCAQD8AwAhtAIBAPwDACG1AgAAogQAILYCQAD_AwAhtwJAAJ8EACELrQIAALEHADCuAgAAsgcAEK8CAACxBwAwsAIBAPwDACGxAgEA_AMAIbICAQD8AwAhswIBAPwDACG0AgEA_AMAIbUCAACiBAAgtgJAAP8DACG3AkAAnwQAIQewAgEA1gQAIbECAQDWBAAhswIBANYEACG0AgEA1gQAIbUCgAAAAAG2AkAA1wQAIbcCQADYBAAhB7ACAQDWBAAhsQIBANYEACGzAgEA1gQAIbQCAQDWBAAhtQKAAAAAAbYCQADXBAAhtwJAANgEACEHsAIBAAAAAbECAQAAAAGzAgEAAAABtAIBAAAAAbUCgAAAAAG2AkAAAAABtwJAAAAAAQUHAAD-BAAgsAIBAAAAAbYCQAAAAAHKAgEAAAAB2AIAAADYAgICAAAAMgAgMQAAvgcAIAMAAAAyACAxAAC-BwAgMgAAvQcAIAEqAACLCAAwAgAAADIAICoAAL0HACACAAAAmgYAICoAALwHACAEsAIBANYEACG2AkAA1wQAIcoCAQDWBAAh2AIAAPsE2AIiBQcAAPwEACCwAgEA1gQAIbYCQADXBAAhygIBANYEACHYAgAA-wTYAiIFBwAA_gQAILACAQAAAAG2AkAAAAABygIBAAAAAdgCAAAA2AICBAcAAIYFACC2AkAAAAABygIBAAAAAdoCAAAA2gICAgAAABMAIDEAAMcHACADAAAAEwAgMQAAxwcAIDIAAMYHACABKgAAiggAMAIAAAATACAqAADGBwAgAgAAAKYGACAqAADFBwAgA7YCQADXBAAhygIBANYEACHaAgAAgwXaAiIEBwAAhAUAILYCQADXBAAhygIBANYEACHaAgAAgwXaAiIEBwAAhgUAILYCQAAAAAHKAgEAAAAB2gIAAADaAgIGsAIBAAAAAbYCQAAAAAHMAkAAAAABgAMBAAAAAYEDAQAAAAGCA0AAAAABAgAAAA0AIDEAANMHACADAAAADQAgMQAA0wcAIDIAANIHACABKgAAiQgAMAsDAACpBAAgrQIAAM8EADCuAgAACwAQrwIAAM8EADCwAgEAAAABtgJAAJ8EACHJAgEA_gMAIcwCQACfBAAhgAMBAPwDACGBAwEA_AMAIYIDQAD_AwAhAgAAAA0AICoAANIHACACAAAA0AcAICoAANEHACAKrQIAAM8HADCuAgAA0AcAEK8CAADPBwAwsAIBAPwDACG2AkAAnwQAIckCAQD-AwAhzAJAAJ8EACGAAwEA_AMAIYEDAQD8AwAhggNAAP8DACEKrQIAAM8HADCuAgAA0AcAEK8CAADPBwAwsAIBAPwDACG2AkAAnwQAIckCAQD-AwAhzAJAAJ8EACGAAwEA_AMAIYEDAQD8AwAhggNAAP8DACEGsAIBANYEACG2AkAA2AQAIcwCQADYBAAhgAMBANYEACGBAwEA1gQAIYIDQADXBAAhBrACAQDWBAAhtgJAANgEACHMAkAA2AQAIYADAQDWBAAhgQMBANYEACGCA0AA1wQAIQawAgEAAAABtgJAAAAAAcwCQAAAAAGAAwEAAAABgQMBAAAAAYIDQAAAAAEIsAIBAAAAAbYCQAAAAAHMAkAAAAAB0gIBAAAAAYIDQAAAAAGDAwEAAAABhAMBAAAAAYUDAQAAAAECAAAACQAgMQAA3wcAIAMAAAAJACAxAADfBwAgMgAA3gcAIAEqAACICAAwDQMAAKAEACCtAgAA0AQAMK4CAAAHABCvAgAA0AQAMLACAQAAAAG2AkAA_wMAIckCAQD8AwAhzAJAAP8DACHSAgEA_gMAIYIDQAD_AwAhgwMBAAAAAYQDAQD-AwAhhQMBAP4DACECAAAACQAgKgAA3gcAIAIAAADcBwAgKgAA3QcAIAytAgAA2wcAMK4CAADcBwAQrwIAANsHADCwAgEA_AMAIbYCQAD_AwAhyQIBAPwDACHMAkAA_wMAIdICAQD-AwAhggNAAP8DACGDAwEA_AMAIYQDAQD-AwAhhQMBAP4DACEMrQIAANsHADCuAgAA3AcAEK8CAADbBwAwsAIBAPwDACG2AkAA_wMAIckCAQD8AwAhzAJAAP8DACHSAgEA_gMAIYIDQAD_AwAhgwMBAPwDACGEAwEA_gMAIYUDAQD-AwAhCLACAQDWBAAhtgJAANcEACHMAkAA1wQAIdICAQDhBAAhggNAANcEACGDAwEA1gQAIYQDAQDhBAAhhQMBAOEEACEIsAIBANYEACG2AkAA1wQAIcwCQADXBAAh0gIBAOEEACGCA0AA1wQAIYMDAQDWBAAhhAMBAOEEACGFAwEA4QQAIQiwAgEAAAABtgJAAAAAAcwCQAAAAAHSAgEAAAABggNAAAAAAYMDAQAAAAGEAwEAAAABhQMBAAAAAQywAgEAAAABtgJAAAAAAcwCQAAAAAGGAwEAAAABhwMBAAAAAYgDAQAAAAGJAwEAAAABigNAAAAAAYsDQAAAAAGMAwEAAAABjQMBAAAAAY4DAQAAAAECAAAABQAgMQAA6wcAIAMAAAAFACAxAADrBwAgMgAA6gcAIAEqAACHCAAwEQMAAKAEACCtAgAA0QQAMK4CAAADABCvAgAA0QQAMLACAQAAAAG2AkAA_wMAIckCAQD8AwAhzAJAAP8DACGGAwEA_AMAIYcDAQD8AwAhiAMBAP4DACGJAwEA_gMAIYoDQACfBAAhiwNAAJ8EACGMAwEA_gMAIY0DAQD-AwAhjgMBAP4DACECAAAABQAgKgAA6gcAIAIAAADoBwAgKgAA6QcAIBCtAgAA5wcAMK4CAADoBwAQrwIAAOcHADCwAgEA_AMAIbYCQAD_AwAhyQIBAPwDACHMAkAA_wMAIYYDAQD8AwAhhwMBAPwDACGIAwEA_gMAIYkDAQD-AwAhigNAAJ8EACGLA0AAnwQAIYwDAQD-AwAhjQMBAP4DACGOAwEA_gMAIRCtAgAA5wcAMK4CAADoBwAQrwIAAOcHADCwAgEA_AMAIbYCQAD_AwAhyQIBAPwDACHMAkAA_wMAIYYDAQD8AwAhhwMBAPwDACGIAwEA_gMAIYkDAQD-AwAhigNAAJ8EACGLA0AAnwQAIYwDAQD-AwAhjQMBAP4DACGOAwEA_gMAIQywAgEA1gQAIbYCQADXBAAhzAJAANcEACGGAwEA1gQAIYcDAQDWBAAhiAMBAOEEACGJAwEA4QQAIYoDQADYBAAhiwNAANgEACGMAwEA4QQAIY0DAQDhBAAhjgMBAOEEACEMsAIBANYEACG2AkAA1wQAIcwCQADXBAAhhgMBANYEACGHAwEA1gQAIYgDAQDhBAAhiQMBAOEEACGKA0AA2AQAIYsDQADYBAAhjAMBAOEEACGNAwEA4QQAIY4DAQDhBAAhDLACAQAAAAG2AkAAAAABzAJAAAAAAYYDAQAAAAGHAwEAAAABiAMBAAAAAYkDAQAAAAGKA0AAAAABiwNAAAAAAYwDAQAAAAGNAwEAAAABjgMBAAAAAQQxAADgBwAwpwMAAOEHADCpAwAA4wcAIK0DAADkBwAwBDEAANQHADCnAwAA1QcAMKkDAADXBwAgrQMAANgHADAEMQAAyAcAMKcDAADJBwAwqQMAAMsHACCtAwAAzAcAMAQxAAC_BwAwpwMAAMAHADCpAwAAwgcAIK0DAACiBgAwBDEAALYHADCnAwAAtwcAMKkDAAC5BwAgrQMAAJYGADAEMQAAqgcAMKcDAACrBwAwqQMAAK0HACCtAwAArgcAMAQxAAChBwAwpwMAAKIHADCpAwAApAcAIK0DAACBBgAwBDEAAJgHADCnAwAAmQcAMKkDAACbBwAgrQMAAPUFADAEMQAAjwcAMKcDAACQBwAwqQMAAJIHACCtAwAA6QUAMAQxAACDBwAwpwMAAIQHADCpAwAAhgcAIK0DAACHBwAwAAAAAAAAAAAAABkEAAD2BwAgBQAA9wcAIAYAAPgHACAdAAD5BwAgHgAA-gcAICAAAPsHACAhAAD8BwAgIgAA_QcAICMAAP4HACAkAAD_BwAg2gIAANIEACDbAgAA0gQAINwCAADSBAAg-AIAANIEACCPAwAA0gQAIJEDAADSBAAglwMAANIEACCYAwAA0gQAIJkDAADSBAAgmgMAANIEACCbAwAA0gQAIJwDAADSBAAgnQMAANIEACCeAwAA0gQAIJ8DAADSBAAgEBEAAIQIACASAACBCAAgEwAAgggAIBQAAIQIACAVAACGCAAgFgAA-QcAIBgAAPoHACAZAACDCAAgGgAA_AcAIBsAAP0HACAcAAD-BwAg2wIAANIEACDtAgAA0gQAIO4CAADSBAAg7wIAANIEACD4AgAA0gQAIAAACwcAAIEIACAIAACBCAAgCQAAgggAIA4AAJ0FACAQAACDCAAg5gIAANIEACDnAgAA0gQAIOgCAADSBAAg6gIAANIEACDrAgAA0gQAIOwCAADSBAAgAwsAAJ0FACDbAgAA0gQAIN0CAADSBAAgAAywAgEAAAABtgJAAAAAAcwCQAAAAAGGAwEAAAABhwMBAAAAAYgDAQAAAAGJAwEAAAABigNAAAAAAYsDQAAAAAGMAwEAAAABjQMBAAAAAY4DAQAAAAEIsAIBAAAAAbYCQAAAAAHMAkAAAAAB0gIBAAAAAYIDQAAAAAGDAwEAAAABhAMBAAAAAYUDAQAAAAEGsAIBAAAAAbYCQAAAAAHMAkAAAAABgAMBAAAAAYEDAQAAAAGCA0AAAAABA7YCQAAAAAHKAgEAAAAB2gIAAADaAgIEsAIBAAAAAbYCQAAAAAHKAgEAAAAB2AIAAADYAgIHsAIBAAAAAbECAQAAAAGzAgEAAAABtAIBAAAAAbUCgAAAAAG2AkAAAAABtwJAAAAAAQK2AkAAAAABygIBAAAAAQuwAgEAAAABtgJAAAAAAcoCAQAAAAHNAgIAAAABzwIAAADPAgLQAgEAAAAB0QIBAAAAAdICAQAAAAHTAgEAAAAB1AKAAAAAAdUCAQAAAAEGKoAAAAABsAIBAAAAAbYCQAAAAAHKAgEAAAABywICAAAAAcwCQAAAAAEKsAIBAAAAAbYCQAAAAAHcAgEAAAAB-QIBAAAAAfoCAQAAAAH7AgIAAAAB_AIBAAAAAf0CIAAAAAH-AgEAAAAB_wIBAAAAAR8FAADtBwAgBgAA7gcAIB0AAO8HACAeAADwBwAgIAAA8QcAICEAAPIHACAiAADzBwAgIwAA9AcAICQAAPUHACCwAgEAAAABtgJAAAAAAcwCQAAAAAHaAgEAAAAB2wICAAAAAdwCAQAAAAH4AkAAAAABjwMBAAAAAZADIAAAAAGRAwEAAAABkwMAAACTAwKVAwAAAJUDApYDIAAAAAGXAwEAAAABmAMBAAAAAZkDgAAAAAGaA0AAAAABmwMBAAAAAZwDIAAAAAGdAwEAAAABngNAAAAAAZ8DQAAAAAECAAAAAQAgMQAAkQgAIAMAAAAPACAxAACRCAAgMgAAlQgAICEAAAAPACAFAAD6BgAgBgAA-wYAIB0AAPwGACAeAAD9BgAgIAAA_gYAICEAAP8GACAiAACABwAgIwAAgQcAICQAAIIHACAqAACVCAAgsAIBANYEACG2AkAA1wQAIcwCQADXBAAh2gIBAOEEACHbAgIA6wQAIdwCAQDhBAAh-AJAANgEACGPAwEA4QQAIZADIADZBQAhkQMBAOEEACGTAwAA9gaTAyKVAwAA9waVAyKWAyAA2QUAIZcDAQDhBAAhmAMBAOEEACGZA4AAAAABmgNAANgEACGbAwEA4QQAIZwDIAD4BgAhnQMBAOEEACGeA0AA2AQAIZ8DQADYBAAhHwUAAPoGACAGAAD7BgAgHQAA_AYAIB4AAP0GACAgAAD-BgAgIQAA_wYAICIAAIAHACAjAACBBwAgJAAAggcAILACAQDWBAAhtgJAANcEACHMAkAA1wQAIdoCAQDhBAAh2wICAOsEACHcAgEA4QQAIfgCQADYBAAhjwMBAOEEACGQAyAA2QUAIZEDAQDhBAAhkwMAAPYGkwMilQMAAPcGlQMilgMgANkFACGXAwEA4QQAIZgDAQDhBAAhmQOAAAAAAZoDQADYBAAhmwMBAOEEACGcAyAA-AYAIZ0DAQDhBAAhngNAANgEACGfA0AA2AQAIR8EAADsBwAgBgAA7gcAIB0AAO8HACAeAADwBwAgIAAA8QcAICEAAPIHACAiAADzBwAgIwAA9AcAICQAAPUHACCwAgEAAAABtgJAAAAAAcwCQAAAAAHaAgEAAAAB2wICAAAAAdwCAQAAAAH4AkAAAAABjwMBAAAAAZADIAAAAAGRAwEAAAABkwMAAACTAwKVAwAAAJUDApYDIAAAAAGXAwEAAAABmAMBAAAAAZkDgAAAAAGaA0AAAAABmwMBAAAAAZwDIAAAAAGdAwEAAAABngNAAAAAAZ8DQAAAAAECAAAAAQAgMQAAlggAIAMAAAAPACAxAACWCAAgMgAAmggAICEAAAAPACAEAAD5BgAgBgAA-wYAIB0AAPwGACAeAAD9BgAgIAAA_gYAICEAAP8GACAiAACABwAgIwAAgQcAICQAAIIHACAqAACaCAAgsAIBANYEACG2AkAA1wQAIcwCQADXBAAh2gIBAOEEACHbAgIA6wQAIdwCAQDhBAAh-AJAANgEACGPAwEA4QQAIZADIADZBQAhkQMBAOEEACGTAwAA9gaTAyKVAwAA9waVAyKWAyAA2QUAIZcDAQDhBAAhmAMBAOEEACGZA4AAAAABmgNAANgEACGbAwEA4QQAIZwDIAD4BgAhnQMBAOEEACGeA0AA2AQAIZ8DQADYBAAhHwQAAPkGACAGAAD7BgAgHQAA_AYAIB4AAP0GACAgAAD-BgAgIQAA_wYAICIAAIAHACAjAACBBwAgJAAAggcAILACAQDWBAAhtgJAANcEACHMAkAA1wQAIdoCAQDhBAAh2wICAOsEACHcAgEA4QQAIfgCQADYBAAhjwMBAOEEACGQAyAA2QUAIZEDAQDhBAAhkwMAAPYGkwMilQMAAPcGlQMilgMgANkFACGXAwEA4QQAIZgDAQDhBAAhmQOAAAAAAZoDQADYBAAhmwMBAOEEACGcAyAA-AYAIZ0DAQDhBAAhngNAANgEACGfA0AA2AQAIR8EAADsBwAgBQAA7QcAIB0AAO8HACAeAADwBwAgIAAA8QcAICEAAPIHACAiAADzBwAgIwAA9AcAICQAAPUHACCwAgEAAAABtgJAAAAAAcwCQAAAAAHaAgEAAAAB2wICAAAAAdwCAQAAAAH4AkAAAAABjwMBAAAAAZADIAAAAAGRAwEAAAABkwMAAACTAwKVAwAAAJUDApYDIAAAAAGXAwEAAAABmAMBAAAAAZkDgAAAAAGaA0AAAAABmwMBAAAAAZwDIAAAAAGdAwEAAAABngNAAAAAAZ8DQAAAAAECAAAAAQAgMQAAmwgAIAMAAAAPACAxAACbCAAgMgAAnwgAICEAAAAPACAEAAD5BgAgBQAA-gYAIB0AAPwGACAeAAD9BgAgIAAA_gYAICEAAP8GACAiAACABwAgIwAAgQcAICQAAIIHACAqAACfCAAgsAIBANYEACG2AkAA1wQAIcwCQADXBAAh2gIBAOEEACHbAgIA6wQAIdwCAQDhBAAh-AJAANgEACGPAwEA4QQAIZADIADZBQAhkQMBAOEEACGTAwAA9gaTAyKVAwAA9waVAyKWAyAA2QUAIZcDAQDhBAAhmAMBAOEEACGZA4AAAAABmgNAANgEACGbAwEA4QQAIZwDIAD4BgAhnQMBAOEEACGeA0AA2AQAIZ8DQADYBAAhHwQAAPkGACAFAAD6BgAgHQAA_AYAIB4AAP0GACAgAAD-BgAgIQAA_wYAICIAAIAHACAjAACBBwAgJAAAggcAILACAQDWBAAhtgJAANcEACHMAkAA1wQAIdoCAQDhBAAh2wICAOsEACHcAgEA4QQAIfgCQADYBAAhjwMBAOEEACGQAyAA2QUAIZEDAQDhBAAhkwMAAPYGkwMilQMAAPcGlQMilgMgANkFACGXAwEA4QQAIZgDAQDhBAAhmQOAAAAAAZoDQADYBAAhmwMBAOEEACGcAyAA-AYAIZ0DAQDhBAAhngNAANgEACGfA0AA2AQAIR8EAADsBwAgBQAA7QcAIAYAAO4HACAdAADvBwAgHgAA8AcAICAAAPEHACAhAADyBwAgIgAA8wcAICMAAPQHACCwAgEAAAABtgJAAAAAAcwCQAAAAAHaAgEAAAAB2wICAAAAAdwCAQAAAAH4AkAAAAABjwMBAAAAAZADIAAAAAGRAwEAAAABkwMAAACTAwKVAwAAAJUDApYDIAAAAAGXAwEAAAABmAMBAAAAAZkDgAAAAAGaA0AAAAABmwMBAAAAAZwDIAAAAAGdAwEAAAABngNAAAAAAZ8DQAAAAAECAAAAAQAgMQAAoAgAIAMAAAAPACAxAACgCAAgMgAApAgAICEAAAAPACAEAAD5BgAgBQAA-gYAIAYAAPsGACAdAAD8BgAgHgAA_QYAICAAAP4GACAhAAD_BgAgIgAAgAcAICMAAIEHACAqAACkCAAgsAIBANYEACG2AkAA1wQAIcwCQADXBAAh2gIBAOEEACHbAgIA6wQAIdwCAQDhBAAh-AJAANgEACGPAwEA4QQAIZADIADZBQAhkQMBAOEEACGTAwAA9gaTAyKVAwAA9waVAyKWAyAA2QUAIZcDAQDhBAAhmAMBAOEEACGZA4AAAAABmgNAANgEACGbAwEA4QQAIZwDIAD4BgAhnQMBAOEEACGeA0AA2AQAIZ8DQADYBAAhHwQAAPkGACAFAAD6BgAgBgAA-wYAIB0AAPwGACAeAAD9BgAgIAAA_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-gYAIAYAAPsGACAeAAD9BgAgIAAA_gYAICEAAP8GACAiAACABwAgIwAAgQcAICQAAIIHACAqAADfCAAgsAIBANYEACG2AkAA1wQAIcwCQADXBAAh2gIBAOEEACHbAgIA6wQAIdwCAQDhBAAh-AJAANgEACGPAwEA4QQAIZADIADZBQAhkQMBAOEEACGTAwAA9gaTAyKVAwAA9waVAyKWAyAA2QUAIZcDAQDhBAAhmAMBAOEEACGZA4AAAAABmgNAANgEACGbAwEA4QQAIZwDIAD4BgAhnQMBAOEEACGeA0AA2AQAIZ8DQADYBAAhHwQAAPkGACAFAAD6BgAgBgAA-wYAIB4AAP0GACAgAAD-BgAgIQAA_wYAICIAAIAHACAjAACBBwAgJAAAggcAILACAQDWBAAhtgJAANcEACHMAkAA1wQAIdoCAQDhBAAh2wICAOsEACHcAgEA4QQAIfgCQADYBAAhjwMBAOEEACGQAyAA2QUAIZEDAQDhBAAhkwMAAPYGkwMilQMAAPcGlQMilgMgANkFACGXAwEA4QQAIZgDAQDhBAAhmQOAAAAAAZoDQADYBAAhmwMBAOEEACGcAyAA-AYAIZ0DAQDhBAAhngNAANgEACGfA0AA2AQAIQMAAAAXACAxAADbCAAgMgAA4ggAIBsAAAAXACARAADbBQAgEgAA3AUAIBMAAN0FACAUAADQBgAgFQAA3gUAIBgAAOAFACAZAADhBQAgGgAA4gUAIBsAAOMFACAcAADkBQAgKgAA4ggAILACAQDWBAAhtgJAANcEACHMAkAA1wQAIdsCAgDrBAAh7QICAOsEACHuAgEA4QQAIe8CAgDrBAAh8QIAANgF8QIi8gIgANkFACHzAiAA2QUAIfQCAgDgBAAh9QICAOAEACH2AgIA4AQAIfcCAgDgBAAh-AJAANgEACEZEQAA2wUAIBIAANwFACATAADdBQAgFAAA0AYAIBUAAN4FACAYAADgBQAgGQAA4QUAIBoAAOIFACAbAADjBQAgHAAA5AUAILACAQDWBAAhtgJAANcEACHMAkAA1wQAIdsCAgDrBAAh7QICAOsEACHuAgEA4QQAIe8CAgDrBAAh8QIAANgF8QIi8gIgANkFACHzAiAA2QUAIfQCAgDgBAAh9QICAOAEACH2AgIA4AQAIfcCAgDgBAAh-AJAANgEACEfBAAA7AcAIAUAAO0HACAGAADuBwAgHQAA7wcAICAAAPEHACAhAADyBwAgIgAA8wcAICMAAPQHACAkAAD1BwAgsAIBAAAAAbYCQAAAAAHMAkAAAAAB2gIBAAAAAdsCAgAAAAHcAgEAAAAB-AJAAAAAAY8DAQAAAAGQAyAAAAABkQMBAAAAAZMDAAAAkwMClQMAAACVAwKWAyAAAAABlwMBAAAAAZgDAQAAAAGZA4AAAAABmgNAAAAAAZsDAQAAAAGcAyAAAAABnQMBAAAAAZ4DQAAAAAGfA0AAAAABAgAAAAEAIDEAAOMIACAZEQAAygYAIBIAALwGACATAAC9BgAgFAAAvgYAIBUAAL8GACAWAADABgAgGQAAwgYAIBoAAMMGACAbAADEBgAgHAAAxQYAILACAQAAAAG2AkAAAAABzAJAAAAAAdsCAgAAAAHtAgIAAAAB7gIBAAAAAe8CAgAAAAHxAgAAAPECAvICIAAAAAHzAiAAAAAB9AICAAAAAfUCAgAAAAH2AgIAAAAB9wICAAAAAfgCQAAAAAECAAAAGgAgMQAA5QgAIAMAAAAPACAxAADjCAAgMgAA6QgAICEAAAAPACAEAAD5BgAgBQAA-gYAIAYAAPsGACAdAAD8BgAgIAAA_gYAICEAAP8GACAiAACABwAgIwAAgQcAICQAAIIHACAqAADpCAAgsAIBANYEACG2AkAA1wQAIcwCQADXBAAh2gIBAOEEACHbAgIA6wQAIdwCAQDhBAAh-AJAANgEACGPAwEA4QQAIZADIADZBQAhkQMBAOEEACGTAwAA9gaTAyKVAwAA9waVAyKWAyAA2QUAIZcDAQDhBAAhmAMBAOEEACGZA4AAAAABmgNAANgEACGbAwEA4QQAIZwDIAD4BgAhnQMBAOEEACGeA0AA2AQAIZ8DQADYBAAhHwQAAPkGACAFAAD6BgAgBgAA-wYAIB0AAPwGACAgAAD-BgAgIQAA_wYAICIAAIAHACAjAACBBwAgJAAAggcAILACAQDWBAAhtgJAANcEACHMAkAA1wQAIdoCAQDhBAAh2wICAOsEACHcAgEA4QQAIfgCQADYBAAhjwMBAOEEACGQAyAA2QUAIZEDAQDhBAAhkwMAAPYGkwMilQMAAPcGlQMilgMgANkFACGXAwEA4QQAIZgDAQDhBAAhmQOAAAAAAZoDQADYBAAhmwMBAOEEACGcAyAA-AYAIZ0DAQDhBAAhngNAANgEACGfA0AA2AQAIQMAAAAXACAxAADlCAAgMgAA7AgAIBsAAAAXACARAADbBQAgEgAA3AUAIBMAAN0FACAUAADQBgAgFQAA3gUAIBYAAN8FACAZAADhBQAgGgAA4gUAIBsAAOMFACAcAADkBQAgKgAA7AgAILACAQDWBAAhtgJAANcEACHMAkAA1wQAIdsCAgDrBAAh7QICAOsEACHuAgEA4QQAIe8CAgDrBAAh8QIAANgF8QIi8gIgANkFACHzAiAA2QUAIfQCAgDgBAAh9QICAOAEACH2AgIA4AQAIfcCAgDgBAAh-AJAANgEACEZEQAA2wUAIBIAANwFACATAADdBQAgFAAA0AYAIBUAAN4FACAWAADfBQAgGQAA4QUAIBoAAOIFACAbAADjBQAgHAAA5AUAILACAQDWBAAhtgJAANcEACHMAkAA1wQAIdsCAgDrBAAh7QICAOsEACHuAgEA4QQAIe8CAgDrBAAh8QIAANgF8QIi8gIgANkFACHzAiAA2QUAIfQCAgDgBAAh9QICAOAEACH2AgIA4AQAIfcCAgDgBAAh-AJAANgEACEfBAAA7AcAIAUAAO0HACAGAADuBwAgHQAA7wcAIB4AAPAHACAgAADxBwAgIgAA8wcAICMAAPQHACAkAAD1BwAgsAIBAAAAAbYCQAAAAAHMAkAAAAAB2gIBAAAAAdsCAgAAAAHcAgEAAAAB-AJAAAAAAY8DAQAAAAGQAyAAAAABkQMBAAAAAZMDAAAAkwMClQMAAACVAwKWAyAAAAABlwMBAAAAAZgDAQAAAAGZA4AAAAABmgNAAAAAAZsDAQAAAAGcAyAAAAABnQMBAAAAAZ4DQAAAAAGfA0AAAAABAgAAAAEAIDEAAO0IACAZEQAAygYAIBIAALwGACATAAC9BgAgFAAAvgYAIBUAAL8GACAWAADABgAgGAAAwQYAIBkAAMIGACAbAADEBgAgHAAAxQYAILACAQAAAAG2AkAAAAABzAJAAAAAAdsCAgAAAAHtAgIAAAAB7gIBAAAAAe8CAgAAAAHxAgAAAPECAvICIAAAAAHzAiAAAAAB9AICAAAAAfUCAgAAAAH2AgIAAAAB9wICAAAAAfgCQAAAAAECAAAAGgAgMQAA7wgAIAMAAAAPACAxAADtCAAgMgAA8wgAICEAAAAPACAEAAD5BgAgBQAA-gYAIAYAAPsGACAdAAD8BgAgHgAA_QYAICAAAP4GACAiAACABwAgIwAAgQcAICQAAIIHACAqAADzCAAgsAIBANYEACG2AkAA1wQAIcwCQADXBAAh2gIBAOEEACHbAgIA6wQAIdwCAQDhBAAh-AJAANgEACGPAwEA4QQAIZADIADZBQAhkQMBAOEEACGTAwAA9gaTAyKVAwAA9waVAyKWAyAA2QUAIZcDAQDhBAAhmAMBAOEEACGZA4AAAAABmgNAANgEACGbAwEA4QQAIZwDIAD4BgAhnQMBAOEEACGeA0AA2AQAIZ8DQADYBAAhHwQAAPkGACAFAAD6BgAgBgAA-wYAIB0AAPwGACAeAAD9BgAgIAAA_gYAICIAAIAHACAjAACBBwAgJAAAggcAILACAQDWBAAhtgJAANcEACHMAkAA1wQAIdoCAQDhBAAh2wICAOsEACHcAgEA4QQAIfgCQADYBAAhjwMBAOEEACGQAyAA2QUAIZEDAQDhBAAhkwMAAPYGkwMilQMAAPcGlQMilgMgANkFACGXAwEA4QQAIZgDAQDhBAAhmQOAAAAAAZoDQADYBAAhmwMBAOEEACGcAyAA-AYAIZ0DAQDhBAAhngNAANgEACGfA0AA2AQAIQMAAAAXACAxAADvCAAgMgAA9ggAIBsAAAAXACARAADbBQAgEgAA3AUAIBMAAN0FACAUAADQBgAgFQAA3gUAIBYAAN8FACAYAADgBQAgGQAA4QUAIBsAAOMFACAcAADkBQAgKgAA9ggAILACAQDWBAAhtgJAANcEACHMAkAA1wQAIdsCAgDrBAAh7QICAOsEACHuAgEA4QQAIe8CAgDrBAAh8QIAANgF8QIi8gIgANkFACHzAiAA2QUAIfQCAgDgBAAh9QICAOAEACH2AgIA4AQAIfcCAgDgBAAh-AJAANgEACEZEQAA2wUAIBIAANwFACATAADdBQAgFAAA0AYAIBUAAN4FACAWAADfBQAgGAAA4AUAIBkAAOEFACAbAADjBQAgHAAA5AUAILACAQDWBAAhtgJAANcEACHMAkAA1wQAIdsCAgDrBAAh7QICAOsEACHuAgEA4QQAIe8CAgDrBAAh8QIAANgF8QIi8gIgANkFACHzAiAA2QUAIfQCAgDgBAAh9QICAOAEACH2AgIA4AQAIfcCAgDgBAAh-AJAANgEACEfBAAA7AcAIAUAAO0HACAGAADuBwAgHQAA7wcAIB4AAPAHACAgAADxBwAgIQAA8gcAICMAAPQHACAkAAD1BwAgsAIBAAAAAbYCQAAAAAHMAkAAAAAB2gIBAAAAAdsCAgAAAAHcAgEAAAAB-AJAAAAAAY8DAQAAAAGQAyAAAAABkQMBAAAAAZMDAAAAkwMClQMAAACVAwKWAyAAAAABlwMBAAAAAZgDAQAAAAGZA4AAAAABmgNAAAAAAZsDAQAAAAGcAyAAAAABnQMBAAAAAZ4DQAAAAAGfA0AAAAABAgAAAAEAIDEAAPcIACAZEQAAygYAIBIAALwGACATAAC9BgAgFAAAvgYAIBUAAL8GACAWAADABgAgGAAAwQYAIBkAAMIGACAaAADDBgAgHAAAxQYAILACAQAAAAG2AkAAAAABzAJAAAAAAdsCAgAAAAHtAgIAAAAB7gIBAAAAAe8CAgAAAAHxAgAAAPECAvICIAAAAAHzAiAAAAAB9AICAAAAAfUCAgAAAAH2AgIAAAAB9wICAAAAAfgCQAAAAAECAAAAGgAgMQAA-QgAIAMAAAAPACAxAAD3CAAgMgAA_QgAICEAAAAPACAEAAD5BgAgBQAA-gYAIAYAAPsGACAdAAD8BgAgHgAA_QYAICAAAP4GACAhAAD_BgAgIwAAgQcAICQAAIIHACAqAAD9CAAgsAIBANYEACG2AkAA1wQAIcwCQADXBAAh2gIBAOEEACHbAgIA6wQAIdwCAQDhBAAh-AJAANgEACGPAwEA4QQAIZADIADZBQAhkQMBAOEEACGTAwAA9gaTAyKVAwAA9waVAyKWAyAA2QUAIZcDAQDhBAAhmAMBAOEEACGZA4AAAAABmgNAANgEACGbAwEA4QQAIZwDIAD4BgAhnQMBAOEEACGeA0AA2AQAIZ8DQADYBAAhHwQAAPkGACAFAAD6BgAgBgAA-wYAIB0AAPwGACAeAAD9BgAgIAAA_gYAICEAAP8GACAjAACBBwAgJAAAggcAILACAQDWBAAhtgJAANcEACHMAkAA1wQAIdoCAQDhBAAh2wICAOsEACHcAgEA4QQAIfgCQADYBAAhjwMBAOEEACGQAyAA2QUAIZEDAQDhBAAhkwMAAPYGkwMilQMAAPcGlQMilgMgANkFACGXAwEA4QQAIZgDAQDhBAAhmQOAAAAAAZoDQADYBAAhmwMBAOEEACGcAyAA-AYAIZ0DAQDhBAAhngNAANgEACGfA0AA2AQAIQMAAAAXACAxAAD5CAAgMgAAgAkAIBsAAAAXACARAADbBQAgEgAA3AUAIBMAAN0FACAUAADQBgAgFQAA3gUAIBYAAN8FACAYAADgBQAgGQAA4QUAIBoAAOIFACAcAADkBQAgKgAAgAkAILACAQDWBAAhtgJAANcEACHMAkAA1wQAIdsCAgDrBAAh7QICAOsEACHuAgEA4QQAIe8CAgDrBAAh8QIAANgF8QIi8gIgANkFACHzAiAA2QUAIfQCAgDgBAAh9QICAOAEACH2AgIA4AQAIfcCAgDgBAAh-AJAANgEACEZEQAA2wUAIBIAANwFACATAADdBQAgFAAA0AYAIBUAAN4FACAWAADfBQAgGAAA4AUAIBkAAOEFACAaAADiBQAgHAAA5AUAILACAQDWBAAhtgJAANcEACHMAkAA1wQAIdsCAgDrBAAh7QICAOsEACHuAgEA4QQAIe8CAgDrBAAh8QIAANgF8QIi8gIgANkFACHzAiAA2QUAIfQCAgDgBAAh9QICAOAEACH2AgIA4AQAIfcCAgDgBAAh-AJAANgEACEZEQAAygYAIBIAALwGACATAAC9BgAgFAAAvgYAIBUAAL8GACAWAADABgAgGAAAwQYAIBkAAMIGACAaAADDBgAgGwAAxAYAILACAQAAAAG2AkAAAAABzAJAAAAAAdsCAgAAAAHtAgIAAAAB7gIBAAAAAe8CAgAAAAHxAgAAAPECAvICIAAAAAHzAiAAAAAB9AICAAAAAfUCAgAAAAH2AgIAAAAB9wICAAAAAfgCQAAAAAECAAAAGgAgMQAAgQkAIB8EAADsBwAgBQAA7QcAIAYAAO4HACAdAADvBwAgHgAA8AcAICAAAPEHACAhAADyBwAgIgAA8wcAICQAAPUHACCwAgEAAAABtgJAAAAAAcwCQAAAAAHaAgEAAAAB2wICAAAAAdwCAQAAAAH4AkAAAAABjwMBAAAAAZADIAAAAAGRAwEAAAABkwMAAACTAwKVAwAAAJUDApYDIAAAAAGXAwEAAAABmAMBAAAAAZkDgAAAAAGaA0AAAAABmwMBAAAAAZwDIAAAAAGdAwEAAAABngNAAAAAAZ8DQAAAAAECAAAAAQAgMQAAgwkAIAMAAAAXACAxAACBCQAgMgAAhwkAIBsAAAAXACARAADbBQAgEgAA3AUAIBMAAN0FACAUAADQBgAgFQAA3gUAIBYAAN8FACAYAADgBQAgGQAA4QUAIBoAAOIFACAbAADjBQAgKgAAhwkAILACAQDWBAAhtgJAANcEACHMAkAA1wQAIdsCAgDrBAAh7QICAOsEACHuAgEA4QQAIe8CAgDrBAAh8QIAANgF8QIi8gIgANkFACHzAiAA2QUAIfQCAgDgBAAh9QICAOAEACH2AgIA4AQAIfcCAgDgBAAh-AJAANgEACEZEQAA2wUAIBIAANwFACATAADdBQAgFAAA0AYAIBUAAN4FACAWAADfBQAgGAAA4AUAIBkAAOEFACAaAADiBQAgGwAA4wUAILACAQDWBAAhtgJAANcEACHMAkAA1wQAIdsCAgDrBAAh7QICAOsEACHuAgEA4QQAIe8CAgDrBAAh8QIAANgF8QIi8gIgANkFACHzAiAA2QUAIfQCAgDgBAAh9QICAOAEACH2AgIA4AQAIfcCAgDgBAAh-AJAANgEACEDAAAADwAgMQAAgwkAIDIAAIoJACAhAAAADwAgBAAA-QYAIAUAAPoGACAGAAD7BgAgHQAA_AYAIB4AAP0GACAgAAD-BgAgIQAA_wYAICIAAIAHACAkAACCBwAgKgAAigkAILACAQDWBAAhtgJAANcEACHMAkAA1wQAIdoCAQDhBAAh2wICAOsEACHcAgEA4QQAIfgCQADYBAAhjwMBAOEEACGQAyAA2QUAIZEDAQDhBAAhkwMAAPYGkwMilQMAAPcGlQMilgMgANkFACGXAwEA4QQAIZgDAQDhBAAhmQOAAAAAAZoDQADYBAAhmwMBAOEEACGcAyAA-AYAIZ0DAQDhBAAhngNAANgEACGfA0AA2AQAIR8EAAD5BgAgBQAA-gYAIAYAAPsGACAdAAD8BgAgHgAA_QYAICAAAP4GACAhAAD_BgAgIgAAgAcAICQAAIIHACCwAgEA1gQAIbYCQADXBAAhzAJAANcEACHaAgEA4QQAIdsCAgDrBAAh3AIBAOEEACH4AkAA2AQAIY8DAQDhBAAhkAMgANkFACGRAwEA4QQAIZMDAAD2BpMDIpUDAAD3BpUDIpYDIADZBQAhlwMBAOEEACGYAwEA4QQAIZkDgAAAAAGaA0AA2AQAIZsDAQDhBAAhnAMgAPgGACGdAwEA4QQAIZ4DQADYBAAhnwNAANgEACEfBAAA7AcAIAUAAO0HACAGAADuBwAgHQAA7wcAIB4AAPAHACAhAADyBwAgIgAA8wcAICMAAPQHACAkAAD1BwAgsAIBAAAAAbYCQAAAAAHMAkAAAAAB2gIBAAAAAdsCAgAAAAHcAgEAAAAB-AJAAAAAAY8DAQAAAAGQAyAAAAABkQMBAAAAAZMDAAAAkwMClQMAAACVAwKWAyAAAAABlwMBAAAAAZgDAQAAAAGZA4AAAAABmgNAAAAAAZsDAQAAAAGcAyAAAAABnQMBAAAAAZ4DQAAAAAGfA0AAAAABAgAAAAEAIDEAAIsJACADAAAADwAgMQAAiwkAIDIAAI8JACAhAAAADwAgBAAA-QYAIAUAAPoGACAGAAD7BgAgHQAA_AYAIB4AAP0GACAhAAD_BgAgIgAAgAcAICMAAIEHACAkAACCBwAgKgAAjwkAILACAQDWBAAhtgJAANcEACHMAkAA1wQAIdoCAQDhBAAh2wICAOsEACHcAgEA4QQAIfgCQADYBAAhjwMBAOEEACGQAyAA2QUAIZEDAQDhBAAhkwMAAPYGkwMilQMAAPcGlQMilgMgANkFACGXAwEA4QQAIZgDAQDhBAAhmQOAAAAAAZoDQADYBAAhmwMBAOEEACGcAyAA-AYAIZ0DAQDhBAAhngNAANgEACGfA0AA2AQAIR8EAAD5BgAgBQAA-gYAIAYAAPsGACAdAAD8BgAgHgAA_QYAICEAAP8GACAiAACABwAgIwAAgQcAICQAAIIHACCwAgEA1gQAIbYCQADXBAAhzAJAANcEACHaAgEA4QQAIdsCAgDrBAAh3AIBAOEEACH4AkAA2AQAIY8DAQDhBAAhkAMgANkFACGRAwEA4QQAIZMDAAD2BpMDIpUDAAD3BpUDIpYDIADZBQAhlwMBAOEEACGYAwEA4QQAIZkDgAAAAAGaA0AA2AQAIZsDAQDhBAAhnAMgAPgGACGdAwEA4QQAIZ4DQADYBAAhnwNAANgEACELBAYCBQoDBg4EDAAUHRQFHkwNIFASIVEOIlIPI1MQJFcTAQMAAQEDAAEBAxABAgMAAQcABgwMABERFgcSKQYTKgYUKwcVLgcWLwUYMw0ZNQsaOQ4bPQ8cQhAGBwAGCBgGCRsGDAAMDh8IECULAgoABw0ACQILIAgMAAoBCyEAAgcABg8ABwMJJgAOJwAQKAACBwAGFzQBAgMAAQcABgIDPgEHAAYCAwABB0MGCBNEABVFABZGABhHABlIABpJABtKABxLAAEfAAEBAwABCgRYAAVZAAZaAB1bAB5cACBdACFeACJfACNgACRhAAAAAAUMABk3ABo4ABs5ABw6AB0AAAAAAAUMABk3ABo4ABs5ABw6AB0BAwABAQMAAQMMACI5ACM6ACQAAAADDAAiOQAjOgAkAQMAAQEDAAEDDAApOQAqOgArAAAAAwwAKTkAKjoAKwEDrgEBAQO0AQEDDAAwOQAxOgAyAAAAAwwAMDkAMToAMgEDAAEBAwABBQwANzcAODgAOTkAOjoAOwAAAAAABQwANzcAODgAOTkAOjoAOwMR3AEHEt0BBhTeAQcDEeQBBxLlAQYU5gEHBQwAQDcAQTgAQjkAQzoARAAAAAAABQwAQDcAQTgAQjkAQzoARAEHAAYBBwAGBQwASTcASjgASzkATDoATQAAAAAABQwASTcASjgASzkATDoATQIKAAcNAAkCCgAHDQAJBQwAUjcAUzgAVDkAVToAVgAAAAAABQwAUjcAUzgAVDkAVToAVgIHAAYPAAcCBwAGDwAHBQwAWzcAXDgAXTkAXjoAXwAAAAAABQwAWzcAXDgAXTkAXjoAXwAABQwAZDcAZTgAZjkAZzoAaAAAAAAABQwAZDcAZTgAZjkAZzoAaAIDAAEHAAYCAwABBwAGAwwAbTkAbjoAbwAAAAMMAG05AG46AG8CBwAGF-kCAQIHAAYX7wIBAwwAdDkAdToAdgAAAAMMAHQ5AHU6AHYCAwABBwAGAgMAAQcABgMMAHs5AHw6AH0AAAADDAB7OQB8OgB9AgOXAwEHAAYCA50DAQcABgUMAIIBNwCDATgAhAE5AIUBOgCGAQAAAAAABQwAggE3AIMBOACEATkAhQE6AIYBAgMAAQevAwYCAwABB7UDBgUMAIsBNwCMATgAjQE5AI4BOgCPAQAAAAAABQwAiwE3AIwBOACNATkAjgE6AI8BAR8AAQEfAAEDDACUATkAlQE6AJYBAAAAAwwAlAE5AJUBOgCWASUCASZiASdkAShlASlmAStoASxqFS1rFi5tAS9vFTBwFzNxATRyATVzFTt2GDx3Hj14Aj55Aj96AkB7AkF8AkJ-AkOAARVEgQEfRYMBAkaFARVHhgEgSIcBAkmIAQJKiQEVS4wBIUyNASVNjgEDTo8BA0-QAQNQkQEDUZIBA1KUAQNTlgEVVJcBJlWZAQNWmwEVV5wBJ1idAQNZngEDWp8BFVuiAShcowEsXaQBBF6lAQRfpgEEYKcBBGGoAQRiqgEEY6wBFWStAS1lsAEEZrIBFWezAS5otQEEabYBBGq3ARVrugEvbLsBM228ARNuvQETb74BE3C_ARNxwAETcsIBE3PEARV0xQE0dccBE3bJARV3ygE1eMsBE3nMARN6zQEVe9ABNnzRATx90gEGftMBBn_UAQaAAdUBBoEB1gEGggHYAQaDAdoBFYQB2wE9hQHgAQaGAeIBFYcB4wE-iAHnAQaJAegBBooB6QEViwHsAT-MAe0BRY0B7gEHjgHvAQePAfABB5AB8QEHkQHyAQeSAfQBB5MB9gEVlAH3AUaVAfkBB5YB-wEVlwH8AUeYAf0BB5kB_gEHmgH_ARWbAYICSJwBgwJOnQGEAgieAYUCCJ8BhgIIoAGHAgihAYgCCKIBigIIowGMAhWkAY0CT6UBjwIIpgGRAhWnAZICUKgBkwIIqQGUAgiqAZUCFasBmAJRrAGZAletAZoCC64BmwILrwGcAguwAZ0CC7EBngILsgGgAguzAaICFbQBowJYtQGlAgu2AacCFbcBqAJZuAGpAgu5AaoCC7oBqwIVuwGuAlq8Aa8CYL0BsQIJvgGyAgm_AbUCCcABtgIJwQG3AgnCAbkCCcMBuwIVxAG8AmHFAb4CCcYBwAIVxwHBAmLIAcICCckBwwIJygHEAhXLAccCY8wByAJpzQHJAgXOAcoCBc8BywIF0AHMAgXRAc0CBdIBzwIF0wHRAhXUAdICatUB1AIF1gHWAhXXAdcCa9gB2AIF2QHZAgXaAdoCFdsB3QJs3AHeAnDdAd8CDd4B4AIN3wHhAg3gAeICDeEB4wIN4gHlAg3jAecCFeQB6AJx5QHrAg3mAe0CFecB7gJy6AHwAg3pAfECDeoB8gIV6wH1AnPsAfYCd-0B9wIO7gH4Ag7vAfkCDvAB-gIO8QH7Ag7yAf0CDvMB_wIV9AGAA3j1AYIDDvYBhAMV9wGFA3n4AYYDDvkBhwMO-gGIAxX7AYsDevwBjAN-_QGNAw_-AY4DD_8BjwMPgAKQAw-BApEDD4ICkwMPgwKVAxWEApYDf4UCmQMPhgKbAxWHApwDgAGIAp4DD4kCnwMPigKgAxWLAqMDgQGMAqQDhwGNAqUDEI4CpgMQjwKnAxCQAqgDEJECqQMQkgKrAxCTAq0DFZQCrgOIAZUCsQMQlgKzAxWXArQDiQGYArYDEJkCtwMQmgK4AxWbArsDigGcArwDkAGdAr0DEp4CvgMSnwK_AxKgAsADEqECwQMSogLDAxKjAsUDFaQCxgORAaUCyAMSpgLKAxWnAssDkgGoAswDEqkCzQMSqgLOAxWrAtEDkwGsAtIDlwE" + strings: JSON.parse("[\"where\",\"orderBy\",\"cursor\",\"user\",\"accounts\",\"sessions\",\"verifications\",\"model\",\"latestOfModel\",\"parentOfModels\",\"modelVersion\",\"modelVersions\",\"_count\",\"tag\",\"tags\",\"taggedVersion\",\"taggedAdditionalFiles\",\"latestVersion\",\"parentModel\",\"childModels\",\"parentVersion\",\"versions\",\"authors\",\"granteeUser\",\"permissions\",\"additionalFiles\",\"likes\",\"interactions\",\"drafts\",\"parent\",\"replies\",\"modelComment\",\"comments\",\"authoredModels\",\"grantedPermissions\",\"actor\",\"events\",\"modelLikes\",\"modelInteractions\",\"modelDrafts\",\"commentLikes\",\"passkeys\",\"User.findUnique\",\"User.findUniqueOrThrow\",\"User.findFirst\",\"User.findFirstOrThrow\",\"User.findMany\",\"data\",\"User.createOne\",\"User.createMany\",\"User.createManyAndReturn\",\"User.updateOne\",\"User.updateMany\",\"User.updateManyAndReturn\",\"create\",\"update\",\"User.upsertOne\",\"User.deleteOne\",\"User.deleteMany\",\"having\",\"_avg\",\"_sum\",\"_min\",\"_max\",\"User.groupBy\",\"User.aggregate\",\"Account.findUnique\",\"Account.findUniqueOrThrow\",\"Account.findFirst\",\"Account.findFirstOrThrow\",\"Account.findMany\",\"Account.createOne\",\"Account.createMany\",\"Account.createManyAndReturn\",\"Account.updateOne\",\"Account.updateMany\",\"Account.updateManyAndReturn\",\"Account.upsertOne\",\"Account.deleteOne\",\"Account.deleteMany\",\"Account.groupBy\",\"Account.aggregate\",\"Session.findUnique\",\"Session.findUniqueOrThrow\",\"Session.findFirst\",\"Session.findFirstOrThrow\",\"Session.findMany\",\"Session.createOne\",\"Session.createMany\",\"Session.createManyAndReturn\",\"Session.updateOne\",\"Session.updateMany\",\"Session.updateManyAndReturn\",\"Session.upsertOne\",\"Session.deleteOne\",\"Session.deleteMany\",\"Session.groupBy\",\"Session.aggregate\",\"Verification.findUnique\",\"Verification.findUniqueOrThrow\",\"Verification.findFirst\",\"Verification.findFirstOrThrow\",\"Verification.findMany\",\"Verification.createOne\",\"Verification.createMany\",\"Verification.createManyAndReturn\",\"Verification.updateOne\",\"Verification.updateMany\",\"Verification.updateManyAndReturn\",\"Verification.upsertOne\",\"Verification.deleteOne\",\"Verification.deleteMany\",\"Verification.groupBy\",\"Verification.aggregate\",\"Passkey.findUnique\",\"Passkey.findUniqueOrThrow\",\"Passkey.findFirst\",\"Passkey.findFirstOrThrow\",\"Passkey.findMany\",\"Passkey.createOne\",\"Passkey.createMany\",\"Passkey.createManyAndReturn\",\"Passkey.updateOne\",\"Passkey.updateMany\",\"Passkey.updateManyAndReturn\",\"Passkey.upsertOne\",\"Passkey.deleteOne\",\"Passkey.deleteMany\",\"Passkey.groupBy\",\"Passkey.aggregate\",\"Model.findUnique\",\"Model.findUniqueOrThrow\",\"Model.findFirst\",\"Model.findFirstOrThrow\",\"Model.findMany\",\"Model.createOne\",\"Model.createMany\",\"Model.createManyAndReturn\",\"Model.updateOne\",\"Model.updateMany\",\"Model.updateManyAndReturn\",\"Model.upsertOne\",\"Model.deleteOne\",\"Model.deleteMany\",\"Model.groupBy\",\"Model.aggregate\",\"ModelVersion.findUnique\",\"ModelVersion.findUniqueOrThrow\",\"ModelVersion.findFirst\",\"ModelVersion.findFirstOrThrow\",\"ModelVersion.findMany\",\"ModelVersion.createOne\",\"ModelVersion.createMany\",\"ModelVersion.createManyAndReturn\",\"ModelVersion.updateOne\",\"ModelVersion.updateMany\",\"ModelVersion.updateManyAndReturn\",\"ModelVersion.upsertOne\",\"ModelVersion.deleteOne\",\"ModelVersion.deleteMany\",\"ModelVersion.groupBy\",\"ModelVersion.aggregate\",\"ModelVersionTag.findUnique\",\"ModelVersionTag.findUniqueOrThrow\",\"ModelVersionTag.findFirst\",\"ModelVersionTag.findFirstOrThrow\",\"ModelVersionTag.findMany\",\"ModelVersionTag.createOne\",\"ModelVersionTag.createMany\",\"ModelVersionTag.createManyAndReturn\",\"ModelVersionTag.updateOne\",\"ModelVersionTag.updateMany\",\"ModelVersionTag.updateManyAndReturn\",\"ModelVersionTag.upsertOne\",\"ModelVersionTag.deleteOne\",\"ModelVersionTag.deleteMany\",\"ModelVersionTag.groupBy\",\"ModelVersionTag.aggregate\",\"ModelAdditionalFile.findUnique\",\"ModelAdditionalFile.findUniqueOrThrow\",\"ModelAdditionalFile.findFirst\",\"ModelAdditionalFile.findFirstOrThrow\",\"ModelAdditionalFile.findMany\",\"ModelAdditionalFile.createOne\",\"ModelAdditionalFile.createMany\",\"ModelAdditionalFile.createManyAndReturn\",\"ModelAdditionalFile.updateOne\",\"ModelAdditionalFile.updateMany\",\"ModelAdditionalFile.updateManyAndReturn\",\"ModelAdditionalFile.upsertOne\",\"ModelAdditionalFile.deleteOne\",\"ModelAdditionalFile.deleteMany\",\"ModelAdditionalFile.groupBy\",\"ModelAdditionalFile.aggregate\",\"Tag.findUnique\",\"Tag.findUniqueOrThrow\",\"Tag.findFirst\",\"Tag.findFirstOrThrow\",\"Tag.findMany\",\"Tag.createOne\",\"Tag.createMany\",\"Tag.createManyAndReturn\",\"Tag.updateOne\",\"Tag.updateMany\",\"Tag.updateManyAndReturn\",\"Tag.upsertOne\",\"Tag.deleteOne\",\"Tag.deleteMany\",\"Tag.groupBy\",\"Tag.aggregate\",\"ModelAuthor.findUnique\",\"ModelAuthor.findUniqueOrThrow\",\"ModelAuthor.findFirst\",\"ModelAuthor.findFirstOrThrow\",\"ModelAuthor.findMany\",\"ModelAuthor.createOne\",\"ModelAuthor.createMany\",\"ModelAuthor.createManyAndReturn\",\"ModelAuthor.updateOne\",\"ModelAuthor.updateMany\",\"ModelAuthor.updateManyAndReturn\",\"ModelAuthor.upsertOne\",\"ModelAuthor.deleteOne\",\"ModelAuthor.deleteMany\",\"ModelAuthor.groupBy\",\"ModelAuthor.aggregate\",\"ModelPermission.findUnique\",\"ModelPermission.findUniqueOrThrow\",\"ModelPermission.findFirst\",\"ModelPermission.findFirstOrThrow\",\"ModelPermission.findMany\",\"ModelPermission.createOne\",\"ModelPermission.createMany\",\"ModelPermission.createManyAndReturn\",\"ModelPermission.updateOne\",\"ModelPermission.updateMany\",\"ModelPermission.updateManyAndReturn\",\"ModelPermission.upsertOne\",\"ModelPermission.deleteOne\",\"ModelPermission.deleteMany\",\"ModelPermission.groupBy\",\"ModelPermission.aggregate\",\"ModelLike.findUnique\",\"ModelLike.findUniqueOrThrow\",\"ModelLike.findFirst\",\"ModelLike.findFirstOrThrow\",\"ModelLike.findMany\",\"ModelLike.createOne\",\"ModelLike.createMany\",\"ModelLike.createManyAndReturn\",\"ModelLike.updateOne\",\"ModelLike.updateMany\",\"ModelLike.updateManyAndReturn\",\"ModelLike.upsertOne\",\"ModelLike.deleteOne\",\"ModelLike.deleteMany\",\"ModelLike.groupBy\",\"ModelLike.aggregate\",\"ModelInteraction.findUnique\",\"ModelInteraction.findUniqueOrThrow\",\"ModelInteraction.findFirst\",\"ModelInteraction.findFirstOrThrow\",\"ModelInteraction.findMany\",\"ModelInteraction.createOne\",\"ModelInteraction.createMany\",\"ModelInteraction.createManyAndReturn\",\"ModelInteraction.updateOne\",\"ModelInteraction.updateMany\",\"ModelInteraction.updateManyAndReturn\",\"ModelInteraction.upsertOne\",\"ModelInteraction.deleteOne\",\"ModelInteraction.deleteMany\",\"ModelInteraction.groupBy\",\"ModelInteraction.aggregate\",\"ModelDraft.findUnique\",\"ModelDraft.findUniqueOrThrow\",\"ModelDraft.findFirst\",\"ModelDraft.findFirstOrThrow\",\"ModelDraft.findMany\",\"ModelDraft.createOne\",\"ModelDraft.createMany\",\"ModelDraft.createManyAndReturn\",\"ModelDraft.updateOne\",\"ModelDraft.updateMany\",\"ModelDraft.updateManyAndReturn\",\"ModelDraft.upsertOne\",\"ModelDraft.deleteOne\",\"ModelDraft.deleteMany\",\"ModelDraft.groupBy\",\"ModelDraft.aggregate\",\"ModelComment.findUnique\",\"ModelComment.findUniqueOrThrow\",\"ModelComment.findFirst\",\"ModelComment.findFirstOrThrow\",\"ModelComment.findMany\",\"ModelComment.createOne\",\"ModelComment.createMany\",\"ModelComment.createManyAndReturn\",\"ModelComment.updateOne\",\"ModelComment.updateMany\",\"ModelComment.updateManyAndReturn\",\"ModelComment.upsertOne\",\"ModelComment.deleteOne\",\"ModelComment.deleteMany\",\"ModelComment.groupBy\",\"ModelComment.aggregate\",\"ModelCommentLike.findUnique\",\"ModelCommentLike.findUniqueOrThrow\",\"ModelCommentLike.findFirst\",\"ModelCommentLike.findFirstOrThrow\",\"ModelCommentLike.findMany\",\"ModelCommentLike.createOne\",\"ModelCommentLike.createMany\",\"ModelCommentLike.createManyAndReturn\",\"ModelCommentLike.updateOne\",\"ModelCommentLike.updateMany\",\"ModelCommentLike.updateManyAndReturn\",\"ModelCommentLike.upsertOne\",\"ModelCommentLike.deleteOne\",\"ModelCommentLike.deleteMany\",\"ModelCommentLike.groupBy\",\"ModelCommentLike.aggregate\",\"Event.findUnique\",\"Event.findUniqueOrThrow\",\"Event.findFirst\",\"Event.findFirstOrThrow\",\"Event.findMany\",\"Event.createOne\",\"Event.createMany\",\"Event.createManyAndReturn\",\"Event.updateOne\",\"Event.updateMany\",\"Event.updateManyAndReturn\",\"Event.upsertOne\",\"Event.deleteOne\",\"Event.deleteMany\",\"Event.groupBy\",\"Event.aggregate\",\"AND\",\"OR\",\"NOT\",\"id\",\"type\",\"actorId\",\"resourceType\",\"resourceId\",\"payload\",\"createdAt\",\"processedAt\",\"equals\",\"in\",\"notIn\",\"lt\",\"lte\",\"gt\",\"gte\",\"not\",\"string_contains\",\"string_starts_with\",\"string_ends_with\",\"array_starts_with\",\"array_ends_with\",\"array_contains\",\"contains\",\"startsWith\",\"endsWith\",\"modelCommentId\",\"userId\",\"legacyId\",\"parentId\",\"modelId\",\"versionNumber\",\"content\",\"likesCount\",\"updatedAt\",\"editedAt\",\"deletedAt\",\"schemaVersion\",\"ModelInteractionKind\",\"kind\",\"sessionId\",\"ipHash\",\"userAgent\",\"referer\",\"geo\",\"cookie\",\"granteeUserId\",\"PermissionLevel\",\"permissionLevel\",\"AuthorRole\",\"role\",\"name\",\"displayName\",\"every\",\"some\",\"none\",\"taggedVersionNumber\",\"fileKey\",\"ModelFileKind\",\"tagId\",\"title\",\"description\",\"changeSummary\",\"previewImageFileKey\",\"netlogoFileKey\",\"netlogoVersion\",\"infoTab\",\"finalizedAt\",\"latestVersionNumber\",\"parentModelId\",\"parentVersionNumber\",\"ModelVisibility\",\"visibility\",\"isEndorsed\",\"isLibraryModel\",\"viewCount\",\"runCount\",\"downloadCount\",\"shareCount\",\"publicKey\",\"credentialID\",\"counter\",\"deviceType\",\"backedUp\",\"transports\",\"aaguid\",\"identifier\",\"value\",\"expiresAt\",\"token\",\"ipAddress\",\"impersonatedBy\",\"accountId\",\"providerId\",\"accessToken\",\"refreshToken\",\"accessTokenExpiresAt\",\"refreshTokenExpiresAt\",\"scope\",\"idToken\",\"password\",\"email\",\"emailVerified\",\"image\",\"SystemRole\",\"systemRole\",\"UserKind\",\"userKind\",\"isProfilePublic\",\"bio\",\"country\",\"socialLinks\",\"dob\",\"affiliation\",\"banned\",\"banReason\",\"banExpires\",\"onboardedAt\",\"modelCommentId_userId\",\"modelId_userId\",\"modelId_granteeUserId\",\"modelId_versionNumber\",\"modelId_versionNumber_tagId\",\"id_latestVersionNumber\",\"is\",\"isNot\",\"connectOrCreate\",\"upsert\",\"createMany\",\"set\",\"disconnect\",\"delete\",\"connect\",\"updateMany\",\"deleteMany\",\"increment\",\"decrement\",\"multiply\",\"divide\"]"), + graph: "ygqqAaACJQQAAJUFACAFAACWBQAgBgAAlwUAICAAAO4EACAhAACJBQAgIgAAigUAICQAAJgFACAlAACLBQAgJgAAjAUAICcAAI0FACAoAADvBAAgKQAAmQUAINICAACRBQAw0wIAAA8AENQCAACRBQAw1QIBAAAAAdsCQADDBAAh8AICAAAAAfYCQADDBAAh-AJAAOMEACGGAwEAwgQAIYcDAQDCBAAhuQMBAAAAAboDIADiBAAhuwMBAMIEACG9AwAAkgW9AyK_AwAAkwW_AyLAAyAA4gQAIcEDAQDCBAAhwgMBAMIEACHDAwAA9AQAIMQDQADjBAAhxQMBAMIEACHGAyAAlAUAIccDAQDCBAAhyANAAOMEACHJA0AA4wQAIQEAAAABACARAwAA5AQAINICAACcBQAw0wIAAAMAENQCAACcBQAw1QIBAMAEACHbAkAAwwQAIe8CAQDABAAh9gJAAMMEACGwAwEAwAQAIbEDAQDABAAhsgMBAMIEACGzAwEAwgQAIbQDQADjBAAhtQNAAOMEACG2AwEAwgQAIbcDAQDCBAAhuAMBAMIEACEIAwAAnAkAILIDAACdBQAgswMAAJ0FACC0AwAAnQUAILUDAACdBQAgtgMAAJ0FACC3AwAAnQUAILgDAACdBQAgEQMAAOQEACDSAgAAnAUAMNMCAAADABDUAgAAnAUAMNUCAQAAAAHbAkAAwwQAIe8CAQDABAAh9gJAAMMEACGwAwEAwAQAIbEDAQDABAAhsgMBAMIEACGzAwEAwgQAIbQDQADjBAAhtQNAAOMEACG2AwEAwgQAIbcDAQDCBAAhuAMBAMIEACEDAAAAAwAgAQAABAAwAgAABQAgDQMAAOQEACDSAgAAmwUAMNMCAAAHABDUAgAAmwUAMNUCAQDABAAh2wJAAMMEACHvAgEAwAQAIfYCQADDBAAh_gIBAMIEACGsA0AAwwQAIa0DAQDABAAhrgMBAMIEACGvAwEAwgQAIQQDAACcCQAg_gIAAJ0FACCuAwAAnQUAIK8DAACdBQAgDQMAAOQEACDSAgAAmwUAMNMCAAAHABDUAgAAmwUAMNUCAQAAAAHbAkAAwwQAIe8CAQDABAAh9gJAAMMEACH-AgEAwgQAIawDQADDBAAhrQMBAAAAAa4DAQDCBAAhrwMBAMIEACEDAAAABwAgAQAACAAwAgAACQAgCwMAAOwEACDSAgAAmgUAMNMCAAALABDUAgAAmgUAMNUCAQDABAAh2wJAAOMEACHvAgEAwgQAIfYCQADjBAAhqgMBAMAEACGrAwEAwAQAIawDQADDBAAhBAMAAJwJACDbAgAAnQUAIO8CAACdBQAg9gIAAJ0FACALAwAA7AQAINICAACaBQAw0wIAAAsAENQCAACaBQAw1QIBAAAAAdsCQADjBAAh7wIBAMIEACH2AkAA4wQAIaoDAQDABAAhqwMBAMAEACGsA0AAwwQAIQMAAAALACABAAAMADACAAANACAlBAAAlQUAIAUAAJYFACAGAACXBQAgIAAA7gQAICEAAIkFACAiAACKBQAgJAAAmAUAICUAAIsFACAmAACMBQAgJwAAjQUAICgAAO8EACApAACZBQAg0gIAAJEFADDTAgAADwAQ1AIAAJEFADDVAgEAwAQAIdsCQADDBAAh8AICAMEEACH2AkAAwwQAIfgCQADjBAAhhgMBAMIEACGHAwEAwgQAIbkDAQDCBAAhugMgAOIEACG7AwEAwgQAIb0DAACSBb0DIr8DAACTBb8DIsADIADiBAAhwQMBAMIEACHCAwEAwgQAIcMDAAD0BAAgxANAAOMEACHFAwEAwgQAIcYDIACUBQAhxwMBAMIEACHIA0AA4wQAIckDQADjBAAhAQAAAA8AIAkDAADkBAAgBwAA6wQAINICAACPBQAw0wIAABEAENQCAACPBQAw2wJAAMMEACHvAgEAwAQAIfICAQDABAAhhgMAAJAFhgMiAgMAAJwJACAHAACeCQAgCgMAAOQEACAHAADrBAAg0gIAAI8FADDTAgAAEQAQ1AIAAI8FADDbAkAAwwQAIe8CAQDABAAh8gIBAMAEACGGAwAAkAWGAyLLAwAAjgUAIAMAAAARACABAAASADACAAATACATBwAA6wQAIAgAAPEEACAJAAD8BAAgDgAAxAQAIBAAAP0EACDSAgAA-wQAMNMCAAAVABDUAgAA-wQAMNsCQADDBAAh8gIBAMAEACHzAgIA4QQAIZADAQDABAAhkQMBAMIEACGSAwEAwgQAIZMDAQDCBAAhlAMBAMAEACGVAwEAwgQAIZYDAQDCBAAhlwNAAOMEACEBAAAAFQAgHhEAAIcFACASAADxBAAgEwAA_AQAIBQAAIcFACAVAACIBQAgFgAAiQUAIBgAAIoFACAZAAD9BAAgGgAAiwUAIBsAAIwFACAcAACNBQAgIAAA7gQAINICAACFBQAw0wIAABcAENQCAACFBQAw1QIBAMAEACHbAkAAwwQAIfACAgDBBAAh9gJAAMMEACH4AkAA4wQAIZgDAgDBBAAhmQMBAMIEACGaAwIAwQQAIZwDAACGBZwDIp0DIADiBAAhngMgAOIEACGfAwIA4QQAIaADAgDhBAAhoQMCAOEEACGiAwIA4QQAIQEAAAAXACAREQAAoQkAIBIAAJ4JACATAACfCQAgFAAAoQkAIBUAAKMJACAWAACTCQAgGAAAlAkAIBkAAKAJACAaAACWCQAgGwAAlwkAIBwAAJgJACAgAACZCQAg8AIAAJ0FACD4AgAAnQUAIJgDAACdBQAgmQMAAJ0FACCaAwAAnQUAIB8RAACHBQAgEgAA8QQAIBMAAPwEACAUAACHBQAgFQAAiAUAIBYAAIkFACAYAACKBQAgGQAA_QQAIBoAAIsFACAbAACMBQAgHAAAjQUAICAAAO4EACDSAgAAhQUAMNMCAAAXABDUAgAAhQUAMNUCAQAAAAHbAkAAwwQAIfACAgAAAAH2AkAAwwQAIfgCQADjBAAhmAMCAMEEACGZAwEAwgQAIZoDAgDBBAAhnAMAAIYFnAMinQMgAOIEACGeAyAA4gQAIZ8DAgDhBAAhoAMCAOEEACGhAwIA4QQAIaIDAgDhBAAhzwMAAIQFACADAAAAFwAgAQAAGQAwAgAAGgAgCQoAAIAFACANAACDBQAg0gIAAIIFADDTAgAAHAAQ1AIAAIIFADDbAkAAwwQAIfICAQDABAAh8wICAOEEACGPAwEAwAQAIQIKAAChCQAgDQAAogkAIAoKAACABQAgDQAAgwUAINICAACCBQAw0wIAABwAENQCAACCBQAw2wJAAMMEACHyAgEAwAQAIfMCAgDhBAAhjwMBAMAEACHOAwAAgQUAIAMAAAAcACABAAAdADACAAAeACADAAAAHAAgAQAAHQAwAgAAHgAgAQAAABwAIAsHAADrBAAgDwAAgAUAINICAAD-BAAw0wIAACIAENQCAAD-BAAw1QIBAMAEACHbAkAAwwQAIfICAQDABAAh-wIAAP8EjwMijAMCAOEEACGNAwEAwAQAIQIHAACeCQAgDwAAoQkAIAsHAADrBAAgDwAAgAUAINICAAD-BAAw0wIAACIAENQCAAD-BAAw1QIBAAAAAdsCQADDBAAh8gIBAMAEACH7AgAA_wSPAyKMAwIA4QQAIY0DAQDABAAhAwAAACIAIAEAACMAMAIAACQAIAEAAAAXACABAAAAHAAgAQAAACIAIAEAAAAXACADAAAAFwAgAQAAGQAwAgAAGgAgAQAAABUAIAsHAACeCQAgCAAAngkAIAkAAJ8JACAOAACWBgAgEAAAoAkAIJEDAACdBQAgkgMAAJ0FACCTAwAAnQUAIJUDAACdBQAglgMAAJ0FACCXAwAAnQUAIBQHAADrBAAgCAAA8QQAIAkAAPwEACAOAADEBAAgEAAA_QQAINICAAD7BAAw0wIAABUAENQCAAD7BAAw2wJAAMMEACHyAgEAwAQAIfMCAgDhBAAhkAMBAMAEACGRAwEAwgQAIZIDAQDCBAAhkwMBAMIEACGUAwEAwAQAIZUDAQDCBAAhlgMBAMIEACGXA0AA4wQAIc0DAAD6BAAgAwAAABUAIAEAACwAMAIAAC0AIAMAAAARACABAAASADACAAATACAKBwAA6wQAIBcAAOwEACDSAgAA-AQAMNMCAAAwABDUAgAA-AQAMNUCAQDABAAh2wJAAMMEACHyAgEAwAQAIYIDAQDCBAAhhAMAAPkEhAMiAwcAAJ4JACAXAACcCQAgggMAAJ0FACALBwAA6wQAIBcAAOwEACDSAgAA-AQAMNMCAAAwABDUAgAA-AQAMNUCAQAAAAHbAkAAwwQAIfICAQDABAAhggMBAMIEACGEAwAA-QSEAyLMAwAA9wQAIAMAAAAwACABAAAxADACAAAyACABAAAADwAgAwAAACIAIAEAACMAMAIAACQAIAgDAADkBAAgBwAA6wQAINICAAD2BAAw0wIAADYAENQCAAD2BAAw2wJAAMMEACHvAgEAwAQAIfICAQDABAAhAgMAAJwJACAHAACeCQAgCQMAAOQEACAHAADrBAAg0gIAAPYEADDTAgAANgAQ1AIAAPYEADDbAkAAwwQAIe8CAQDABAAh8gIBAMAEACHLAwAA9QQAIAMAAAA2ACABAAA3ADACAAA4ACARAwAA7AQAIAcAAOsEACDSAgAA8gQAMNMCAAA6ABDUAgAA8gQAMNUCAQDABAAh2wJAAMMEACHvAgEAwgQAIfICAQDABAAh8wICAMEEACH7AgAA8wT7AiL8AgEAwgQAIf0CAQDCBAAh_gIBAMIEACH_AgEAwgQAIYADAAD0BAAggQMBAMIEACEKAwAAnAkAIAcAAJ4JACDvAgAAnQUAIPMCAACdBQAg_AIAAJ0FACD9AgAAnQUAIP4CAACdBQAg_wIAAJ0FACCAAwAAnQUAIIEDAACdBQAgEQMAAOwEACAHAADrBAAg0gIAAPIEADDTAgAAOgAQ1AIAAPIEADDVAgEAAAAB2wJAAMMEACHvAgEAwgQAIfICAQDABAAh8wICAMEEACH7AgAA8wT7AiL8AgEAwgQAIf0CAQDCBAAh_gIBAMIEACH_AgEAwgQAIYADAAD0BAAggQMBAMIEACEDAAAAOgAgAQAAOwAwAgAAPAAgAQAAAA8AIAwDAADkBAAgBwAA8QQAIC8AAOYEACDSAgAA8AQAMNMCAAA_ABDUAgAA8AQAMNUCAQDABAAh2wJAAMMEACHvAgEAwAQAIfICAQDCBAAh9gJAAMMEACH5AgIA4QQAIQMDAACcCQAgBwAAngkAIPICAACdBQAgDAMAAOQEACAHAADxBAAgLwAA5gQAINICAADwBAAw0wIAAD8AENQCAADwBAAw1QIBAAAAAdsCQADDBAAh7wIBAMAEACHyAgEAwgQAIfYCQADDBAAh-QICAOEEACEDAAAAPwAgAQAAQAAwAgAAQQAgAQAAABcAIBQDAADsBAAgBwAA6wQAIBoAAO8EACAdAADtBAAgHgAA7gQAINICAADqBAAw0wIAAEQAENQCAADqBAAw1QIBAMAEACHbAkAAwwQAIe8CAQDCBAAh8AICAMEEACHxAgEAwgQAIfICAQDABAAh8wICAMEEACH0AgEAwgQAIfUCAgDhBAAh9gJAAMMEACH3AkAA4wQAIfgCQADjBAAhDAMAAJwJACAHAACeCQAgGgAAmgkAIB0AAJ0JACAeAACZCQAg7wIAAJ0FACDwAgAAnQUAIPECAACdBQAg8wIAAJ0FACD0AgAAnQUAIPcCAACdBQAg-AIAAJ0FACAUAwAA7AQAIAcAAOsEACAaAADvBAAgHQAA7QQAIB4AAO4EACDSAgAA6gQAMNMCAABEABDUAgAA6gQAMNUCAQAAAAHbAkAAwwQAIe8CAQDCBAAh8AICAAAAAfECAQDCBAAh8gIBAMAEACHzAgIAwQQAIfQCAQDCBAAh9QICAOEEACH2AkAAwwQAIfcCQADjBAAh-AJAAOMEACEDAAAARAAgAQAARQAwAgAARgAgAQAAAA8AIAEAAABEACADAAAARAAgAQAARQAwAgAARgAgCAMAAOQEACAfAADpBAAg0gIAAOgEADDTAgAASwAQ1AIAAOgEADDbAkAAwwQAIe4CAQDABAAh7wIBAMAEACECAwAAnAkAIB8AAJ0JACAJAwAA5AQAIB8AAOkEACDSAgAA6AQAMNMCAABLABDUAgAA6AQAMNsCQADDBAAh7gIBAMAEACHvAgEAwAQAIcoDAADnBAAgAwAAAEsAIAEAAEwAMAIAAE0AIAEAAABEACABAAAASwAgAQAAABcAIAEAAAAVACABAAAAEQAgAQAAADAAIAEAAAAiACABAAAANgAgAQAAADoAIAEAAAA_ACABAAAARAAgAwAAADAAIAEAADEAMAIAADIAIAwjAADkBAAg0gIAAOUEADDTAgAAWwAQ1AIAAOUEADDVAgEAwAQAIdYCAQDABAAh1wIBAMAEACHYAgEAwAQAIdkCAQDABAAh2gIAAOYEACDbAkAAwwQAIdwCQADjBAAhAiMAAJwJACDcAgAAnQUAIAwjAADkBAAg0gIAAOUEADDTAgAAWwAQ1AIAAOUEADDVAgEAAAAB1gIBAMAEACHXAgEAwAQAIdgCAQDABAAh2QIBAMAEACHaAgAA5gQAINsCQADDBAAh3AJAAOMEACEDAAAAWwAgAQAAXAAwAgAAXQAgAwAAADYAIAEAADcAMAIAADgAIAMAAAA6ACABAAA7ADACAAA8ACADAAAAPwAgAQAAQAAwAgAAQQAgAwAAAEQAIAEAAEUAMAIAAEYAIAMAAABLACABAABMADACAABNACAPAwAA5AQAINICAADgBAAw0wIAAGQAENQCAADgBAAw1QIBAMAEACHbAkAA4wQAIe8CAQDABAAhhwMBAMIEACGjAwEAwAQAIaQDAQDABAAhpQMCAOEEACGmAwEAwAQAIacDIADiBAAhqAMBAMIEACGpAwEAwgQAIQUDAACcCQAg2wIAAJ0FACCHAwAAnQUAIKgDAACdBQAgqQMAAJ0FACAPAwAA5AQAINICAADgBAAw0wIAAGQAENQCAADgBAAw1QIBAAAAAdsCQADjBAAh7wIBAMAEACGHAwEAwgQAIaMDAQDABAAhpAMBAMAEACGlAwIA4QQAIaYDAQDABAAhpwMgAOIEACGoAwEAwgQAIakDAQDCBAAhAwAAAGQAIAEAAGUAMAIAAGYAIAEAAAADACABAAAABwAgAQAAAAsAIAEAAAARACABAAAAMAAgAQAAAFsAIAEAAAA2ACABAAAAOgAgAQAAAD8AIAEAAABEACABAAAASwAgAQAAAGQAIAEAAAABACAbBAAAkAkAIAUAAJEJACAGAACSCQAgIAAAmQkAICEAAJMJACAiAACUCQAgJAAAlQkAICUAAJYJACAmAACXCQAgJwAAmAkAICgAAJoJACApAACbCQAg8AIAAJ0FACD4AgAAnQUAIIYDAACdBQAghwMAAJ0FACC5AwAAnQUAILsDAACdBQAgwQMAAJ0FACDCAwAAnQUAIMMDAACdBQAgxAMAAJ0FACDFAwAAnQUAIMYDAACdBQAgxwMAAJ0FACDIAwAAnQUAIMkDAACdBQAgAwAAAA8AIAEAAHUAMAIAAAEAIAMAAAAPACABAAB1ADACAAABACADAAAADwAgAQAAdQAwAgAAAQAgIgQAAIQJACAFAACFCQAgBgAAhgkAICAAAI0JACAhAACHCQAgIgAAiAkAICQAAIkJACAlAACKCQAgJgAAiwkAICcAAIwJACAoAACOCQAgKQAAjwkAINUCAQAAAAHbAkAAAAAB8AICAAAAAfYCQAAAAAH4AkAAAAABhgMBAAAAAYcDAQAAAAG5AwEAAAABugMgAAAAAbsDAQAAAAG9AwAAAL0DAr8DAAAAvwMCwAMgAAAAAcEDAQAAAAHCAwEAAAABwwOAAAAAAcQDQAAAAAHFAwEAAAABxgMgAAAAAccDAQAAAAHIA0AAAAAByQNAAAAAAQEvAAB5ACAW1QIBAAAAAdsCQAAAAAHwAgIAAAAB9gJAAAAAAfgCQAAAAAGGAwEAAAABhwMBAAAAAbkDAQAAAAG6AyAAAAABuwMBAAAAAb0DAAAAvQMCvwMAAAC_AwLAAyAAAAABwQMBAAAAAcIDAQAAAAHDA4AAAAABxANAAAAAAcUDAQAAAAHGAyAAAAABxwMBAAAAAcgDQAAAAAHJA0AAAAABAS8AAHsAMAEvAAB7ADAiBAAA_QcAIAUAAP4HACAGAAD_BwAgIAAAhggAICEAAIAIACAiAACBCAAgJAAAgggAICUAAIMIACAmAACECAAgJwAAhQgAICgAAIcIACApAACICAAg1QIBAKEFACHbAkAAogUAIfACAgCyBQAh9gJAAKIFACH4AkAAowUAIYYDAQCzBQAhhwMBALMFACG5AwEAswUAIboDIADSBgAhuwMBALMFACG9AwAA-ge9AyK_AwAA-we_AyLAAyAA0gYAIcEDAQCzBQAhwgMBALMFACHDA4AAAAABxANAAKMFACHFAwEAswUAIcYDIAD8BwAhxwMBALMFACHIA0AAowUAIckDQACjBQAhAgAAAAEAIC8AAH4AIBbVAgEAoQUAIdsCQACiBQAh8AICALIFACH2AkAAogUAIfgCQACjBQAhhgMBALMFACGHAwEAswUAIbkDAQCzBQAhugMgANIGACG7AwEAswUAIb0DAAD6B70DIr8DAAD7B78DIsADIADSBgAhwQMBALMFACHCAwEAswUAIcMDgAAAAAHEA0AAowUAIcUDAQCzBQAhxgMgAPwHACHHAwEAswUAIcgDQACjBQAhyQNAAKMFACECAAAADwAgLwAAgAEAIAIAAAAPACAvAACAAQAgAwAAAAEAIDYAAHkAIDcAAH4AIAEAAAABACABAAAADwAgFAwAAPUHACA8AAD2BwAgPQAA-QcAID4AAPgHACA_AAD3BwAg8AIAAJ0FACD4AgAAnQUAIIYDAACdBQAghwMAAJ0FACC5AwAAnQUAILsDAACdBQAgwQMAAJ0FACDCAwAAnQUAIMMDAACdBQAgxAMAAJ0FACDFAwAAnQUAIMYDAACdBQAgxwMAAJ0FACDIAwAAnQUAIMkDAACdBQAgGdICAADWBAAw0wIAAIcBABDUAgAA1gQAMNUCAQCWBAAh2wJAAJgEACHwAgIApQQAIfYCQACYBAAh-AJAAJkEACGGAwEApgQAIYcDAQCmBAAhuQMBAKYEACG6AyAAzQQAIbsDAQCmBAAhvQMAANcEvQMivwMAANgEvwMiwAMgAM0EACHBAwEApgQAIcIDAQCmBAAhwwMAALEEACDEA0AAmQQAIcUDAQCmBAAhxgMgANkEACHHAwEApgQAIcgDQACZBAAhyQNAAJkEACEDAAAADwAgAQAAhgEAMDsAAIcBACADAAAADwAgAQAAdQAwAgAAAQAgAQAAAAUAIAEAAAAFACADAAAAAwAgAQAABAAwAgAABQAgAwAAAAMAIAEAAAQAMAIAAAUAIAMAAAADACABAAAEADACAAAFACAOAwAA9AcAINUCAQAAAAHbAkAAAAAB7wIBAAAAAfYCQAAAAAGwAwEAAAABsQMBAAAAAbIDAQAAAAGzAwEAAAABtANAAAAAAbUDQAAAAAG2AwEAAAABtwMBAAAAAbgDAQAAAAEBLwAAjwEAIA3VAgEAAAAB2wJAAAAAAe8CAQAAAAH2AkAAAAABsAMBAAAAAbEDAQAAAAGyAwEAAAABswMBAAAAAbQDQAAAAAG1A0AAAAABtgMBAAAAAbcDAQAAAAG4AwEAAAABAS8AAJEBADABLwAAkQEAMA4DAADzBwAg1QIBAKEFACHbAkAAogUAIe8CAQChBQAh9gJAAKIFACGwAwEAoQUAIbEDAQChBQAhsgMBALMFACGzAwEAswUAIbQDQACjBQAhtQNAAKMFACG2AwEAswUAIbcDAQCzBQAhuAMBALMFACECAAAABQAgLwAAlAEAIA3VAgEAoQUAIdsCQACiBQAh7wIBAKEFACH2AkAAogUAIbADAQChBQAhsQMBAKEFACGyAwEAswUAIbMDAQCzBQAhtANAAKMFACG1A0AAowUAIbYDAQCzBQAhtwMBALMFACG4AwEAswUAIQIAAAADACAvAACWAQAgAgAAAAMAIC8AAJYBACADAAAABQAgNgAAjwEAIDcAAJQBACABAAAABQAgAQAAAAMAIAoMAADwBwAgPgAA8gcAID8AAPEHACCyAwAAnQUAILMDAACdBQAgtAMAAJ0FACC1AwAAnQUAILYDAACdBQAgtwMAAJ0FACC4AwAAnQUAIBDSAgAA1QQAMNMCAACdAQAQ1AIAANUEADDVAgEAlgQAIdsCQACYBAAh7wIBAJYEACH2AkAAmAQAIbADAQCWBAAhsQMBAJYEACGyAwEApgQAIbMDAQCmBAAhtANAAJkEACG1A0AAmQQAIbYDAQCmBAAhtwMBAKYEACG4AwEApgQAIQMAAAADACABAACcAQAwOwAAnQEAIAMAAAADACABAAAEADACAAAFACABAAAACQAgAQAAAAkAIAMAAAAHACABAAAIADACAAAJACADAAAABwAgAQAACAAwAgAACQAgAwAAAAcAIAEAAAgAMAIAAAkAIAoDAADvBwAg1QIBAAAAAdsCQAAAAAHvAgEAAAAB9gJAAAAAAf4CAQAAAAGsA0AAAAABrQMBAAAAAa4DAQAAAAGvAwEAAAABAS8AAKUBACAJ1QIBAAAAAdsCQAAAAAHvAgEAAAAB9gJAAAAAAf4CAQAAAAGsA0AAAAABrQMBAAAAAa4DAQAAAAGvAwEAAAABAS8AAKcBADABLwAApwEAMAoDAADuBwAg1QIBAKEFACHbAkAAogUAIe8CAQChBQAh9gJAAKIFACH-AgEAswUAIawDQACiBQAhrQMBAKEFACGuAwEAswUAIa8DAQCzBQAhAgAAAAkAIC8AAKoBACAJ1QIBAKEFACHbAkAAogUAIe8CAQChBQAh9gJAAKIFACH-AgEAswUAIawDQACiBQAhrQMBAKEFACGuAwEAswUAIa8DAQCzBQAhAgAAAAcAIC8AAKwBACACAAAABwAgLwAArAEAIAMAAAAJACA2AAClAQAgNwAAqgEAIAEAAAAJACABAAAABwAgBgwAAOsHACA-AADtBwAgPwAA7AcAIP4CAACdBQAgrgMAAJ0FACCvAwAAnQUAIAzSAgAA1AQAMNMCAACzAQAQ1AIAANQEADDVAgEAlgQAIdsCQACYBAAh7wIBAJYEACH2AkAAmAQAIf4CAQCmBAAhrANAAJgEACGtAwEAlgQAIa4DAQCmBAAhrwMBAKYEACEDAAAABwAgAQAAsgEAMDsAALMBACADAAAABwAgAQAACAAwAgAACQAgAQAAAA0AIAEAAAANACADAAAACwAgAQAADAAwAgAADQAgAwAAAAsAIAEAAAwAMAIAAA0AIAMAAAALACABAAAMADACAAANACAIAwAA6gcAINUCAQAAAAHbAkAAAAAB7wIBAAAAAfYCQAAAAAGqAwEAAAABqwMBAAAAAawDQAAAAAEBLwAAuwEAIAfVAgEAAAAB2wJAAAAAAe8CAQAAAAH2AkAAAAABqgMBAAAAAasDAQAAAAGsA0AAAAABAS8AAL0BADABLwAAvQEAMAEAAAAPACAIAwAA6QcAINUCAQChBQAh2wJAAKMFACHvAgEAswUAIfYCQACjBQAhqgMBAKEFACGrAwEAoQUAIawDQACiBQAhAgAAAA0AIC8AAMEBACAH1QIBAKEFACHbAkAAowUAIe8CAQCzBQAh9gJAAKMFACGqAwEAoQUAIasDAQChBQAhrANAAKIFACECAAAACwAgLwAAwwEAIAIAAAALACAvAADDAQAgAQAAAA8AIAMAAAANACA2AAC7AQAgNwAAwQEAIAEAAAANACABAAAACwAgBgwAAOYHACA-AADoBwAgPwAA5wcAINsCAACdBQAg7wIAAJ0FACD2AgAAnQUAIArSAgAA0wQAMNMCAADLAQAQ1AIAANMEADDVAgEAlgQAIdsCQACZBAAh7wIBAKYEACH2AkAAmQQAIaoDAQCWBAAhqwMBAJYEACGsA0AAmAQAIQMAAAALACABAADKAQAwOwAAywEAIAMAAAALACABAAAMADACAAANACABAAAAZgAgAQAAAGYAIAMAAABkACABAABlADACAABmACADAAAAZAAgAQAAZQAwAgAAZgAgAwAAAGQAIAEAAGUAMAIAAGYAIAwDAADlBwAg1QIBAAAAAdsCQAAAAAHvAgEAAAABhwMBAAAAAaMDAQAAAAGkAwEAAAABpQMCAAAAAaYDAQAAAAGnAyAAAAABqAMBAAAAAakDAQAAAAEBLwAA0wEAIAvVAgEAAAAB2wJAAAAAAe8CAQAAAAGHAwEAAAABowMBAAAAAaQDAQAAAAGlAwIAAAABpgMBAAAAAacDIAAAAAGoAwEAAAABqQMBAAAAAQEvAADVAQAwAS8AANUBADAMAwAA5AcAINUCAQChBQAh2wJAAKMFACHvAgEAoQUAIYcDAQCzBQAhowMBAKEFACGkAwEAoQUAIaUDAgC0BQAhpgMBAKEFACGnAyAA0gYAIagDAQCzBQAhqQMBALMFACECAAAAZgAgLwAA2AEAIAvVAgEAoQUAIdsCQACjBQAh7wIBAKEFACGHAwEAswUAIaMDAQChBQAhpAMBAKEFACGlAwIAtAUAIaYDAQChBQAhpwMgANIGACGoAwEAswUAIakDAQCzBQAhAgAAAGQAIC8AANoBACACAAAAZAAgLwAA2gEAIAMAAABmACA2AADTAQAgNwAA2AEAIAEAAABmACABAAAAZAAgCQwAAN8HACA8AADgBwAgPQAA4wcAID4AAOIHACA_AADhBwAg2wIAAJ0FACCHAwAAnQUAIKgDAACdBQAgqQMAAJ0FACAO0gIAANIEADDTAgAA4QEAENQCAADSBAAw1QIBAJYEACHbAkAAmQQAIe8CAQCWBAAhhwMBAKYEACGjAwEAlgQAIaQDAQCWBAAhpQMCAKcEACGmAwEAlgQAIacDIADNBAAhqAMBAKYEACGpAwEApgQAIQMAAABkACABAADgAQAwOwAA4QEAIAMAAABkACABAABlADACAABmACABAAAAGgAgAQAAABoAIAMAAAAXACABAAAZADACAAAaACADAAAAFwAgAQAAGQAwAgAAGgAgAwAAABcAIAEAABkAMAIAABoAIBsRAADOBwAgEgAAvwcAIBMAAMAHACAUAADBBwAgFQAAwgcAIBYAAMMHACAYAADEBwAgGQAAxQcAIBoAAMYHACAbAADHBwAgHAAAyAcAICAAAMkHACDVAgEAAAAB2wJAAAAAAfACAgAAAAH2AkAAAAAB-AJAAAAAAZgDAgAAAAGZAwEAAAABmgMCAAAAAZwDAAAAnAMCnQMgAAAAAZ4DIAAAAAGfAwIAAAABoAMCAAAAAaEDAgAAAAGiAwIAAAABAS8AAOkBACAP1QIBAAAAAdsCQAAAAAHwAgIAAAAB9gJAAAAAAfgCQAAAAAGYAwIAAAABmQMBAAAAAZoDAgAAAAGcAwAAAJwDAp0DIAAAAAGeAyAAAAABnwMCAAAAAaADAgAAAAGhAwIAAAABogMCAAAAAQEvAADrAQAwAS8AAOsBADABAAAAFQAgAQAAABcAIAEAAAAVACAbEQAA1AYAIBIAANUGACATAADWBgAgFAAA1AcAIBUAANcGACAWAADYBgAgGAAA2QYAIBkAANoGACAaAADbBgAgGwAA3AYAIBwAAN0GACAgAADeBgAg1QIBAKEFACHbAkAAogUAIfACAgCyBQAh9gJAAKIFACH4AkAAowUAIZgDAgCyBQAhmQMBALMFACGaAwIAsgUAIZwDAADRBpwDIp0DIADSBgAhngMgANIGACGfAwIAtAUAIaADAgC0BQAhoQMCALQFACGiAwIAtAUAIQIAAAAaACAvAADxAQAgD9UCAQChBQAh2wJAAKIFACHwAgIAsgUAIfYCQACiBQAh-AJAAKMFACGYAwIAsgUAIZkDAQCzBQAhmgMCALIFACGcAwAA0QacAyKdAyAA0gYAIZ4DIADSBgAhnwMCALQFACGgAwIAtAUAIaEDAgC0BQAhogMCALQFACECAAAAFwAgLwAA8wEAIAIAAAAXACAvAADzAQAgAQAAABUAIAEAAAAXACABAAAAFQAgAwAAABoAIDYAAOkBACA3AADxAQAgAQAAABoAIAEAAAAXACAKDAAA2gcAIDwAANsHACA9AADeBwAgPgAA3QcAID8AANwHACDwAgAAnQUAIPgCAACdBQAgmAMAAJ0FACCZAwAAnQUAIJoDAACdBQAgEtICAADLBAAw0wIAAP0BABDUAgAAywQAMNUCAQCWBAAh2wJAAJgEACHwAgIApQQAIfYCQACYBAAh-AJAAJkEACGYAwIApQQAIZkDAQCmBAAhmgMCAKUEACGcAwAAzAScAyKdAyAAzQQAIZ4DIADNBAAhnwMCAKcEACGgAwIApwQAIaEDAgCnBAAhogMCAKcEACEDAAAAFwAgAQAA_AEAMDsAAP0BACADAAAAFwAgAQAAGQAwAgAAGgAgAQAAAC0AIAEAAAAtACADAAAAFQAgAQAALAAwAgAALQAgAwAAABUAIAEAACwAMAIAAC0AIAMAAAAVACABAAAsADACAAAtACAQBwAA2QcAIAgAALkHACAJAAC6BwAgDgAAuwcAIBAAALwHACDbAkAAAAAB8gIBAAAAAfMCAgAAAAGQAwEAAAABkQMBAAAAAZIDAQAAAAGTAwEAAAABlAMBAAAAAZUDAQAAAAGWAwEAAAABlwNAAAAAAQEvAACFAgAgC9sCQAAAAAHyAgEAAAAB8wICAAAAAZADAQAAAAGRAwEAAAABkgMBAAAAAZMDAQAAAAGUAwEAAAABlQMBAAAAAZYDAQAAAAGXA0AAAAABAS8AAIcCADABLwAAhwIAMBAHAACtBgAgCAAArgYAIAkAAK8GACAOAACwBgAgEAAAsQYAINsCQACiBQAh8gIBAKEFACHzAgIAtAUAIZADAQChBQAhkQMBALMFACGSAwEAswUAIZMDAQCzBQAhlAMBAKEFACGVAwEAswUAIZYDAQCzBQAhlwNAAKMFACECAAAALQAgLwAAigIAIAvbAkAAogUAIfICAQChBQAh8wICALQFACGQAwEAoQUAIZEDAQCzBQAhkgMBALMFACGTAwEAswUAIZQDAQChBQAhlQMBALMFACGWAwEAswUAIZcDQACjBQAhAgAAABUAIC8AAIwCACACAAAAFQAgLwAAjAIAIAMAAAAtACA2AACFAgAgNwAAigIAIAEAAAAtACABAAAAFQAgCwwAAKgGACA8AACpBgAgPQAArAYAID4AAKsGACA_AACqBgAgkQMAAJ0FACCSAwAAnQUAIJMDAACdBQAglQMAAJ0FACCWAwAAnQUAIJcDAACdBQAgDtICAADKBAAw0wIAAJMCABDUAgAAygQAMNsCQACYBAAh8gIBAJYEACHzAgIApwQAIZADAQCWBAAhkQMBAKYEACGSAwEApgQAIZMDAQCmBAAhlAMBAJYEACGVAwEApgQAIZYDAQCmBAAhlwNAAJkEACEDAAAAFQAgAQAAkgIAMDsAAJMCACADAAAAFQAgAQAALAAwAgAALQAgAQAAAB4AIAEAAAAeACADAAAAHAAgAQAAHQAwAgAAHgAgAwAAABwAIAEAAB0AMAIAAB4AIAMAAAAcACABAAAdADACAAAeACAGCgAAlAYAIA0AAKcGACDbAkAAAAAB8gIBAAAAAfMCAgAAAAGPAwEAAAABAS8AAJsCACAE2wJAAAAAAfICAQAAAAHzAgIAAAABjwMBAAAAAQEvAACdAgAwAS8AAJ0CADAGCgAAkgYAIA0AAKYGACDbAkAAogUAIfICAQChBQAh8wICALQFACGPAwEAoQUAIQIAAAAeACAvAACgAgAgBNsCQACiBQAh8gIBAKEFACHzAgIAtAUAIY8DAQChBQAhAgAAABwAIC8AAKICACACAAAAHAAgLwAAogIAIAMAAAAeACA2AACbAgAgNwAAoAIAIAEAAAAeACABAAAAHAAgBQwAAKEGACA8AACiBgAgPQAApQYAID4AAKQGACA_AACjBgAgB9ICAADJBAAw0wIAAKkCABDUAgAAyQQAMNsCQACYBAAh8gIBAJYEACHzAgIApwQAIY8DAQCWBAAhAwAAABwAIAEAAKgCADA7AACpAgAgAwAAABwAIAEAAB0AMAIAAB4AIAEAAAAkACABAAAAJAAgAwAAACIAIAEAACMAMAIAACQAIAMAAAAiACABAAAjADACAAAkACADAAAAIgAgAQAAIwAwAgAAJAAgCAcAAJ8GACAPAACgBgAg1QIBAAAAAdsCQAAAAAHyAgEAAAAB-wIAAACPAwKMAwIAAAABjQMBAAAAAQEvAACxAgAgBtUCAQAAAAHbAkAAAAAB8gIBAAAAAfsCAAAAjwMCjAMCAAAAAY0DAQAAAAEBLwAAswIAMAEvAACzAgAwCAcAAJ0GACAPAACeBgAg1QIBAKEFACHbAkAAogUAIfICAQChBQAh-wIAAJwGjwMijAMCALQFACGNAwEAoQUAIQIAAAAkACAvAAC2AgAgBtUCAQChBQAh2wJAAKIFACHyAgEAoQUAIfsCAACcBo8DIowDAgC0BQAhjQMBAKEFACECAAAAIgAgLwAAuAIAIAIAAAAiACAvAAC4AgAgAwAAACQAIDYAALECACA3AAC2AgAgAQAAACQAIAEAAAAiACAFDAAAlwYAIDwAAJgGACA9AACbBgAgPgAAmgYAID8AAJkGACAJ0gIAAMUEADDTAgAAvwIAENQCAADFBAAw1QIBAJYEACHbAkAAmAQAIfICAQCWBAAh-wIAAMYEjwMijAMCAKcEACGNAwEAlgQAIQMAAAAiACABAAC-AgAwOwAAvwIAIAMAAAAiACABAAAjADACAAAkACAJCwAAxAQAINICAAC_BAAw0wIAAMUCABDUAgAAvwQAMNUCAQAAAAHbAkAAwwQAIfACAgAAAAGHAwEAAAABiAMBAMIEACEBAAAAwgIAIAEAAADCAgAgCQsAAMQEACDSAgAAvwQAMNMCAADFAgAQ1AIAAL8EADDVAgEAwAQAIdsCQADDBAAh8AICAMEEACGHAwEAwAQAIYgDAQDCBAAhAwsAAJYGACDwAgAAnQUAIIgDAACdBQAgAwAAAMUCACABAADGAgAwAgAAwgIAIAMAAADFAgAgAQAAxgIAMAIAAMICACADAAAAxQIAIAEAAMYCADACAADCAgAgBgsAAJUGACDVAgEAAAAB2wJAAAAAAfACAgAAAAGHAwEAAAABiAMBAAAAAQEvAADKAgAgBdUCAQAAAAHbAkAAAAAB8AICAAAAAYcDAQAAAAGIAwEAAAABAS8AAMwCADABLwAAzAIAMAYLAACGBgAg1QIBAKEFACHbAkAAogUAIfACAgCyBQAhhwMBAKEFACGIAwEAswUAIQIAAADCAgAgLwAAzwIAIAXVAgEAoQUAIdsCQACiBQAh8AICALIFACGHAwEAoQUAIYgDAQCzBQAhAgAAAMUCACAvAADRAgAgAgAAAMUCACAvAADRAgAgAwAAAMICACA2AADKAgAgNwAAzwIAIAEAAADCAgAgAQAAAMUCACAHDAAAgQYAIDwAAIIGACA9AACFBgAgPgAAhAYAID8AAIMGACDwAgAAnQUAIIgDAACdBQAgCNICAAC-BAAw0wIAANgCABDUAgAAvgQAMNUCAQCWBAAh2wJAAJgEACHwAgIApQQAIYcDAQCWBAAhiAMBAKYEACEDAAAAxQIAIAEAANcCADA7AADYAgAgAwAAAMUCACABAADGAgAwAgAAwgIAIAEAAAATACABAAAAEwAgAwAAABEAIAEAABIAMAIAABMAIAMAAAARACABAAASADACAAATACADAAAAEQAgAQAAEgAwAgAAEwAgBgMAAIAGACAHAAD_BQAg2wJAAAAAAe8CAQAAAAHyAgEAAAABhgMAAACGAwIBLwAA4AIAIATbAkAAAAAB7wIBAAAAAfICAQAAAAGGAwAAAIYDAgEvAADiAgAwAS8AAOICADAGAwAA_gUAIAcAAP0FACDbAkAAogUAIe8CAQChBQAh8gIBAKEFACGGAwAA_AWGAyICAAAAEwAgLwAA5QIAIATbAkAAogUAIe8CAQChBQAh8gIBAKEFACGGAwAA_AWGAyICAAAAEQAgLwAA5wIAIAIAAAARACAvAADnAgAgAwAAABMAIDYAAOACACA3AADlAgAgAQAAABMAIAEAAAARACADDAAA-QUAID4AAPsFACA_AAD6BQAgB9ICAAC6BAAw0wIAAO4CABDUAgAAugQAMNsCQACYBAAh7wIBAJYEACHyAgEAlgQAIYYDAAC7BIYDIgMAAAARACABAADtAgAwOwAA7gIAIAMAAAARACABAAASADACAAATACABAAAAMgAgAQAAADIAIAMAAAAwACABAAAxADACAAAyACADAAAAMAAgAQAAMQAwAgAAMgAgAwAAADAAIAEAADEAMAIAADIAIAcHAAD3BQAgFwAA-AUAINUCAQAAAAHbAkAAAAAB8gIBAAAAAYIDAQAAAAGEAwAAAIQDAgEvAAD2AgAgBdUCAQAAAAHbAkAAAAAB8gIBAAAAAYIDAQAAAAGEAwAAAIQDAgEvAAD4AgAwAS8AAPgCADABAAAADwAgBwcAAPUFACAXAAD2BQAg1QIBAKEFACHbAkAAogUAIfICAQChBQAhggMBALMFACGEAwAA9AWEAyICAAAAMgAgLwAA_AIAIAXVAgEAoQUAIdsCQACiBQAh8gIBAKEFACGCAwEAswUAIYQDAAD0BYQDIgIAAAAwACAvAAD-AgAgAgAAADAAIC8AAP4CACABAAAADwAgAwAAADIAIDYAAPYCACA3AAD8AgAgAQAAADIAIAEAAAAwACAEDAAA8QUAID4AAPMFACA_AADyBQAgggMAAJ0FACAI0gIAALYEADDTAgAAhgMAENQCAAC2BAAw1QIBAJYEACHbAkAAmAQAIfICAQCWBAAhggMBAKYEACGEAwAAtwSEAyIDAAAAMAAgAQAAhQMAMDsAAIYDACADAAAAMAAgAQAAMQAwAgAAMgAgAQAAADgAIAEAAAA4ACADAAAANgAgAQAANwAwAgAAOAAgAwAAADYAIAEAADcAMAIAADgAIAMAAAA2ACABAAA3ADACAAA4ACAFAwAA8AUAIAcAAO8FACDbAkAAAAAB7wIBAAAAAfICAQAAAAEBLwAAjgMAIAPbAkAAAAAB7wIBAAAAAfICAQAAAAEBLwAAkAMAMAEvAACQAwAwBQMAAO4FACAHAADtBQAg2wJAAKIFACHvAgEAoQUAIfICAQChBQAhAgAAADgAIC8AAJMDACAD2wJAAKIFACHvAgEAoQUAIfICAQChBQAhAgAAADYAIC8AAJUDACACAAAANgAgLwAAlQMAIAMAAAA4ACA2AACOAwAgNwAAkwMAIAEAAAA4ACABAAAANgAgAwwAAOoFACA-AADsBQAgPwAA6wUAIAbSAgAAtQQAMNMCAACcAwAQ1AIAALUEADDbAkAAmAQAIe8CAQCWBAAh8gIBAJYEACEDAAAANgAgAQAAmwMAMDsAAJwDACADAAAANgAgAQAANwAwAgAAOAAgAQAAADwAIAEAAAA8ACADAAAAOgAgAQAAOwAwAgAAPAAgAwAAADoAIAEAADsAMAIAADwAIAMAAAA6ACABAAA7ADACAAA8ACAOAwAA6QUAIAcAAOgFACDVAgEAAAAB2wJAAAAAAe8CAQAAAAHyAgEAAAAB8wICAAAAAfsCAAAA-wIC_AIBAAAAAf0CAQAAAAH-AgEAAAAB_wIBAAAAAYADgAAAAAGBAwEAAAABAS8AAKQDACAM1QIBAAAAAdsCQAAAAAHvAgEAAAAB8gIBAAAAAfMCAgAAAAH7AgAAAPsCAvwCAQAAAAH9AgEAAAAB_gIBAAAAAf8CAQAAAAGAA4AAAAABgQMBAAAAAQEvAACmAwAwAS8AAKYDADABAAAADwAgDgMAAOcFACAHAADmBQAg1QIBAKEFACHbAkAAogUAIe8CAQCzBQAh8gIBAKEFACHzAgIAsgUAIfsCAADlBfsCIvwCAQCzBQAh_QIBALMFACH-AgEAswUAIf8CAQCzBQAhgAOAAAAAAYEDAQCzBQAhAgAAADwAIC8AAKoDACAM1QIBAKEFACHbAkAAogUAIe8CAQCzBQAh8gIBAKEFACHzAgIAsgUAIfsCAADlBfsCIvwCAQCzBQAh_QIBALMFACH-AgEAswUAIf8CAQCzBQAhgAOAAAAAAYEDAQCzBQAhAgAAADoAIC8AAKwDACACAAAAOgAgLwAArAMAIAEAAAAPACADAAAAPAAgNgAApAMAIDcAAKoDACABAAAAPAAgAQAAADoAIA0MAADgBQAgPAAA4QUAID0AAOQFACA-AADjBQAgPwAA4gUAIO8CAACdBQAg8wIAAJ0FACD8AgAAnQUAIP0CAACdBQAg_gIAAJ0FACD_AgAAnQUAIIADAACdBQAggQMAAJ0FACAP0gIAAK8EADDTAgAAtAMAENQCAACvBAAw1QIBAJYEACHbAkAAmAQAIe8CAQCmBAAh8gIBAJYEACHzAgIApQQAIfsCAACwBPsCIvwCAQCmBAAh_QIBAKYEACH-AgEApgQAIf8CAQCmBAAhgAMAALEEACCBAwEApgQAIQMAAAA6ACABAACzAwAwOwAAtAMAIAMAAAA6ACABAAA7ADACAAA8ACABAAAAQQAgAQAAAEEAIAMAAAA_ACABAABAADACAABBACADAAAAPwAgAQAAQAAwAgAAQQAgAwAAAD8AIAEAAEAAMAIAAEEAIAkDAADeBQAgBwAA3wUAIC-AAAAAAdUCAQAAAAHbAkAAAAAB7wIBAAAAAfICAQAAAAH2AkAAAAAB-QICAAAAAQEvAAC8AwAgBy-AAAAAAdUCAQAAAAHbAkAAAAAB7wIBAAAAAfICAQAAAAH2AkAAAAAB-QICAAAAAQEvAAC-AwAwAS8AAL4DADABAAAAFwAgCQMAANwFACAHAADdBQAgL4AAAAAB1QIBAKEFACHbAkAAogUAIe8CAQChBQAh8gIBALMFACH2AkAAogUAIfkCAgC0BQAhAgAAAEEAIC8AAMIDACAHL4AAAAAB1QIBAKEFACHbAkAAogUAIe8CAQChBQAh8gIBALMFACH2AkAAogUAIfkCAgC0BQAhAgAAAD8AIC8AAMQDACACAAAAPwAgLwAAxAMAIAEAAAAXACADAAAAQQAgNgAAvAMAIDcAAMIDACABAAAAQQAgAQAAAD8AIAYMAADXBQAgPAAA2AUAID0AANsFACA-AADaBQAgPwAA2QUAIPICAACdBQAgCi8AAJcEACDSAgAArgQAMNMCAADMAwAQ1AIAAK4EADDVAgEAlgQAIdsCQACYBAAh7wIBAJYEACHyAgEApgQAIfYCQACYBAAh-QICAKcEACEDAAAAPwAgAQAAywMAMDsAAMwDACADAAAAPwAgAQAAQAAwAgAAQQAgAQAAAEYAIAEAAABGACADAAAARAAgAQAARQAwAgAARgAgAwAAAEQAIAEAAEUAMAIAAEYAIAMAAABEACABAABFADACAABGACARAwAA0wUAIAcAANIFACAaAADVBQAgHQAA1gUAIB4AANQFACDVAgEAAAAB2wJAAAAAAe8CAQAAAAHwAgIAAAAB8QIBAAAAAfICAQAAAAHzAgIAAAAB9AIBAAAAAfUCAgAAAAH2AkAAAAAB9wJAAAAAAfgCQAAAAAEBLwAA1AMAIAzVAgEAAAAB2wJAAAAAAe8CAQAAAAHwAgIAAAAB8QIBAAAAAfICAQAAAAHzAgIAAAAB9AIBAAAAAfUCAgAAAAH2AkAAAAAB9wJAAAAAAfgCQAAAAAEBLwAA1gMAMAEvAADWAwAwAQAAAA8AIAEAAABEACARAwAAtgUAIAcAALUFACAaAAC5BQAgHQAAtwUAIB4AALgFACDVAgEAoQUAIdsCQACiBQAh7wIBALMFACHwAgIAsgUAIfECAQCzBQAh8gIBAKEFACHzAgIAsgUAIfQCAQCzBQAh9QICALQFACH2AkAAogUAIfcCQACjBQAh-AJAAKMFACECAAAARgAgLwAA2wMAIAzVAgEAoQUAIdsCQACiBQAh7wIBALMFACHwAgIAsgUAIfECAQCzBQAh8gIBAKEFACHzAgIAsgUAIfQCAQCzBQAh9QICALQFACH2AkAAogUAIfcCQACjBQAh-AJAAKMFACECAAAARAAgLwAA3QMAIAIAAABEACAvAADdAwAgAQAAAA8AIAEAAABEACADAAAARgAgNgAA1AMAIDcAANsDACABAAAARgAgAQAAAEQAIAwMAACtBQAgPAAArgUAID0AALEFACA-AACwBQAgPwAArwUAIO8CAACdBQAg8AIAAJ0FACDxAgAAnQUAIPMCAACdBQAg9AIAAJ0FACD3AgAAnQUAIPgCAACdBQAgD9ICAACkBAAw0wIAAOYDABDUAgAApAQAMNUCAQCWBAAh2wJAAJgEACHvAgEApgQAIfACAgClBAAh8QIBAKYEACHyAgEAlgQAIfMCAgClBAAh9AIBAKYEACH1AgIApwQAIfYCQACYBAAh9wJAAJkEACH4AkAAmQQAIQMAAABEACABAADlAwAwOwAA5gMAIAMAAABEACABAABFADACAABGACABAAAATQAgAQAAAE0AIAMAAABLACABAABMADACAABNACADAAAASwAgAQAATAAwAgAATQAgAwAAAEsAIAEAAEwAMAIAAE0AIAUDAACsBQAgHwAAqwUAINsCQAAAAAHuAgEAAAAB7wIBAAAAAQEvAADuAwAgA9sCQAAAAAHuAgEAAAAB7wIBAAAAAQEvAADwAwAwAS8AAPADADAFAwAAqgUAIB8AAKkFACDbAkAAogUAIe4CAQChBQAh7wIBAKEFACECAAAATQAgLwAA8wMAIAPbAkAAogUAIe4CAQChBQAh7wIBAKEFACECAAAASwAgLwAA9QMAIAIAAABLACAvAAD1AwAgAwAAAE0AIDYAAO4DACA3AADzAwAgAQAAAE0AIAEAAABLACADDAAApgUAID4AAKgFACA_AACnBQAgBtICAACjBAAw0wIAAPwDABDUAgAAowQAMNsCQACYBAAh7gIBAJYEACHvAgEAlgQAIQMAAABLACABAAD7AwAwOwAA_AMAIAMAAABLACABAABMADACAABNACABAAAAXQAgAQAAAF0AIAMAAABbACABAABcADACAABdACADAAAAWwAgAQAAXAAwAgAAXQAgAwAAAFsAIAEAAFwAMAIAAF0AIAkjAAClBQAg1QIBAAAAAdYCAQAAAAHXAgEAAAAB2AIBAAAAAdkCAQAAAAHaAoAAAAAB2wJAAAAAAdwCQAAAAAEBLwAAhAQAIAjVAgEAAAAB1gIBAAAAAdcCAQAAAAHYAgEAAAAB2QIBAAAAAdoCgAAAAAHbAkAAAAAB3AJAAAAAAQEvAACGBAAwAS8AAIYEADAJIwAApAUAINUCAQChBQAh1gIBAKEFACHXAgEAoQUAIdgCAQChBQAh2QIBAKEFACHaAoAAAAAB2wJAAKIFACHcAkAAowUAIQIAAABdACAvAACJBAAgCNUCAQChBQAh1gIBAKEFACHXAgEAoQUAIdgCAQChBQAh2QIBAKEFACHaAoAAAAAB2wJAAKIFACHcAkAAowUAIQIAAABbACAvAACLBAAgAgAAAFsAIC8AAIsEACADAAAAXQAgNgAAhAQAIDcAAIkEACABAAAAXQAgAQAAAFsAIAQMAACeBQAgPgAAoAUAID8AAJ8FACDcAgAAnQUAIAvSAgAAlQQAMNMCAACSBAAQ1AIAAJUEADDVAgEAlgQAIdYCAQCWBAAh1wIBAJYEACHYAgEAlgQAIdkCAQCWBAAh2gIAAJcEACDbAkAAmAQAIdwCQACZBAAhAwAAAFsAIAEAAJEEADA7AACSBAAgAwAAAFsAIAEAAFwAMAIAAF0AIAvSAgAAlQQAMNMCAACSBAAQ1AIAAJUEADDVAgEAlgQAIdYCAQCWBAAh1wIBAJYEACHYAgEAlgQAIdkCAQCWBAAh2gIAAJcEACDbAkAAmAQAIdwCQACZBAAhDgwAAJ4EACA-AACiBAAgPwAAogQAIN0CAQAAAAHeAgEAAAAE3wIBAAAABOACAQAAAAHhAgEAAAAB4gIBAAAAAeMCAQAAAAHkAgEAoQQAIesCAQAAAAHsAgEAAAAB7QIBAAAAAQ8MAACeBAAgPgAAoAQAID8AAKAEACDdAoAAAAAB4AKAAAAAAeECgAAAAAHiAoAAAAAB4wKAAAAAAeQCgAAAAAHlAgEAAAAB5gIBAAAAAecCAQAAAAHoAoAAAAAB6QKAAAAAAeoCgAAAAAELDAAAngQAID4AAJ8EACA_AACfBAAg3QJAAAAAAd4CQAAAAATfAkAAAAAE4AJAAAAAAeECQAAAAAHiAkAAAAAB4wJAAAAAAeQCQACdBAAhCwwAAJsEACA-AACcBAAgPwAAnAQAIN0CQAAAAAHeAkAAAAAF3wJAAAAABeACQAAAAAHhAkAAAAAB4gJAAAAAAeMCQAAAAAHkAkAAmgQAIQsMAACbBAAgPgAAnAQAID8AAJwEACDdAkAAAAAB3gJAAAAABd8CQAAAAAXgAkAAAAAB4QJAAAAAAeICQAAAAAHjAkAAAAAB5AJAAJoEACEI3QICAAAAAd4CAgAAAAXfAgIAAAAF4AICAAAAAeECAgAAAAHiAgIAAAAB4wICAAAAAeQCAgCbBAAhCN0CQAAAAAHeAkAAAAAF3wJAAAAABeACQAAAAAHhAkAAAAAB4gJAAAAAAeMCQAAAAAHkAkAAnAQAIQsMAACeBAAgPgAAnwQAID8AAJ8EACDdAkAAAAAB3gJAAAAABN8CQAAAAATgAkAAAAAB4QJAAAAAAeICQAAAAAHjAkAAAAAB5AJAAJ0EACEI3QICAAAAAd4CAgAAAATfAgIAAAAE4AICAAAAAeECAgAAAAHiAgIAAAAB4wICAAAAAeQCAgCeBAAhCN0CQAAAAAHeAkAAAAAE3wJAAAAABOACQAAAAAHhAkAAAAAB4gJAAAAAAeMCQAAAAAHkAkAAnwQAIQzdAoAAAAAB4AKAAAAAAeECgAAAAAHiAoAAAAAB4wKAAAAAAeQCgAAAAAHlAgEAAAAB5gIBAAAAAecCAQAAAAHoAoAAAAAB6QKAAAAAAeoCgAAAAAEODAAAngQAID4AAKIEACA_AACiBAAg3QIBAAAAAd4CAQAAAATfAgEAAAAE4AIBAAAAAeECAQAAAAHiAgEAAAAB4wIBAAAAAeQCAQChBAAh6wIBAAAAAewCAQAAAAHtAgEAAAABC90CAQAAAAHeAgEAAAAE3wIBAAAABOACAQAAAAHhAgEAAAAB4gIBAAAAAeMCAQAAAAHkAgEAogQAIesCAQAAAAHsAgEAAAAB7QIBAAAAAQbSAgAAowQAMNMCAAD8AwAQ1AIAAKMEADDbAkAAmAQAIe4CAQCWBAAh7wIBAJYEACEP0gIAAKQEADDTAgAA5gMAENQCAACkBAAw1QIBAJYEACHbAkAAmAQAIe8CAQCmBAAh8AICAKUEACHxAgEApgQAIfICAQCWBAAh8wICAKUEACH0AgEApgQAIfUCAgCnBAAh9gJAAJgEACH3AkAAmQQAIfgCQACZBAAhDQwAAJsEACA8AACtBAAgPQAAmwQAID4AAJsEACA_AACbBAAg3QICAAAAAd4CAgAAAAXfAgIAAAAF4AICAAAAAeECAgAAAAHiAgIAAAAB4wICAAAAAeQCAgCsBAAhDgwAAJsEACA-AACrBAAgPwAAqwQAIN0CAQAAAAHeAgEAAAAF3wIBAAAABeACAQAAAAHhAgEAAAAB4gIBAAAAAeMCAQAAAAHkAgEAqgQAIesCAQAAAAHsAgEAAAAB7QIBAAAAAQ0MAACeBAAgPAAAqQQAID0AAJ4EACA-AACeBAAgPwAAngQAIN0CAgAAAAHeAgIAAAAE3wICAAAABOACAgAAAAHhAgIAAAAB4gICAAAAAeMCAgAAAAHkAgIAqAQAIQ0MAACeBAAgPAAAqQQAID0AAJ4EACA-AACeBAAgPwAAngQAIN0CAgAAAAHeAgIAAAAE3wICAAAABOACAgAAAAHhAgIAAAAB4gICAAAAAeMCAgAAAAHkAgIAqAQAIQjdAggAAAAB3gIIAAAABN8CCAAAAATgAggAAAAB4QIIAAAAAeICCAAAAAHjAggAAAAB5AIIAKkEACEODAAAmwQAID4AAKsEACA_AACrBAAg3QIBAAAAAd4CAQAAAAXfAgEAAAAF4AIBAAAAAeECAQAAAAHiAgEAAAAB4wIBAAAAAeQCAQCqBAAh6wIBAAAAAewCAQAAAAHtAgEAAAABC90CAQAAAAHeAgEAAAAF3wIBAAAABeACAQAAAAHhAgEAAAAB4gIBAAAAAeMCAQAAAAHkAgEAqwQAIesCAQAAAAHsAgEAAAAB7QIBAAAAAQ0MAACbBAAgPAAArQQAID0AAJsEACA-AACbBAAgPwAAmwQAIN0CAgAAAAHeAgIAAAAF3wICAAAABeACAgAAAAHhAgIAAAAB4gICAAAAAeMCAgAAAAHkAgIArAQAIQjdAggAAAAB3gIIAAAABd8CCAAAAAXgAggAAAAB4QIIAAAAAeICCAAAAAHjAggAAAAB5AIIAK0EACEKLwAAlwQAINICAACuBAAw0wIAAMwDABDUAgAArgQAMNUCAQCWBAAh2wJAAJgEACHvAgEAlgQAIfICAQCmBAAh9gJAAJgEACH5AgIApwQAIQ_SAgAArwQAMNMCAAC0AwAQ1AIAAK8EADDVAgEAlgQAIdsCQACYBAAh7wIBAKYEACHyAgEAlgQAIfMCAgClBAAh-wIAALAE-wIi_AIBAKYEACH9AgEApgQAIf4CAQCmBAAh_wIBAKYEACGAAwAAsQQAIIEDAQCmBAAhBwwAAJ4EACA-AAC0BAAgPwAAtAQAIN0CAAAA-wIC3gIAAAD7AgjfAgAAAPsCCOQCAACzBPsCIg8MAACbBAAgPgAAsgQAID8AALIEACDdAoAAAAAB4AKAAAAAAeECgAAAAAHiAoAAAAAB4wKAAAAAAeQCgAAAAAHlAgEAAAAB5gIBAAAAAecCAQAAAAHoAoAAAAAB6QKAAAAAAeoCgAAAAAEM3QKAAAAAAeACgAAAAAHhAoAAAAAB4gKAAAAAAeMCgAAAAAHkAoAAAAAB5QIBAAAAAeYCAQAAAAHnAgEAAAAB6AKAAAAAAekCgAAAAAHqAoAAAAABBwwAAJ4EACA-AAC0BAAgPwAAtAQAIN0CAAAA-wIC3gIAAAD7AgjfAgAAAPsCCOQCAACzBPsCIgTdAgAAAPsCAt4CAAAA-wII3wIAAAD7AgjkAgAAtAT7AiIG0gIAALUEADDTAgAAnAMAENQCAAC1BAAw2wJAAJgEACHvAgEAlgQAIfICAQCWBAAhCNICAAC2BAAw0wIAAIYDABDUAgAAtgQAMNUCAQCWBAAh2wJAAJgEACHyAgEAlgQAIYIDAQCmBAAhhAMAALcEhAMiBwwAAJ4EACA-AAC5BAAgPwAAuQQAIN0CAAAAhAMC3gIAAACEAwjfAgAAAIQDCOQCAAC4BIQDIgcMAACeBAAgPgAAuQQAID8AALkEACDdAgAAAIQDAt4CAAAAhAMI3wIAAACEAwjkAgAAuASEAyIE3QIAAACEAwLeAgAAAIQDCN8CAAAAhAMI5AIAALkEhAMiB9ICAAC6BAAw0wIAAO4CABDUAgAAugQAMNsCQACYBAAh7wIBAJYEACHyAgEAlgQAIYYDAAC7BIYDIgcMAACeBAAgPgAAvQQAID8AAL0EACDdAgAAAIYDAt4CAAAAhgMI3wIAAACGAwjkAgAAvASGAyIHDAAAngQAID4AAL0EACA_AAC9BAAg3QIAAACGAwLeAgAAAIYDCN8CAAAAhgMI5AIAALwEhgMiBN0CAAAAhgMC3gIAAACGAwjfAgAAAIYDCOQCAAC9BIYDIgjSAgAAvgQAMNMCAADYAgAQ1AIAAL4EADDVAgEAlgQAIdsCQACYBAAh8AICAKUEACGHAwEAlgQAIYgDAQCmBAAhCQsAAMQEACDSAgAAvwQAMNMCAADFAgAQ1AIAAL8EADDVAgEAwAQAIdsCQADDBAAh8AICAMEEACGHAwEAwAQAIYgDAQDCBAAhC90CAQAAAAHeAgEAAAAE3wIBAAAABOACAQAAAAHhAgEAAAAB4gIBAAAAAeMCAQAAAAHkAgEAogQAIesCAQAAAAHsAgEAAAAB7QIBAAAAAQjdAgIAAAAB3gICAAAABd8CAgAAAAXgAgIAAAAB4QICAAAAAeICAgAAAAHjAgIAAAAB5AICAJsEACEL3QIBAAAAAd4CAQAAAAXfAgEAAAAF4AIBAAAAAeECAQAAAAHiAgEAAAAB4wIBAAAAAeQCAQCrBAAh6wIBAAAAAewCAQAAAAHtAgEAAAABCN0CQAAAAAHeAkAAAAAE3wJAAAAABOACQAAAAAHhAkAAAAAB4gJAAAAAAeMCQAAAAAHkAkAAnwQAIQOJAwAAHAAgigMAABwAIIsDAAAcACAJ0gIAAMUEADDTAgAAvwIAENQCAADFBAAw1QIBAJYEACHbAkAAmAQAIfICAQCWBAAh-wIAAMYEjwMijAMCAKcEACGNAwEAlgQAIQcMAACeBAAgPgAAyAQAID8AAMgEACDdAgAAAI8DAt4CAAAAjwMI3wIAAACPAwjkAgAAxwSPAyIHDAAAngQAID4AAMgEACA_AADIBAAg3QIAAACPAwLeAgAAAI8DCN8CAAAAjwMI5AIAAMcEjwMiBN0CAAAAjwMC3gIAAACPAwjfAgAAAI8DCOQCAADIBI8DIgfSAgAAyQQAMNMCAACpAgAQ1AIAAMkEADDbAkAAmAQAIfICAQCWBAAh8wICAKcEACGPAwEAlgQAIQ7SAgAAygQAMNMCAACTAgAQ1AIAAMoEADDbAkAAmAQAIfICAQCWBAAh8wICAKcEACGQAwEAlgQAIZEDAQCmBAAhkgMBAKYEACGTAwEApgQAIZQDAQCWBAAhlQMBAKYEACGWAwEApgQAIZcDQACZBAAhEtICAADLBAAw0wIAAP0BABDUAgAAywQAMNUCAQCWBAAh2wJAAJgEACHwAgIApQQAIfYCQACYBAAh-AJAAJkEACGYAwIApQQAIZkDAQCmBAAhmgMCAKUEACGcAwAAzAScAyKdAyAAzQQAIZ4DIADNBAAhnwMCAKcEACGgAwIApwQAIaEDAgCnBAAhogMCAKcEACEHDAAAngQAID4AANEEACA_AADRBAAg3QIAAACcAwLeAgAAAJwDCN8CAAAAnAMI5AIAANAEnAMiBQwAAJ4EACA-AADPBAAgPwAAzwQAIN0CIAAAAAHkAiAAzgQAIQUMAACeBAAgPgAAzwQAID8AAM8EACDdAiAAAAAB5AIgAM4EACEC3QIgAAAAAeQCIADPBAAhBwwAAJ4EACA-AADRBAAgPwAA0QQAIN0CAAAAnAMC3gIAAACcAwjfAgAAAJwDCOQCAADQBJwDIgTdAgAAAJwDAt4CAAAAnAMI3wIAAACcAwjkAgAA0QScAyIO0gIAANIEADDTAgAA4QEAENQCAADSBAAw1QIBAJYEACHbAkAAmQQAIe8CAQCWBAAhhwMBAKYEACGjAwEAlgQAIaQDAQCWBAAhpQMCAKcEACGmAwEAlgQAIacDIADNBAAhqAMBAKYEACGpAwEApgQAIQrSAgAA0wQAMNMCAADLAQAQ1AIAANMEADDVAgEAlgQAIdsCQACZBAAh7wIBAKYEACH2AkAAmQQAIaoDAQCWBAAhqwMBAJYEACGsA0AAmAQAIQzSAgAA1AQAMNMCAACzAQAQ1AIAANQEADDVAgEAlgQAIdsCQACYBAAh7wIBAJYEACH2AkAAmAQAIf4CAQCmBAAhrANAAJgEACGtAwEAlgQAIa4DAQCmBAAhrwMBAKYEACEQ0gIAANUEADDTAgAAnQEAENQCAADVBAAw1QIBAJYEACHbAkAAmAQAIe8CAQCWBAAh9gJAAJgEACGwAwEAlgQAIbEDAQCWBAAhsgMBAKYEACGzAwEApgQAIbQDQACZBAAhtQNAAJkEACG2AwEApgQAIbcDAQCmBAAhuAMBAKYEACEZ0gIAANYEADDTAgAAhwEAENQCAADWBAAw1QIBAJYEACHbAkAAmAQAIfACAgClBAAh9gJAAJgEACH4AkAAmQQAIYYDAQCmBAAhhwMBAKYEACG5AwEApgQAIboDIADNBAAhuwMBAKYEACG9AwAA1wS9AyK_AwAA2AS_AyLAAyAAzQQAIcEDAQCmBAAhwgMBAKYEACHDAwAAsQQAIMQDQACZBAAhxQMBAKYEACHGAyAA2QQAIccDAQCmBAAhyANAAJkEACHJA0AAmQQAIQcMAACeBAAgPgAA3wQAID8AAN8EACDdAgAAAL0DAt4CAAAAvQMI3wIAAAC9AwjkAgAA3gS9AyIHDAAAngQAID4AAN0EACA_AADdBAAg3QIAAAC_AwLeAgAAAL8DCN8CAAAAvwMI5AIAANwEvwMiBQwAAJsEACA-AADbBAAgPwAA2wQAIN0CIAAAAAHkAiAA2gQAIQUMAACbBAAgPgAA2wQAID8AANsEACDdAiAAAAAB5AIgANoEACEC3QIgAAAAAeQCIADbBAAhBwwAAJ4EACA-AADdBAAgPwAA3QQAIN0CAAAAvwMC3gIAAAC_AwjfAgAAAL8DCOQCAADcBL8DIgTdAgAAAL8DAt4CAAAAvwMI3wIAAAC_AwjkAgAA3QS_AyIHDAAAngQAID4AAN8EACA_AADfBAAg3QIAAAC9AwLeAgAAAL0DCN8CAAAAvQMI5AIAAN4EvQMiBN0CAAAAvQMC3gIAAAC9AwjfAgAAAL0DCOQCAADfBL0DIg8DAADkBAAg0gIAAOAEADDTAgAAZAAQ1AIAAOAEADDVAgEAwAQAIdsCQADjBAAh7wIBAMAEACGHAwEAwgQAIaMDAQDABAAhpAMBAMAEACGlAwIA4QQAIaYDAQDABAAhpwMgAOIEACGoAwEAwgQAIakDAQDCBAAhCN0CAgAAAAHeAgIAAAAE3wICAAAABOACAgAAAAHhAgIAAAAB4gICAAAAAeMCAgAAAAHkAgIAngQAIQLdAiAAAAAB5AIgAM8EACEI3QJAAAAAAd4CQAAAAAXfAkAAAAAF4AJAAAAAAeECQAAAAAHiAkAAAAAB4wJAAAAAAeQCQACcBAAhJwQAAJUFACAFAACWBQAgBgAAlwUAICAAAO4EACAhAACJBQAgIgAAigUAICQAAJgFACAlAACLBQAgJgAAjAUAICcAAI0FACAoAADvBAAgKQAAmQUAINICAACRBQAw0wIAAA8AENQCAACRBQAw1QIBAMAEACHbAkAAwwQAIfACAgDBBAAh9gJAAMMEACH4AkAA4wQAIYYDAQDCBAAhhwMBAMIEACG5AwEAwgQAIboDIADiBAAhuwMBAMIEACG9AwAAkgW9AyK_AwAAkwW_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_gIBAMIEACH_AgEAwgQAIYADAAD0BAAggQMBAMIEACEE3QIAAAD7AgLeAgAAAPsCCN8CAAAA-wII5AIAALQE-wIiDN0CgAAAAAHgAoAAAAAB4QKAAAAAAeICgAAAAAHjAoAAAAAB5AKAAAAAAeUCAQAAAAHmAgEAAAAB5wIBAAAAAegCgAAAAAHpAoAAAAAB6gKAAAAAAQLvAgEAAAAB8gIBAAAAAQgDAADkBAAgBwAA6wQAINICAAD2BAAw0wIAADYAENQCAAD2BAAw2wJAAMMEACHvAgEAwAQAIfICAQDABAAhAvICAQAAAAGCAwEAAAABCgcAAOsEACAXAADsBAAg0gIAAPgEADDTAgAAMAAQ1AIAAPgEADDVAgEAwAQAIdsCQADDBAAh8gIBAMAEACGCAwEAwgQAIYQDAAD5BIQDIgTdAgAAAIQDAt4CAAAAhAMI3wIAAACEAwjkAgAAuQSEAyIC8gIBAAAAAfMCAgAAAAETBwAA6wQAIAgAAPEEACAJAAD8BAAgDgAAxAQAIBAAAP0EACDSAgAA-wQAMNMCAAAVABDUAgAA-wQAMNsCQADDBAAh8gIBAMAEACHzAgIA4QQAIZADAQDABAAhkQMBAMIEACGSAwEAwgQAIZMDAQDCBAAhlAMBAMAEACGVAwEAwgQAIZYDAQDCBAAhlwNAAOMEACEDiQMAABcAIIoDAAAXACCLAwAAFwAgA4kDAAAiACCKAwAAIgAgiwMAACIAIAsHAADrBAAgDwAAgAUAINICAAD-BAAw0wIAACIAENQCAAD-BAAw1QIBAMAEACHbAkAAwwQAIfICAQDABAAh-wIAAP8EjwMijAMCAOEEACGNAwEAwAQAIQTdAgAAAI8DAt4CAAAAjwMI3wIAAACPAwjkAgAAyASPAyIVBwAA6wQAIAgAAPEEACAJAAD8BAAgDgAAxAQAIBAAAP0EACDSAgAA-wQAMNMCAAAVABDUAgAA-wQAMNsCQADDBAAh8gIBAMAEACHzAgIA4QQAIZADAQDABAAhkQMBAMIEACGSAwEAwgQAIZMDAQDCBAAhlAMBAMAEACGVAwEAwgQAIZYDAQDCBAAhlwNAAOMEACHQAwAAFQAg0QMAABUAIAPyAgEAAAAB8wICAAAAAY8DAQAAAAEJCgAAgAUAIA0AAIMFACDSAgAAggUAMNMCAAAcABDUAgAAggUAMNsCQADDBAAh8gIBAMAEACHzAgIA4QQAIY8DAQDABAAhCwsAAMQEACDSAgAAvwQAMNMCAADFAgAQ1AIAAL8EADDVAgEAwAQAIdsCQADDBAAh8AICAMEEACGHAwEAwAQAIYgDAQDCBAAh0AMAAMUCACDRAwAAxQIAIALVAgEAAAABmAMCAAAAAR4RAACHBQAgEgAA8QQAIBMAAPwEACAUAACHBQAgFQAAiAUAIBYAAIkFACAYAACKBQAgGQAA_QQAIBoAAIsFACAbAACMBQAgHAAAjQUAICAAAO4EACDSAgAAhQUAMNMCAAAXABDUAgAAhQUAMNUCAQDABAAh2wJAAMMEACHwAgIAwQQAIfYCQADDBAAh-AJAAOMEACGYAwIAwQQAIZkDAQDCBAAhmgMCAMEEACGcAwAAhgWcAyKdAyAA4gQAIZ4DIADiBAAhnwMCAOEEACGgAwIA4QQAIaEDAgDhBAAhogMCAOEEACEE3QIAAACcAwLeAgAAAJwDCN8CAAAAnAMI5AIAANEEnAMiFQcAAOsEACAIAADxBAAgCQAA_AQAIA4AAMQEACAQAAD9BAAg0gIAAPsEADDTAgAAFQAQ1AIAAPsEADDbAkAAwwQAIfICAQDABAAh8wICAOEEACGQAwEAwAQAIZEDAQDCBAAhkgMBAMIEACGTAwEAwgQAIZQDAQDABAAhlQMBAMIEACGWAwEAwgQAIZcDQADjBAAh0AMAABUAINEDAAAVACADiQMAABUAIIoDAAAVACCLAwAAFQAgA4kDAAARACCKAwAAEQAgiwMAABEAIAOJAwAAMAAgigMAADAAIIsDAAAwACADiQMAADYAIIoDAAA2ACCLAwAANgAgA4kDAAA6ACCKAwAAOgAgiwMAADoAIAOJAwAAPwAgigMAAD8AIIsDAAA_ACAC7wIBAAAAAfICAQAAAAEJAwAA5AQAIAcAAOsEACDSAgAAjwUAMNMCAAARABDUAgAAjwUAMNsCQADDBAAh7wIBAMAEACHyAgEAwAQAIYYDAACQBYYDIgTdAgAAAIYDAt4CAAAAhgMI3wIAAACGAwjkAgAAvQSGAyIlBAAAlQUAIAUAAJYFACAGAACXBQAgIAAA7gQAICEAAIkFACAiAACKBQAgJAAAmAUAICUAAIsFACAmAACMBQAgJwAAjQUAICgAAO8EACApAACZBQAg0gIAAJEFADDTAgAADwAQ1AIAAJEFADDVAgEAwAQAIdsCQADDBAAh8AICAMEEACH2AkAAwwQAIfgCQADjBAAhhgMBAMIEACGHAwEAwgQAIbkDAQDCBAAhugMgAOIEACG7AwEAwgQAIb0DAACSBb0DIr8DAACTBb8DIsADIADiBAAhwQMBAMIEACHCAwEAwgQAIcMDAAD0BAAgxANAAOMEACHFAwEAwgQAIcYDIACUBQAhxwMBAMIEACHIA0AA4wQAIckDQADjBAAhBN0CAAAAvQMC3gIAAAC9AwjfAgAAAL0DCOQCAADfBL0DIgTdAgAAAL8DAt4CAAAAvwMI3wIAAAC_AwjkAgAA3QS_AyIC3QIgAAAAAeQCIADbBAAhA4kDAAADACCKAwAAAwAgiwMAAAMAIAOJAwAABwAgigMAAAcAIIsDAAAHACADiQMAAAsAIIoDAAALACCLAwAACwAgA4kDAABbACCKAwAAWwAgiwMAAFsAIAOJAwAAZAAgigMAAGQAIIsDAABkACALAwAA7AQAINICAACaBQAw0wIAAAsAENQCAACaBQAw1QIBAMAEACHbAkAA4wQAIe8CAQDCBAAh9gJAAOMEACGqAwEAwAQAIasDAQDABAAhrANAAMMEACENAwAA5AQAINICAACbBQAw0wIAAAcAENQCAACbBQAw1QIBAMAEACHbAkAAwwQAIe8CAQDABAAh9gJAAMMEACH-AgEAwgQAIawDQADDBAAhrQMBAMAEACGuAwEAwgQAIa8DAQDCBAAhEQMAAOQEACDSAgAAnAUAMNMCAAADABDUAgAAnAUAMNUCAQDABAAh2wJAAMMEACHvAgEAwAQAIfYCQADDBAAhsAMBAMAEACGxAwEAwAQAIbIDAQDCBAAhswMBAMIEACG0A0AA4wQAIbUDQADjBAAhtgMBAMIEACG3AwEAwgQAIbgDAQDCBAAhAAAAAAHVAwEAAAABAdUDQAAAAAEB1QNAAAAAAQU2AADGCgAgNwAAyQoAINIDAADHCgAg0wMAAMgKACDYAwAAAQAgAzYAAMYKACDSAwAAxwoAINgDAAABACAAAAAFNgAAvgoAIDcAAMQKACDSAwAAvwoAINMDAADDCgAg2AMAAEYAIAU2AAC8CgAgNwAAwQoAINIDAAC9CgAg0wMAAMAKACDYAwAAAQAgAzYAAL4KACDSAwAAvwoAINgDAABGACADNgAAvAoAINIDAAC9CgAg2AMAAAEAIAAAAAAABdUDAgAAAAHbAwIAAAAB3AMCAAAAAd0DAgAAAAHeAwIAAAABAdUDAQAAAAEF1QMCAAAAAdsDAgAAAAHcAwIAAAAB3QMCAAAAAd4DAgAAAAEFNgAArwoAIDcAALoKACDSAwAAsAoAINMDAAC5CgAg2AMAABoAIAc2AACtCgAgNwAAtwoAINIDAACuCgAg0wMAALYKACDWAwAADwAg1wMAAA8AINgDAAABACAHNgAAqwoAIDcAALQKACDSAwAArAoAINMDAACzCgAg1gMAAEQAINcDAABEACDYAwAARgAgCzYAAMYFADA3AADLBQAw0gMAAMcFADDTAwAAyAUAMNQDAADJBQAg1QMAAMoFADDWAwAAygUAMNcDAADKBQAw2AMAAMoFADDZAwAAzAUAMNoDAADNBQAwCzYAALoFADA3AAC_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-wkAIDcAAIEKACDSAwAA_AkAINMDAACACgAg2AMAABoAIAU2AAD5CQAgNwAA_gkAINIDAAD6CQAg0wMAAP0JACDYAwAAAQAgAzYAAPsJACDSAwAA_AkAINgDAAAaACADNgAA-QkAINIDAAD6CQAg2AMAAAEAIAAAAAAACzYAAIcGADA3AACMBgAw0gMAAIgGADDTAwAAiQYAMNQDAACKBgAg1QMAAIsGADDWAwAAiwYAMNcDAACLBgAw2AMAAIsGADDZAwAAjQYAMNoDAACOBgAwBAoAAJQGACDbAkAAAAAB8gIBAAAAAfMCAgAAAAECAAAAHgAgNgAAkwYAIAMAAAAeACA2AACTBgAgNwAAkQYAIAEvAAD4CQAwCgoAAIAFACANAACDBQAg0gIAAIIFADDTAgAAHAAQ1AIAAIIFADDbAkAAwwQAIfICAQDABAAh8wICAOEEACGPAwEAwAQAIc4DAACBBQAgAgAAAB4AIC8AAJEGACACAAAAjwYAIC8AAJAGACAH0gIAAI4GADDTAgAAjwYAENQCAACOBgAw2wJAAMMEACHyAgEAwAQAIfMCAgDhBAAhjwMBAMAEACEH0gIAAI4GADDTAgAAjwYAENQCAACOBgAw2wJAAMMEACHyAgEAwAQAIfMCAgDhBAAhjwMBAMAEACED2wJAAKIFACHyAgEAoQUAIfMCAgC0BQAhBAoAAJIGACDbAkAAogUAIfICAQChBQAh8wICALQFACEFNgAA8wkAIDcAAPYJACDSAwAA9AkAINMDAAD1CQAg2AMAAC0AIAQKAACUBgAg2wJAAAAAAfICAQAAAAHzAgIAAAABAzYAAPMJACDSAwAA9AkAINgDAAAtACAENgAAhwYAMNIDAACIBgAw1AMAAIoGACDYAwAAiwYAMAAAAAAAAAHVAwAAAI8DAgU2AADrCQAgNwAA8QkAINIDAADsCQAg0wMAAPAJACDYAwAAGgAgBTYAAOkJACA3AADuCQAg0gMAAOoJACDTAwAA7QkAINgDAAAtACADNgAA6wkAINIDAADsCQAg2AMAABoAIAM2AADpCQAg0gMAAOoJACDYAwAALQAgAAAAAAAFNgAA5AkAIDcAAOcJACDSAwAA5QkAINMDAADmCQAg2AMAAMICACADNgAA5AkAINIDAADlCQAg2AMAAMICACAAAAAAAAU2AADECQAgNwAA4gkAINIDAADFCQAg0wMAAOEJACDYAwAAGgAgBzYAAL0HACA3AADXBwAg0gMAAL4HACDTAwAA1gcAINYDAAAXACDXAwAAFwAg2AMAABoAIAs2AADHBgAwNwAAzAYAMNIDAADIBgAw0wMAAMkGADDUAwAAygYAINUDAADLBgAw1gMAAMsGADDXAwAAywYAMNgDAADLBgAw2QMAAM0GADDaAwAAzgYAMAs2AAC-BgAwNwAAwgYAMNIDAAC_BgAw0wMAAMAGADDUAwAAwQYAINUDAACLBgAw1gMAAIsGADDXAwAAiwYAMNgDAACLBgAw2QMAAMMGADDaAwAAjgYAMAs2AACyBgAwNwAAtwYAMNIDAACzBgAw0wMAALQGADDUAwAAtQYAINUDAAC2BgAw1gMAALYGADDXAwAAtgYAMNgDAAC2BgAw2QMAALgGADDaAwAAuQYAMAUHAACfBgAg1QIBAAAAAdsCQAAAAAH7AgAAAI8DAo0DAQAAAAECAAAAJAAgNgAAvQYAIAMAAAAkACA2AAC9BgAgNwAAvAYAIAEvAADgCQAwCwcAAOsEACAPAACABQAg0gIAAP4EADDTAgAAIgAQ1AIAAP4EADDVAgEAAAAB2wJAAMMEACHyAgEAwAQAIfsCAAD_BI8DIowDAgDhBAAhjQMBAMAEACECAAAAJAAgLwAAvAYAIAIAAAC6BgAgLwAAuwYAIAnSAgAAuQYAMNMCAAC6BgAQ1AIAALkGADDVAgEAwAQAIdsCQADDBAAh8gIBAMAEACH7AgAA_wSPAyKMAwIA4QQAIY0DAQDABAAhCdICAAC5BgAw0wIAALoGABDUAgAAuQYAMNUCAQDABAAh2wJAAMMEACHyAgEAwAQAIfsCAAD_BI8DIowDAgDhBAAhjQMBAMAEACEE1QIBAKEFACHbAkAAogUAIfsCAACcBo8DIo0DAQChBQAhBQcAAJ0GACDVAgEAoQUAIdsCQACiBQAh-wIAAJwGjwMijQMBAKEFACEFBwAAnwYAINUCAQAAAAHbAkAAAAAB-wIAAACPAwKNAwEAAAABAw0AAKcGACDbAkAAAAABjwMBAAAAAQIAAAAeACA2AADGBgAgAwAAAB4AIDYAAMYGACA3AADFBgAgAS8AAN8JADACAAAAHgAgLwAAxQYAIAIAAACPBgAgLwAAxAYAIALbAkAAogUAIY8DAQChBQAhAw0AAKYGACDbAkAAogUAIY8DAQChBQAhAw0AAKcGACDbAkAAAAABjwMBAAAAARgRAADOBwAgEgAAvwcAIBMAAMAHACAVAADCBwAgFgAAwwcAIBgAAMQHACAZAADFBwAgGgAAxgcAIBsAAMcHACAcAADIBwAgIAAAyQcAINUCAQAAAAHbAkAAAAAB8AICAAAAAfYCQAAAAAH4AkAAAAABmAMCAAAAAZwDAAAAnAMCnQMgAAAAAZ4DIAAAAAGfAwIAAAABoAMCAAAAAaEDAgAAAAGiAwIAAAABAgAAABoAIDYAANUHACADAAAAGgAgNgAA1QcAIDcAANMGACABLwAA3gkAMB8RAACHBQAgEgAA8QQAIBMAAPwEACAUAACHBQAgFQAAiAUAIBYAAIkFACAYAACKBQAgGQAA_QQAIBoAAIsFACAbAACMBQAgHAAAjQUAICAAAO4EACDSAgAAhQUAMNMCAAAXABDUAgAAhQUAMNUCAQAAAAHbAkAAwwQAIfACAgAAAAH2AkAAwwQAIfgCQADjBAAhmAMCAMEEACGZAwEAwgQAIZoDAgDBBAAhnAMAAIYFnAMinQMgAOIEACGeAyAA4gQAIZ8DAgDhBAAhoAMCAOEEACGhAwIA4QQAIaIDAgDhBAAhzwMAAIQFACACAAAAGgAgLwAA0wYAIAIAAADPBgAgLwAA0AYAIBLSAgAAzgYAMNMCAADPBgAQ1AIAAM4GADDVAgEAwAQAIdsCQADDBAAh8AICAMEEACH2AkAAwwQAIfgCQADjBAAhmAMCAMEEACGZAwEAwgQAIZoDAgDBBAAhnAMAAIYFnAMinQMgAOIEACGeAyAA4gQAIZ8DAgDhBAAhoAMCAOEEACGhAwIA4QQAIaIDAgDhBAAhEtICAADOBgAw0wIAAM8GABDUAgAAzgYAMNUCAQDABAAh2wJAAMMEACHwAgIAwQQAIfYCQADDBAAh-AJAAOMEACGYAwIAwQQAIZkDAQDCBAAhmgMCAMEEACGcAwAAhgWcAyKdAyAA4gQAIZ4DIADiBAAhnwMCAOEEACGgAwIA4QQAIaEDAgDhBAAhogMCAOEEACEN1QIBAKEFACHbAkAAogUAIfACAgCyBQAh9gJAAKIFACH4AkAAowUAIZgDAgCyBQAhnAMAANEGnAMinQMgANIGACGeAyAA0gYAIZ8DAgC0BQAhoAMCALQFACGhAwIAtAUAIaIDAgC0BQAhAdUDAAAAnAMCAdUDIAAAAAEYEQAA1AYAIBIAANUGACATAADWBgAgFQAA1wYAIBYAANgGACAYAADZBgAgGQAA2gYAIBoAANsGACAbAADcBgAgHAAA3QYAICAAAN4GACDVAgEAoQUAIdsCQACiBQAh8AICALIFACH2AkAAogUAIfgCQACjBQAhmAMCALIFACGcAwAA0QacAyKdAyAA0gYAIZ4DIADSBgAhnwMCALQFACGgAwIAtAUAIaEDAgC0BQAhogMCALQFACEHNgAAywkAIDcAANwJACDSAwAAzAkAINMDAADbCQAg1gMAABUAINcDAAAVACDYAwAALQAgBzYAAM4JACA3AADZCQAg0gMAAM8JACDTAwAA2AkAINYDAAAXACDXAwAAFwAg2AMAABoAIAs2AADKBwAwNwAA0AcAMNIDAADLBwAw0wMAAM8HADDUAwAAzAcAINUDAADLBgAw1gMAAMsGADDXAwAAywYAMNgDAADLBgAw2QMAANEHADDaAwAAzgYAMAs2AACtBwAwNwAAsgcAMNIDAACuBwAw0wMAAK8HADDUAwAAsAcAINUDAACxBwAw1gMAALEHADDXAwAAsQcAMNgDAACxBwAw2QMAALMHADDaAwAAtAcAMAs2AAChBwAwNwAApgcAMNIDAACiBwAw0wMAAKMHADDUAwAApAcAINUDAAClBwAw1gMAAKUHADDXAwAApQcAMNgDAAClBwAw2QMAAKcHADDaAwAAqAcAMAs2AACVBwAwNwAAmgcAMNIDAACWBwAw0wMAAJcHADDUAwAAmAcAINUDAACZBwAw1gMAAJkHADDXAwAAmQcAMNgDAACZBwAw2QMAAJsHADDaAwAAnAcAMAs2AACMBwAwNwAAkAcAMNIDAACNBwAw0wMAAI4HADDUAwAAjwcAINUDAAC2BgAw1gMAALYGADDXAwAAtgYAMNgDAAC2BgAw2QMAAJEHADDaAwAAuQYAMAs2AACABwAwNwAAhQcAMNIDAACBBwAw0wMAAIIHADDUAwAAgwcAINUDAACEBwAw1gMAAIQHADDXAwAAhAcAMNgDAACEBwAw2QMAAIYHADDaAwAAhwcAMAs2AAD0BgAwNwAA-QYAMNIDAAD1BgAw0wMAAPYGADDUAwAA9wYAINUDAAD4BgAw1gMAAPgGADDXAwAA-AYAMNgDAAD4BgAw2QMAAPoGADDaAwAA-wYAMAs2AADoBgAwNwAA7QYAMNIDAADpBgAw0wMAAOoGADDUAwAA6wYAINUDAADsBgAw1gMAAOwGADDXAwAA7AYAMNgDAADsBgAw2QMAAO4GADDaAwAA7wYAMAs2AADfBgAwNwAA4wYAMNIDAADgBgAw0wMAAOEGADDUAwAA4gYAINUDAADKBQAw1gMAAMoFADDXAwAAygUAMNgDAADKBQAw2QMAAOQGADDaAwAAzQUAMA8DAADTBQAgGgAA1QUAIB0AANYFACAeAADUBQAg1QIBAAAAAdsCQAAAAAHvAgEAAAAB8AICAAAAAfECAQAAAAHzAgIAAAAB9AIBAAAAAfUCAgAAAAH2AkAAAAAB9wJAAAAAAfgCQAAAAAECAAAARgAgNgAA5wYAIAMAAABGACA2AADnBgAgNwAA5gYAIAEvAADXCQAwAgAAAEYAIC8AAOYGACACAAAAzgUAIC8AAOUGACAL1QIBAKEFACHbAkAAogUAIe8CAQCzBQAh8AICALIFACHxAgEAswUAIfMCAgCyBQAh9AIBALMFACH1AgIAtAUAIfYCQACiBQAh9wJAAKMFACH4AkAAowUAIQ8DAAC2BQAgGgAAuQUAIB0AALcFACAeAAC4BQAg1QIBAKEFACHbAkAAogUAIe8CAQCzBQAh8AICALIFACHxAgEAswUAIfMCAgCyBQAh9AIBALMFACH1AgIAtAUAIfYCQACiBQAh9wJAAKMFACH4AkAAowUAIQ8DAADTBQAgGgAA1QUAIB0AANYFACAeAADUBQAg1QIBAAAAAdsCQAAAAAHvAgEAAAAB8AICAAAAAfECAQAAAAHzAgIAAAAB9AIBAAAAAfUCAgAAAAH2AkAAAAAB9wJAAAAAAfgCQAAAAAEHAwAA3gUAIC-AAAAAAdUCAQAAAAHbAkAAAAAB7wIBAAAAAfYCQAAAAAH5AgIAAAABAgAAAEEAIDYAAPMGACADAAAAQQAgNgAA8wYAIDcAAPIGACABLwAA1gkAMAwDAADkBAAgBwAA8QQAIC8AAOYEACDSAgAA8AQAMNMCAAA_ABDUAgAA8AQAMNUCAQAAAAHbAkAAwwQAIe8CAQDABAAh8gIBAMIEACH2AkAAwwQAIfkCAgDhBAAhAgAAAEEAIC8AAPIGACACAAAA8AYAIC8AAPEGACAKLwAA5gQAINICAADvBgAw0wIAAPAGABDUAgAA7wYAMNUCAQDABAAh2wJAAMMEACHvAgEAwAQAIfICAQDCBAAh9gJAAMMEACH5AgIA4QQAIQovAADmBAAg0gIAAO8GADDTAgAA8AYAENQCAADvBgAw1QIBAMAEACHbAkAAwwQAIe8CAQDABAAh8gIBAMIEACH2AkAAwwQAIfkCAgDhBAAhBi-AAAAAAdUCAQChBQAh2wJAAKIFACHvAgEAoQUAIfYCQACiBQAh-QICALQFACEHAwAA3AUAIC-AAAAAAdUCAQChBQAh2wJAAKIFACHvAgEAoQUAIfYCQACiBQAh-QICALQFACEHAwAA3gUAIC-AAAAAAdUCAQAAAAHbAkAAAAAB7wIBAAAAAfYCQAAAAAH5AgIAAAABDAMAAOkFACDVAgEAAAAB2wJAAAAAAe8CAQAAAAHzAgIAAAAB-wIAAAD7AgL8AgEAAAAB_QIBAAAAAf4CAQAAAAH_AgEAAAABgAOAAAAAAYEDAQAAAAECAAAAPAAgNgAA_wYAIAMAAAA8ACA2AAD_BgAgNwAA_gYAIAEvAADVCQAwEQMAAOwEACAHAADrBAAg0gIAAPIEADDTAgAAOgAQ1AIAAPIEADDVAgEAAAAB2wJAAMMEACHvAgEAwgQAIfICAQDABAAh8wICAMEEACH7AgAA8wT7AiL8AgEAwgQAIf0CAQDCBAAh_gIBAMIEACH_AgEAwgQAIYADAAD0BAAggQMBAMIEACECAAAAPAAgLwAA_gYAIAIAAAD8BgAgLwAA_QYAIA_SAgAA-wYAMNMCAAD8BgAQ1AIAAPsGADDVAgEAwAQAIdsCQADDBAAh7wIBAMIEACHyAgEAwAQAIfMCAgDBBAAh-wIAAPME-wIi_AIBAMIEACH9AgEAwgQAIf4CAQDCBAAh_wIBAMIEACGAAwAA9AQAIIEDAQDCBAAhD9ICAAD7BgAw0wIAAPwGABDUAgAA-wYAMNUCAQDABAAh2wJAAMMEACHvAgEAwgQAIfICAQDABAAh8wICAMEEACH7AgAA8wT7AiL8AgEAwgQAIf0CAQDCBAAh_gIBAMIEACH_AgEAwgQAIYADAAD0BAAggQMBAMIEACEL1QIBAKEFACHbAkAAogUAIe8CAQCzBQAh8wICALIFACH7AgAA5QX7AiL8AgEAswUAIf0CAQCzBQAh_gIBALMFACH_AgEAswUAIYADgAAAAAGBAwEAswUAIQwDAADnBQAg1QIBAKEFACHbAkAAogUAIe8CAQCzBQAh8wICALIFACH7AgAA5QX7AiL8AgEAswUAIf0CAQCzBQAh_gIBALMFACH_AgEAswUAIYADgAAAAAGBAwEAswUAIQwDAADpBQAg1QIBAAAAAdsCQAAAAAHvAgEAAAAB8wICAAAAAfsCAAAA-wIC_AIBAAAAAf0CAQAAAAH-AgEAAAAB_wIBAAAAAYADgAAAAAGBAwEAAAABAwMAAPAFACDbAkAAAAAB7wIBAAAAAQIAAAA4ACA2AACLBwAgAwAAADgAIDYAAIsHACA3AACKBwAgAS8AANQJADAJAwAA5AQAIAcAAOsEACDSAgAA9gQAMNMCAAA2ABDUAgAA9gQAMNsCQADDBAAh7wIBAMAEACHyAgEAwAQAIcsDAAD1BAAgAgAAADgAIC8AAIoHACACAAAAiAcAIC8AAIkHACAG0gIAAIcHADDTAgAAiAcAENQCAACHBwAw2wJAAMMEACHvAgEAwAQAIfICAQDABAAhBtICAACHBwAw0wIAAIgHABDUAgAAhwcAMNsCQADDBAAh7wIBAMAEACHyAgEAwAQAIQLbAkAAogUAIe8CAQChBQAhAwMAAO4FACDbAkAAogUAIe8CAQChBQAhAwMAAPAFACDbAkAAAAAB7wIBAAAAAQYPAACgBgAg1QIBAAAAAdsCQAAAAAH7AgAAAI8DAowDAgAAAAGNAwEAAAABAgAAACQAIDYAAJQHACADAAAAJAAgNgAAlAcAIDcAAJMHACABLwAA0wkAMAIAAAAkACAvAACTBwAgAgAAALoGACAvAACSBwAgBdUCAQChBQAh2wJAAKIFACH7AgAAnAaPAyKMAwIAtAUAIY0DAQChBQAhBg8AAJ4GACDVAgEAoQUAIdsCQACiBQAh-wIAAJwGjwMijAMCALQFACGNAwEAoQUAIQYPAACgBgAg1QIBAAAAAdsCQAAAAAH7AgAAAI8DAowDAgAAAAGNAwEAAAABBRcAAPgFACDVAgEAAAAB2wJAAAAAAYIDAQAAAAGEAwAAAIQDAgIAAAAyACA2AACgBwAgAwAAADIAIDYAAKAHACA3AACfBwAgAS8AANIJADALBwAA6wQAIBcAAOwEACDSAgAA-AQAMNMCAAAwABDUAgAA-AQAMNUCAQAAAAHbAkAAwwQAIfICAQDABAAhggMBAMIEACGEAwAA-QSEAyLMAwAA9wQAIAIAAAAyACAvAACfBwAgAgAAAJ0HACAvAACeBwAgCNICAACcBwAw0wIAAJ0HABDUAgAAnAcAMNUCAQDABAAh2wJAAMMEACHyAgEAwAQAIYIDAQDCBAAhhAMAAPkEhAMiCNICAACcBwAw0wIAAJ0HABDUAgAAnAcAMNUCAQDABAAh2wJAAMMEACHyAgEAwAQAIYIDAQDCBAAhhAMAAPkEhAMiBNUCAQChBQAh2wJAAKIFACGCAwEAswUAIYQDAAD0BYQDIgUXAAD2BQAg1QIBAKEFACHbAkAAogUAIYIDAQCzBQAhhAMAAPQFhAMiBRcAAPgFACDVAgEAAAAB2wJAAAAAAYIDAQAAAAGEAwAAAIQDAgQDAACABgAg2wJAAAAAAe8CAQAAAAGGAwAAAIYDAgIAAAATACA2AACsBwAgAwAAABMAIDYAAKwHACA3AACrBwAgAS8AANEJADAKAwAA5AQAIAcAAOsEACDSAgAAjwUAMNMCAAARABDUAgAAjwUAMNsCQADDBAAh7wIBAMAEACHyAgEAwAQAIYYDAACQBYYDIssDAACOBQAgAgAAABMAIC8AAKsHACACAAAAqQcAIC8AAKoHACAH0gIAAKgHADDTAgAAqQcAENQCAACoBwAw2wJAAMMEACHvAgEAwAQAIfICAQDABAAhhgMAAJAFhgMiB9ICAACoBwAw0wIAAKkHABDUAgAAqAcAMNsCQADDBAAh7wIBAMAEACHyAgEAwAQAIYYDAACQBYYDIgPbAkAAogUAIe8CAQChBQAhhgMAAPwFhgMiBAMAAP4FACDbAkAAogUAIe8CAQChBQAhhgMAAPwFhgMiBAMAAIAGACDbAkAAAAAB7wIBAAAAAYYDAAAAhgMCDggAALkHACAJAAC6BwAgDgAAuwcAIBAAALwHACDbAkAAAAAB8wICAAAAAZADAQAAAAGRAwEAAAABkgMBAAAAAZMDAQAAAAGUAwEAAAABlQMBAAAAAZYDAQAAAAGXA0AAAAABAgAAAC0AIDYAALgHACADAAAALQAgNgAAuAcAIDcAALcHACABLwAA0AkAMBQHAADrBAAgCAAA8QQAIAkAAPwEACAOAADEBAAgEAAA_QQAINICAAD7BAAw0wIAABUAENQCAAD7BAAw2wJAAMMEACHyAgEAwAQAIfMCAgDhBAAhkAMBAMAEACGRAwEAwgQAIZIDAQDCBAAhkwMBAMIEACGUAwEAwAQAIZUDAQDCBAAhlgMBAMIEACGXA0AA4wQAIc0DAAD6BAAgAgAAAC0AIC8AALcHACACAAAAtQcAIC8AALYHACAO0gIAALQHADDTAgAAtQcAENQCAAC0BwAw2wJAAMMEACHyAgEAwAQAIfMCAgDhBAAhkAMBAMAEACGRAwEAwgQAIZIDAQDCBAAhkwMBAMIEACGUAwEAwAQAIZUDAQDCBAAhlgMBAMIEACGXA0AA4wQAIQ7SAgAAtAcAMNMCAAC1BwAQ1AIAALQHADDbAkAAwwQAIfICAQDABAAh8wICAOEEACGQAwEAwAQAIZEDAQDCBAAhkgMBAMIEACGTAwEAwgQAIZQDAQDABAAhlQMBAMIEACGWAwEAwgQAIZcDQADjBAAhCtsCQACiBQAh8wICALQFACGQAwEAoQUAIZEDAQCzBQAhkgMBALMFACGTAwEAswUAIZQDAQChBQAhlQMBALMFACGWAwEAswUAIZcDQACjBQAhDggAAK4GACAJAACvBgAgDgAAsAYAIBAAALEGACDbAkAAogUAIfMCAgC0BQAhkAMBAKEFACGRAwEAswUAIZIDAQCzBQAhkwMBALMFACGUAwEAoQUAIZUDAQCzBQAhlgMBALMFACGXA0AAowUAIQ4IAAC5BwAgCQAAugcAIA4AALsHACAQAAC8BwAg2wJAAAAAAfMCAgAAAAGQAwEAAAABkQMBAAAAAZIDAQAAAAGTAwEAAAABlAMBAAAAAZUDAQAAAAGWAwEAAAABlwNAAAAAAQM2AAC9BwAg0gMAAL4HACDYAwAAGgAgBDYAAMcGADDSAwAAyAYAMNQDAADKBgAg2AMAAMsGADAENgAAvgYAMNIDAAC_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_CQAgNwAAwgkAINIDAADACQAg0wMAAMEJACDYAwAAAQAgAzYAAL8JACDSAwAAwAkAINgDAAABACAAAAAHNgAAugkAIDcAAL0JACDSAwAAuwkAINMDAAC8CQAg1gMAAA8AINcDAAAPACDYAwAAAQAgAzYAALoJACDSAwAAuwkAINgDAAABACAAAAAFNgAAtQkAIDcAALgJACDSAwAAtgkAINMDAAC3CQAg2AMAAAEAIAM2AAC1CQAg0gMAALYJACDYAwAAAQAgAAAABTYAALAJACA3AACzCQAg0gMAALEJACDTAwAAsgkAINgDAAABACADNgAAsAkAINIDAACxCQAg2AMAAAEAIAAAAAAAAdUDAAAAvQMCAdUDAAAAvwMCAdUDIAAAAAELNgAA-AgAMDcAAP0IADDSAwAA-QgAMNMDAAD6CAAw1AMAAPsIACDVAwAA_AgAMNYDAAD8CAAw1wMAAPwIADDYAwAA_AgAMNkDAAD-CAAw2gMAAP8IADALNgAA7AgAMDcAAPEIADDSAwAA7QgAMNMDAADuCAAw1AMAAO8IACDVAwAA8AgAMNYDAADwCAAw1wMAAPAIADDYAwAA8AgAMNkDAADyCAAw2gMAAPMIADALNgAA4AgAMDcAAOUIADDSAwAA4QgAMNMDAADiCAAw1AMAAOMIACDVAwAA5AgAMNYDAADkCAAw1wMAAOQIADDYAwAA5AgAMNkDAADmCAAw2gMAAOcIADALNgAA1wgAMDcAANsIADDSAwAA2AgAMNMDAADZCAAw1AMAANoIACDVAwAApQcAMNYDAAClBwAw1wMAAKUHADDYAwAApQcAMNkDAADcCAAw2gMAAKgHADALNgAAzggAMDcAANIIADDSAwAAzwgAMNMDAADQCAAw1AMAANEIACDVAwAAmQcAMNYDAACZBwAw1wMAAJkHADDYAwAAmQcAMNkDAADTCAAw2gMAAJwHADALNgAAwggAMDcAAMcIADDSAwAAwwgAMNMDAADECAAw1AMAAMUIACDVAwAAxggAMNYDAADGCAAw1wMAAMYIADDYAwAAxggAMNkDAADICAAw2gMAAMkIADALNgAAuQgAMDcAAL0IADDSAwAAuggAMNMDAAC7CAAw1AMAALwIACDVAwAAhAcAMNYDAACEBwAw1wMAAIQHADDYAwAAhAcAMNkDAAC-CAAw2gMAAIcHADALNgAAsAgAMDcAALQIADDSAwAAsQgAMNMDAACyCAAw1AMAALMIACDVAwAA-AYAMNYDAAD4BgAw1wMAAPgGADDYAwAA-AYAMNkDAAC1CAAw2gMAAPsGADALNgAApwgAMDcAAKsIADDSAwAAqAgAMNMDAACpCAAw1AMAAKoIACDVAwAA7AYAMNYDAADsBgAw1wMAAOwGADDYAwAA7AYAMNkDAACsCAAw2gMAAO8GADALNgAAnggAMDcAAKIIADDSAwAAnwgAMNMDAACgCAAw1AMAAKEIACDVAwAAygUAMNYDAADKBQAw1wMAAMoFADDYAwAAygUAMNkDAACjCAAw2gMAAM0FADALNgAAlQgAMDcAAJkIADDSAwAAlggAMNMDAACXCAAw1AMAAJgIACDVAwAAvgUAMNYDAAC-BQAw1wMAAL4FADDYAwAAvgUAMNkDAACaCAAw2gMAAMEFADALNgAAiQgAMDcAAI4IADDSAwAAiggAMNMDAACLCAAw1AMAAIwIACDVAwAAjQgAMNYDAACNCAAw1wMAAI0IADDYAwAAjQgAMNkDAACPCAAw2gMAAJAIADAK1QIBAAAAAdsCQAAAAAGHAwEAAAABowMBAAAAAaQDAQAAAAGlAwIAAAABpgMBAAAAAacDIAAAAAGoAwEAAAABqQMBAAAAAQIAAABmACA2AACUCAAgAwAAAGYAIDYAAJQIACA3AACTCAAgAS8AAK8JADAPAwAA5AQAINICAADgBAAw0wIAAGQAENQCAADgBAAw1QIBAAAAAdsCQADjBAAh7wIBAMAEACGHAwEAwgQAIaMDAQDABAAhpAMBAMAEACGlAwIA4QQAIaYDAQDABAAhpwMgAOIEACGoAwEAwgQAIakDAQDCBAAhAgAAAGYAIC8AAJMIACACAAAAkQgAIC8AAJIIACAO0gIAAJAIADDTAgAAkQgAENQCAACQCAAw1QIBAMAEACHbAkAA4wQAIe8CAQDABAAhhwMBAMIEACGjAwEAwAQAIaQDAQDABAAhpQMCAOEEACGmAwEAwAQAIacDIADiBAAhqAMBAMIEACGpAwEAwgQAIQ7SAgAAkAgAMNMCAACRCAAQ1AIAAJAIADDVAgEAwAQAIdsCQADjBAAh7wIBAMAEACGHAwEAwgQAIaMDAQDABAAhpAMBAMAEACGlAwIA4QQAIaYDAQDABAAhpwMgAOIEACGoAwEAwgQAIakDAQDCBAAhCtUCAQChBQAh2wJAAKMFACGHAwEAswUAIaMDAQChBQAhpAMBAKEFACGlAwIAtAUAIaYDAQChBQAhpwMgANIGACGoAwEAswUAIakDAQCzBQAhCtUCAQChBQAh2wJAAKMFACGHAwEAswUAIaMDAQChBQAhpAMBAKEFACGlAwIAtAUAIaYDAQChBQAhpwMgANIGACGoAwEAswUAIakDAQCzBQAhCtUCAQAAAAHbAkAAAAABhwMBAAAAAaMDAQAAAAGkAwEAAAABpQMCAAAAAaYDAQAAAAGnAyAAAAABqAMBAAAAAakDAQAAAAEDHwAAqwUAINsCQAAAAAHuAgEAAAABAgAAAE0AIDYAAJ0IACADAAAATQAgNgAAnQgAIDcAAJwIACABLwAArgkAMAIAAABNACAvAACcCAAgAgAAAMIFACAvAACbCAAgAtsCQACiBQAh7gIBAKEFACEDHwAAqQUAINsCQACiBQAh7gIBAKEFACEDHwAAqwUAINsCQAAAAAHuAgEAAAABDwcAANIFACAaAADVBQAgHQAA1gUAIB4AANQFACDVAgEAAAAB2wJAAAAAAfACAgAAAAHxAgEAAAAB8gIBAAAAAfMCAgAAAAH0AgEAAAAB9QICAAAAAfYCQAAAAAH3AkAAAAAB-AJAAAAAAQIAAABGACA2AACmCAAgAwAAAEYAIDYAAKYIACA3AAClCAAgAS8AAK0JADACAAAARgAgLwAApQgAIAIAAADOBQAgLwAApAgAIAvVAgEAoQUAIdsCQACiBQAh8AICALIFACHxAgEAswUAIfICAQChBQAh8wICALIFACH0AgEAswUAIfUCAgC0BQAh9gJAAKIFACH3AkAAowUAIfgCQACjBQAhDwcAALUFACAaAAC5BQAgHQAAtwUAIB4AALgFACDVAgEAoQUAIdsCQACiBQAh8AICALIFACHxAgEAswUAIfICAQChBQAh8wICALIFACH0AgEAswUAIfUCAgC0BQAh9gJAAKIFACH3AkAAowUAIfgCQACjBQAhDwcAANIFACAaAADVBQAgHQAA1gUAIB4AANQFACDVAgEAAAAB2wJAAAAAAfACAgAAAAHxAgEAAAAB8gIBAAAAAfMCAgAAAAH0AgEAAAAB9QICAAAAAfYCQAAAAAH3AkAAAAAB-AJAAAAAAQcHAADfBQAgL4AAAAAB1QIBAAAAAdsCQAAAAAHyAgEAAAAB9gJAAAAAAfkCAgAAAAECAAAAQQAgNgAArwgAIAMAAABBACA2AACvCAAgNwAArggAIAEvAACsCQAwAgAAAEEAIC8AAK4IACACAAAA8AYAIC8AAK0IACAGL4AAAAAB1QIBAKEFACHbAkAAogUAIfICAQCzBQAh9gJAAKIFACH5AgIAtAUAIQcHAADdBQAgL4AAAAAB1QIBAKEFACHbAkAAogUAIfICAQCzBQAh9gJAAKIFACH5AgIAtAUAIQcHAADfBQAgL4AAAAAB1QIBAAAAAdsCQAAAAAHyAgEAAAAB9gJAAAAAAfkCAgAAAAEMBwAA6AUAINUCAQAAAAHbAkAAAAAB8gIBAAAAAfMCAgAAAAH7AgAAAPsCAvwCAQAAAAH9AgEAAAAB_gIBAAAAAf8CAQAAAAGAA4AAAAABgQMBAAAAAQIAAAA8ACA2AAC4CAAgAwAAADwAIDYAALgIACA3AAC3CAAgAS8AAKsJADACAAAAPAAgLwAAtwgAIAIAAAD8BgAgLwAAtggAIAvVAgEAoQUAIdsCQACiBQAh8gIBAKEFACHzAgIAsgUAIfsCAADlBfsCIvwCAQCzBQAh_QIBALMFACH-AgEAswUAIf8CAQCzBQAhgAOAAAAAAYEDAQCzBQAhDAcAAOYFACDVAgEAoQUAIdsCQACiBQAh8gIBAKEFACHzAgIAsgUAIfsCAADlBfsCIvwCAQCzBQAh_QIBALMFACH-AgEAswUAIf8CAQCzBQAhgAOAAAAAAYEDAQCzBQAhDAcAAOgFACDVAgEAAAAB2wJAAAAAAfICAQAAAAHzAgIAAAAB-wIAAAD7AgL8AgEAAAAB_QIBAAAAAf4CAQAAAAH_AgEAAAABgAOAAAAAAYEDAQAAAAEDBwAA7wUAINsCQAAAAAHyAgEAAAABAgAAADgAIDYAAMEIACADAAAAOAAgNgAAwQgAIDcAAMAIACABLwAAqgkAMAIAAAA4ACAvAADACAAgAgAAAIgHACAvAAC_CAAgAtsCQACiBQAh8gIBAKEFACEDBwAA7QUAINsCQACiBQAh8gIBAKEFACEDBwAA7wUAINsCQAAAAAHyAgEAAAABB9UCAQAAAAHWAgEAAAAB2AIBAAAAAdkCAQAAAAHaAoAAAAAB2wJAAAAAAdwCQAAAAAECAAAAXQAgNgAAzQgAIAMAAABdACA2AADNCAAgNwAAzAgAIAEvAACpCQAwDCMAAOQEACDSAgAA5QQAMNMCAABbABDUAgAA5QQAMNUCAQAAAAHWAgEAwAQAIdcCAQDABAAh2AIBAMAEACHZAgEAwAQAIdoCAADmBAAg2wJAAMMEACHcAkAA4wQAIQIAAABdACAvAADMCAAgAgAAAMoIACAvAADLCAAgC9ICAADJCAAw0wIAAMoIABDUAgAAyQgAMNUCAQDABAAh1gIBAMAEACHXAgEAwAQAIdgCAQDABAAh2QIBAMAEACHaAgAA5gQAINsCQADDBAAh3AJAAOMEACEL0gIAAMkIADDTAgAAyggAENQCAADJCAAw1QIBAMAEACHWAgEAwAQAIdcCAQDABAAh2AIBAMAEACHZAgEAwAQAIdoCAADmBAAg2wJAAMMEACHcAkAA4wQAIQfVAgEAoQUAIdYCAQChBQAh2AIBAKEFACHZAgEAoQUAIdoCgAAAAAHbAkAAogUAIdwCQACjBQAhB9UCAQChBQAh1gIBAKEFACHYAgEAoQUAIdkCAQChBQAh2gKAAAAAAdsCQACiBQAh3AJAAKMFACEH1QIBAAAAAdYCAQAAAAHYAgEAAAAB2QIBAAAAAdoCgAAAAAHbAkAAAAAB3AJAAAAAAQUHAAD3BQAg1QIBAAAAAdsCQAAAAAHyAgEAAAABhAMAAACEAwICAAAAMgAgNgAA1ggAIAMAAAAyACA2AADWCAAgNwAA1QgAIAEvAACoCQAwAgAAADIAIC8AANUIACACAAAAnQcAIC8AANQIACAE1QIBAKEFACHbAkAAogUAIfICAQChBQAhhAMAAPQFhAMiBQcAAPUFACDVAgEAoQUAIdsCQACiBQAh8gIBAKEFACGEAwAA9AWEAyIFBwAA9wUAINUCAQAAAAHbAkAAAAAB8gIBAAAAAYQDAAAAhAMCBAcAAP8FACDbAkAAAAAB8gIBAAAAAYYDAAAAhgMCAgAAABMAIDYAAN8IACADAAAAEwAgNgAA3wgAIDcAAN4IACABLwAApwkAMAIAAAATACAvAADeCAAgAgAAAKkHACAvAADdCAAgA9sCQACiBQAh8gIBAKEFACGGAwAA_AWGAyIEBwAA_QUAINsCQACiBQAh8gIBAKEFACGGAwAA_AWGAyIEBwAA_wUAINsCQAAAAAHyAgEAAAABhgMAAACGAwIG1QIBAAAAAdsCQAAAAAH2AkAAAAABqgMBAAAAAasDAQAAAAGsA0AAAAABAgAAAA0AIDYAAOsIACADAAAADQAgNgAA6wgAIDcAAOoIACABLwAApgkAMAsDAADsBAAg0gIAAJoFADDTAgAACwAQ1AIAAJoFADDVAgEAAAAB2wJAAOMEACHvAgEAwgQAIfYCQADjBAAhqgMBAMAEACGrAwEAwAQAIawDQADDBAAhAgAAAA0AIC8AAOoIACACAAAA6AgAIC8AAOkIACAK0gIAAOcIADDTAgAA6AgAENQCAADnCAAw1QIBAMAEACHbAkAA4wQAIe8CAQDCBAAh9gJAAOMEACGqAwEAwAQAIasDAQDABAAhrANAAMMEACEK0gIAAOcIADDTAgAA6AgAENQCAADnCAAw1QIBAMAEACHbAkAA4wQAIe8CAQDCBAAh9gJAAOMEACGqAwEAwAQAIasDAQDABAAhrANAAMMEACEG1QIBAKEFACHbAkAAowUAIfYCQACjBQAhqgMBAKEFACGrAwEAoQUAIawDQACiBQAhBtUCAQChBQAh2wJAAKMFACH2AkAAowUAIaoDAQChBQAhqwMBAKEFACGsA0AAogUAIQbVAgEAAAAB2wJAAAAAAfYCQAAAAAGqAwEAAAABqwMBAAAAAawDQAAAAAEI1QIBAAAAAdsCQAAAAAH2AkAAAAAB_gIBAAAAAawDQAAAAAGtAwEAAAABrgMBAAAAAa8DAQAAAAECAAAACQAgNgAA9wgAIAMAAAAJACA2AAD3CAAgNwAA9ggAIAEvAAClCQAwDQMAAOQEACDSAgAAmwUAMNMCAAAHABDUAgAAmwUAMNUCAQAAAAHbAkAAwwQAIe8CAQDABAAh9gJAAMMEACH-AgEAwgQAIawDQADDBAAhrQMBAAAAAa4DAQDCBAAhrwMBAMIEACECAAAACQAgLwAA9ggAIAIAAAD0CAAgLwAA9QgAIAzSAgAA8wgAMNMCAAD0CAAQ1AIAAPMIADDVAgEAwAQAIdsCQADDBAAh7wIBAMAEACH2AkAAwwQAIf4CAQDCBAAhrANAAMMEACGtAwEAwAQAIa4DAQDCBAAhrwMBAMIEACEM0gIAAPMIADDTAgAA9AgAENQCAADzCAAw1QIBAMAEACHbAkAAwwQAIe8CAQDABAAh9gJAAMMEACH-AgEAwgQAIawDQADDBAAhrQMBAMAEACGuAwEAwgQAIa8DAQDCBAAhCNUCAQChBQAh2wJAAKIFACH2AkAAogUAIf4CAQCzBQAhrANAAKIFACGtAwEAoQUAIa4DAQCzBQAhrwMBALMFACEI1QIBAKEFACHbAkAAogUAIfYCQACiBQAh_gIBALMFACGsA0AAogUAIa0DAQChBQAhrgMBALMFACGvAwEAswUAIQjVAgEAAAAB2wJAAAAAAfYCQAAAAAH-AgEAAAABrANAAAAAAa0DAQAAAAGuAwEAAAABrwMBAAAAAQzVAgEAAAAB2wJAAAAAAfYCQAAAAAGwAwEAAAABsQMBAAAAAbIDAQAAAAGzAwEAAAABtANAAAAAAbUDQAAAAAG2AwEAAAABtwMBAAAAAbgDAQAAAAECAAAABQAgNgAAgwkAIAMAAAAFACA2AACDCQAgNwAAggkAIAEvAACkCQAwEQMAAOQEACDSAgAAnAUAMNMCAAADABDUAgAAnAUAMNUCAQAAAAHbAkAAwwQAIe8CAQDABAAh9gJAAMMEACGwAwEAwAQAIbEDAQDABAAhsgMBAMIEACGzAwEAwgQAIbQDQADjBAAhtQNAAOMEACG2AwEAwgQAIbcDAQDCBAAhuAMBAMIEACECAAAABQAgLwAAggkAIAIAAACACQAgLwAAgQkAIBDSAgAA_wgAMNMCAACACQAQ1AIAAP8IADDVAgEAwAQAIdsCQADDBAAh7wIBAMAEACH2AkAAwwQAIbADAQDABAAhsQMBAMAEACGyAwEAwgQAIbMDAQDCBAAhtANAAOMEACG1A0AA4wQAIbYDAQDCBAAhtwMBAMIEACG4AwEAwgQAIRDSAgAA_wgAMNMCAACACQAQ1AIAAP8IADDVAgEAwAQAIdsCQADDBAAh7wIBAMAEACH2AkAAwwQAIbADAQDABAAhsQMBAMAEACGyAwEAwgQAIbMDAQDCBAAhtANAAOMEACG1A0AA4wQAIbYDAQDCBAAhtwMBAMIEACG4AwEAwgQAIQzVAgEAoQUAIdsCQACiBQAh9gJAAKIFACGwAwEAoQUAIbEDAQChBQAhsgMBALMFACGzAwEAswUAIbQDQACjBQAhtQNAAKMFACG2AwEAswUAIbcDAQCzBQAhuAMBALMFACEM1QIBAKEFACHbAkAAogUAIfYCQACiBQAhsAMBAKEFACGxAwEAoQUAIbIDAQCzBQAhswMBALMFACG0A0AAowUAIbUDQACjBQAhtgMBALMFACG3AwEAswUAIbgDAQCzBQAhDNUCAQAAAAHbAkAAAAAB9gJAAAAAAbADAQAAAAGxAwEAAAABsgMBAAAAAbMDAQAAAAG0A0AAAAABtQNAAAAAAbYDAQAAAAG3AwEAAAABuAMBAAAAAQQ2AAD4CAAw0gMAAPkIADDUAwAA-wgAINgDAAD8CAAwBDYAAOwIADDSAwAA7QgAMNQDAADvCAAg2AMAAPAIADAENgAA4AgAMNIDAADhCAAw1AMAAOMIACDYAwAA5AgAMAQ2AADXCAAw0gMAANgIADDUAwAA2ggAINgDAAClBwAwBDYAAM4IADDSAwAAzwgAMNQDAADRCAAg2AMAAJkHADAENgAAwggAMNIDAADDCAAw1AMAAMUIACDYAwAAxggAMAQ2AAC5CAAw0gMAALoIADDUAwAAvAgAINgDAACEBwAwBDYAALAIADDSAwAAsQgAMNQDAACzCAAg2AMAAPgGADAENgAApwgAMNIDAACoCAAw1AMAAKoIACDYAwAA7AYAMAQ2AACeCAAw0gMAAJ8IADDUAwAAoQgAINgDAADKBQAwBDYAAJUIADDSAwAAlggAMNQDAACYCAAg2AMAAL4FADAENgAAiQgAMNIDAACKCAAw1AMAAIwIACDYAwAAjQgAMAAAAAAAAAAAAAAAABsEAACQCQAgBQAAkQkAIAYAAJIJACAgAACZCQAgIQAAkwkAICIAAJQJACAkAACVCQAgJQAAlgkAICYAAJcJACAnAACYCQAgKAAAmgkAICkAAJsJACDwAgAAnQUAIPgCAACdBQAghgMAAJ0FACCHAwAAnQUAILkDAACdBQAguwMAAJ0FACDBAwAAnQUAIMIDAACdBQAgwwMAAJ0FACDEAwAAnQUAIMUDAACdBQAgxgMAAJ0FACDHAwAAnQUAIMgDAACdBQAgyQMAAJ0FACAMAwAAnAkAIAcAAJ4JACAaAACaCQAgHQAAnQkAIB4AAJkJACDvAgAAnQUAIPACAACdBQAg8QIAAJ0FACDzAgAAnQUAIPQCAACdBQAg9wIAAJ0FACD4AgAAnQUAIBERAAChCQAgEgAAngkAIBMAAJ8JACAUAAChCQAgFQAAowkAIBYAAJMJACAYAACUCQAgGQAAoAkAIBoAAJYJACAbAACXCQAgHAAAmAkAICAAAJkJACDwAgAAnQUAIPgCAACdBQAgmAMAAJ0FACCZAwAAnQUAIJoDAACdBQAgAAALBwAAngkAIAgAAJ4JACAJAACfCQAgDgAAlgYAIBAAAKAJACCRAwAAnQUAIJIDAACdBQAgkwMAAJ0FACCVAwAAnQUAIJYDAACdBQAglwMAAJ0FACADCwAAlgYAIPACAACdBQAgiAMAAJ0FACAADNUCAQAAAAHbAkAAAAAB9gJAAAAAAbADAQAAAAGxAwEAAAABsgMBAAAAAbMDAQAAAAG0A0AAAAABtQNAAAAAAbYDAQAAAAG3AwEAAAABuAMBAAAAAQjVAgEAAAAB2wJAAAAAAfYCQAAAAAH-AgEAAAABrANAAAAAAa0DAQAAAAGuAwEAAAABrwMBAAAAAQbVAgEAAAAB2wJAAAAAAfYCQAAAAAGqAwEAAAABqwMBAAAAAawDQAAAAAED2wJAAAAAAfICAQAAAAGGAwAAAIYDAgTVAgEAAAAB2wJAAAAAAfICAQAAAAGEAwAAAIQDAgfVAgEAAAAB1gIBAAAAAdgCAQAAAAHZAgEAAAAB2gKAAAAAAdsCQAAAAAHcAkAAAAABAtsCQAAAAAHyAgEAAAABC9UCAQAAAAHbAkAAAAAB8gIBAAAAAfMCAgAAAAH7AgAAAPsCAvwCAQAAAAH9AgEAAAAB_gIBAAAAAf8CAQAAAAGAA4AAAAABgQMBAAAAAQYvgAAAAAHVAgEAAAAB2wJAAAAAAfICAQAAAAH2AkAAAAAB-QICAAAAAQvVAgEAAAAB2wJAAAAAAfACAgAAAAHxAgEAAAAB8gIBAAAAAfMCAgAAAAH0AgEAAAAB9QICAAAAAfYCQAAAAAH3AkAAAAAB-AJAAAAAAQLbAkAAAAAB7gIBAAAAAQrVAgEAAAAB2wJAAAAAAYcDAQAAAAGjAwEAAAABpAMBAAAAAaUDAgAAAAGmAwEAAAABpwMgAAAAAagDAQAAAAGpAwEAAAABIQUAAIUJACAGAACGCQAgIAAAjQkAICEAAIcJACAiAACICQAgJAAAiQkAICUAAIoJACAmAACLCQAgJwAAjAkAICgAAI4JACApAACPCQAg1QIBAAAAAdsCQAAAAAHwAgIAAAAB9gJAAAAAAfgCQAAAAAGGAwEAAAABhwMBAAAAAbkDAQAAAAG6AyAAAAABuwMBAAAAAb0DAAAAvQMCvwMAAAC_AwLAAyAAAAABwQMBAAAAAcIDAQAAAAHDA4AAAAABxANAAAAAAcUDAQAAAAHGAyAAAAABxwMBAAAAAcgDQAAAAAHJA0AAAAABAgAAAAEAIDYAALAJACADAAAADwAgNgAAsAkAIDcAALQJACAjAAAADwAgBQAA_gcAIAYAAP8HACAgAACGCAAgIQAAgAgAICIAAIEIACAkAACCCAAgJQAAgwgAICYAAIQIACAnAACFCAAgKAAAhwgAICkAAIgIACAvAAC0CQAg1QIBAKEFACHbAkAAogUAIfACAgCyBQAh9gJAAKIFACH4AkAAowUAIYYDAQCzBQAhhwMBALMFACG5AwEAswUAIboDIADSBgAhuwMBALMFACG9AwAA-ge9AyK_AwAA-we_AyLAAyAA0gYAIcEDAQCzBQAhwgMBALMFACHDA4AAAAABxANAAKMFACHFAwEAswUAIcYDIAD8BwAhxwMBALMFACHIA0AAowUAIckDQACjBQAhIQUAAP4HACAGAAD_BwAgIAAAhggAICEAAIAIACAiAACBCAAgJAAAgggAICUAAIMIACAmAACECAAgJwAAhQgAICgAAIcIACApAACICAAg1QIBAKEFACHbAkAAogUAIfACAgCyBQAh9gJAAKIFACH4AkAAowUAIYYDAQCzBQAhhwMBALMFACG5AwEAswUAIboDIADSBgAhuwMBALMFACG9AwAA-ge9AyK_AwAA-we_AyLAAyAA0gYAIcEDAQCzBQAhwgMBALMFACHDA4AAAAABxANAAKMFACHFAwEAswUAIcYDIAD8BwAhxwMBALMFACHIA0AAowUAIckDQACjBQAhIQQAAIQJACAGAACGCQAgIAAAjQkAICEAAIcJACAiAACICQAgJAAAiQkAICUAAIoJACAmAACLCQAgJwAAjAkAICgAAI4JACApAACPCQAg1QIBAAAAAdsCQAAAAAHwAgIAAAAB9gJAAAAAAfgCQAAAAAGGAwEAAAABhwMBAAAAAbkDAQAAAAG6AyAAAAABuwMBAAAAAb0DAAAAvQMCvwMAAAC_AwLAAyAAAAABwQMBAAAAAcIDAQAAAAHDA4AAAAABxANAAAAAAcUDAQAAAAHGAyAAAAABxwMBAAAAAcgDQAAAAAHJA0AAAAABAgAAAAEAIDYAALUJACADAAAADwAgNgAAtQkAIDcAALkJACAjAAAADwAgBAAA_QcAIAYAAP8HACAgAACGCAAgIQAAgAgAICIAAIEIACAkAACCCAAgJQAAgwgAICYAAIQIACAnAACFCAAgKAAAhwgAICkAAIgIACAvAAC5CQAg1QIBAKEFACHbAkAAogUAIfACAgCyBQAh9gJAAKIFACH4AkAAowUAIYYDAQCzBQAhhwMBALMFACG5AwEAswUAIboDIADSBgAhuwMBALMFACG9AwAA-ge9AyK_AwAA-we_AyLAAyAA0gYAIcEDAQCzBQAhwgMBALMFACHDA4AAAAABxANAAKMFACHFAwEAswUAIcYDIAD8BwAhxwMBALMFACHIA0AAowUAIckDQACjBQAhIQQAAP0HACAGAAD_BwAgIAAAhggAICEAAIAIACAiAACBCAAgJAAAgggAICUAAIMIACAmAACECAAgJwAAhQgAICgAAIcIACApAACICAAg1QIBAKEFACHbAkAAogUAIfACAgCyBQAh9gJAAKIFACH4AkAAowUAIYYDAQCzBQAhhwMBALMFACG5AwEAswUAIboDIADSBgAhuwMBALMFACG9AwAA-ge9AyK_AwAA-we_AyLAAyAA0gYAIcEDAQCzBQAhwgMBALMFACHDA4AAAAABxANAAKMFACHFAwEAswUAIcYDIAD8BwAhxwMBALMFACHIA0AAowUAIckDQACjBQAhIQQAAIQJACAFAACFCQAgIAAAjQkAICEAAIcJACAiAACICQAgJAAAiQkAICUAAIoJACAmAACLCQAgJwAAjAkAICgAAI4JACApAACPCQAg1QIBAAAAAdsCQAAAAAHwAgIAAAAB9gJAAAAAAfgCQAAAAAGGAwEAAAABhwMBAAAAAbkDAQAAAAG6AyAAAAABuwMBAAAAAb0DAAAAvQMCvwMAAAC_AwLAAyAAAAABwQMBAAAAAcIDAQAAAAHDA4AAAAABxANAAAAAAcUDAQAAAAHGAyAAAAABxwMBAAAAAcgDQAAAAAHJA0AAAAABAgAAAAEAIDYAALoJACADAAAADwAgNgAAugkAIDcAAL4JACAjAAAADwAgBAAA_QcAIAUAAP4HACAgAACGCAAgIQAAgAgAICIAAIEIACAkAACCCAAgJQAAgwgAICYAAIQIACAnAACFCAAgKAAAhwgAICkAAIgIACAvAAC-CQAg1QIBAKEFACHbAkAAogUAIfACAgCyBQAh9gJAAKIFACH4AkAAowUAIYYDAQCzBQAhhwMBALMFACG5AwEAswUAIboDIADSBgAhuwMBALMFACG9AwAA-ge9AyK_AwAA-we_AyLAAyAA0gYAIcEDAQCzBQAhwgMBALMFACHDA4AAAAABxANAAKMFACHFAwEAswUAIcYDIAD8BwAhxwMBALMFACHIA0AAowUAIckDQACjBQAhIQQAAP0HACAFAAD-BwAgIAAAhggAICEAAIAIACAiAACBCAAgJAAAgggAICUAAIMIACAmAACECAAgJwAAhQgAICgAAIcIACApAACICAAg1QIBAKEFACHbAkAAogUAIfACAgCyBQAh9gJAAKIFACH4AkAAowUAIYYDAQCzBQAhhwMBALMFACG5AwEAswUAIboDIADSBgAhuwMBALMFACG9AwAA-ge9AyK_AwAA-we_AyLAAyAA0gYAIcEDAQCzBQAhwgMBALMFACHDA4AAAAABxANAAKMFACHFAwEAswUAIcYDIAD8BwAhxwMBALMFACHIA0AAowUAIckDQACjBQAhIQQAAIQJACAFAACFCQAgBgAAhgkAICAAAI0JACAhAACHCQAgIgAAiAkAICQAAIkJACAlAACKCQAgJgAAiwkAICcAAIwJACAoAACOCQAg1QIBAAAAAdsCQAAAAAHwAgIAAAAB9gJAAAAAAfgCQAAAAAGGAwEAAAABhwMBAAAAAbkDAQAAAAG6AyAAAAABuwMBAAAAAb0DAAAAvQMCvwMAAAC_AwLAAyAAAAABwQMBAAAAAcIDAQAAAAHDA4AAAAABxANAAAAAAcUDAQAAAAHGAyAAAAABxwMBAAAAAcgDQAAAAAHJA0AAAAABAgAAAAEAIDYAAL8JACADAAAADwAgNgAAvwkAIDcAAMMJACAjAAAADwAgBAAA_QcAIAUAAP4HACAGAAD_BwAgIAAAhggAICEAAIAIACAiAACBCAAgJAAAgggAICUAAIMIACAmAACECAAgJwAAhQgAICgAAIcIACAvAADDCQAg1QIBAKEFACHbAkAAogUAIfACAgCyBQAh9gJAAKIFACH4AkAAowUAIYYDAQCzBQAhhwMBALMFACG5AwEAswUAIboDIADSBgAhuwMBALMFACG9AwAA-ge9AyK_AwAA-we_AyLAAyAA0gYAIcEDAQCzBQAhwgMBALMFACHDA4AAAAABxANAAKMFACHFAwEAswUAIcYDIAD8BwAhxwMBALMFACHIA0AAowUAIckDQACjBQAhIQQAAP0HACAFAAD-BwAgBgAA_wcAICAAAIYIACAhAACACAAgIgAAgQgAICQAAIIIACAlAACDCAAgJgAAhAgAICcAAIUIACAoAACHCAAg1QIBAKEFACHbAkAAogUAIfACAgCyBQAh9gJAAKIFACH4AkAAowUAIYYDAQCzBQAhhwMBALMFACG5AwEAswUAIboDIADSBgAhuwMBALMFACG9AwAA-ge9AyK_AwAA-we_AyLAAyAA0gYAIcEDAQCzBQAhwgMBALMFACHDA4AAAAABxANAAKMFACHFAwEAswUAIcYDIAD8BwAhxwMBALMFACHIA0AAowUAIckDQACjBQAhGhEAAM4HACASAAC_BwAgEwAAwAcAIBQAAMEHACAWAADDBwAgGAAAxAcAIBkAAMUHACAaAADGBwAgGwAAxwcAIBwAAMgHACAgAADJBwAg1QIBAAAAAdsCQAAAAAHwAgIAAAAB9gJAAAAAAfgCQAAAAAGYAwIAAAABmQMBAAAAAZoDAgAAAAGcAwAAAJwDAp0DIAAAAAGeAyAAAAABnwMCAAAAAaADAgAAAAGhAwIAAAABogMCAAAAAQIAAAAaACA2AADECQAgDwcAANkHACAIAAC5BwAgDgAAuwcAIBAAALwHACDbAkAAAAAB8gIBAAAAAfMCAgAAAAGQAwEAAAABkQMBAAAAAZIDAQAAAAGTAwEAAAABlAMBAAAAAZUDAQAAAAGWAwEAAAABlwNAAAAAAQIAAAAtACA2AADGCQAgAwAAABUAIDYAAMYJACA3AADKCQAgEQAAABUAIAcAAK0GACAIAACuBgAgDgAAsAYAIBAAALEGACAvAADKCQAg2wJAAKIFACHyAgEAoQUAIfMCAgC0BQAhkAMBAKEFACGRAwEAswUAIZIDAQCzBQAhkwMBALMFACGUAwEAoQUAIZUDAQCzBQAhlgMBALMFACGXA0AAowUAIQ8HAACtBgAgCAAArgYAIA4AALAGACAQAACxBgAg2wJAAKIFACHyAgEAoQUAIfMCAgC0BQAhkAMBAKEFACGRAwEAswUAIZIDAQCzBQAhkwMBALMFACGUAwEAoQUAIZUDAQCzBQAhlgMBALMFACGXA0AAowUAIQ8HAADZBwAgCQAAugcAIA4AALsHACAQAAC8BwAg2wJAAAAAAfICAQAAAAHzAgIAAAABkAMBAAAAAZEDAQAAAAGSAwEAAAABkwMBAAAAAZQDAQAAAAGVAwEAAAABlgMBAAAAAZcDQAAAAAECAAAALQAgNgAAywkAIA7VAgEAAAAB2wJAAAAAAfACAgAAAAH2AkAAAAAB-AJAAAAAAZgDAgAAAAGaAwIAAAABnAMAAACcAwKdAyAAAAABngMgAAAAAZ8DAgAAAAGgAwIAAAABoQMCAAAAAaIDAgAAAAEaEQAAzgcAIBIAAL8HACAUAADBBwAgFQAAwgcAIBYAAMMHACAYAADEBwAgGQAAxQcAIBoAAMYHACAbAADHBwAgHAAAyAcAICAAAMkHACDVAgEAAAAB2wJAAAAAAfACAgAAAAH2AkAAAAAB-AJAAAAAAZgDAgAAAAGZAwEAAAABmgMCAAAAAZwDAAAAnAMCnQMgAAAAAZ4DIAAAAAGfAwIAAAABoAMCAAAAAaEDAgAAAAGiAwIAAAABAgAAABoAIDYAAM4JACAK2wJAAAAAAfMCAgAAAAGQAwEAAAABkQMBAAAAAZIDAQAAAAGTAwEAAAABlAMBAAAAAZUDAQAAAAGWAwEAAAABlwNAAAAAAQPbAkAAAAAB7wIBAAAAAYYDAAAAhgMCBNUCAQAAAAHbAkAAAAABggMBAAAAAYQDAAAAhAMCBdUCAQAAAAHbAkAAAAAB-wIAAACPAwKMAwIAAAABjQMBAAAAAQLbAkAAAAAB7wIBAAAAAQvVAgEAAAAB2wJAAAAAAe8CAQAAAAHzAgIAAAAB-wIAAAD7AgL8AgEAAAAB_QIBAAAAAf4CAQAAAAH_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_BwAgEwAAwAcAIBQAAMEHACAVAADCBwAgGAAAxAcAIBkAAMUHACAaAADGBwAgGwAAxwcAIBwAAMgHACAgAADJBwAg1QIBAAAAAdsCQAAAAAHwAgIAAAAB9gJAAAAAAfgCQAAAAAGYAwIAAAABmQMBAAAAAZoDAgAAAAGcAwAAAJwDAp0DIAAAAAGeAyAAAAABnwMCAAAAAaADAgAAAAGhAwIAAAABogMCAAAAAQIAAAAaACA2AAD7CQAgAwAAAA8AIDYAAPkJACA3AAD_CQAgIwAAAA8AIAQAAP0HACAFAAD-BwAgBgAA_wcAICAAAIYIACAiAACBCAAgJAAAgggAICUAAIMIACAmAACECAAgJwAAhQgAICgAAIcIACApAACICAAgLwAA_wkAINUCAQChBQAh2wJAAKIFACHwAgIAsgUAIfYCQACiBQAh-AJAAKMFACGGAwEAswUAIYcDAQCzBQAhuQMBALMFACG6AyAA0gYAIbsDAQCzBQAhvQMAAPoHvQMivwMAAPsHvwMiwAMgANIGACHBAwEAswUAIcIDAQCzBQAhwwOAAAAAAcQDQACjBQAhxQMBALMFACHGAyAA_AcAIccDAQCzBQAhyANAAKMFACHJA0AAowUAISEEAAD9BwAgBQAA_gcAIAYAAP8HACAgAACGCAAgIgAAgQgAICQAAIIIACAlAACDCAAgJgAAhAgAICcAAIUIACAoAACHCAAgKQAAiAgAINUCAQChBQAh2wJAAKIFACHwAgIAsgUAIfYCQACiBQAh-AJAAKMFACGGAwEAswUAIYcDAQCzBQAhuQMBALMFACG6AyAA0gYAIbsDAQCzBQAhvQMAAPoHvQMivwMAAPsHvwMiwAMgANIGACHBAwEAswUAIcIDAQCzBQAhwwOAAAAAAcQDQACjBQAhxQMBALMFACHGAyAA_AcAIccDAQCzBQAhyANAAKMFACHJA0AAowUAIQMAAAAXACA2AAD7CQAgNwAAggoAIBwAAAAXACARAADUBgAgEgAA1QYAIBMAANYGACAUAADUBwAgFQAA1wYAIBgAANkGACAZAADaBgAgGgAA2wYAIBsAANwGACAcAADdBgAgIAAA3gYAIC8AAIIKACDVAgEAoQUAIdsCQACiBQAh8AICALIFACH2AkAAogUAIfgCQACjBQAhmAMCALIFACGZAwEAswUAIZoDAgCyBQAhnAMAANEGnAMinQMgANIGACGeAyAA0gYAIZ8DAgC0BQAhoAMCALQFACGhAwIAtAUAIaIDAgC0BQAhGhEAANQGACASAADVBgAgEwAA1gYAIBQAANQHACAVAADXBgAgGAAA2QYAIBkAANoGACAaAADbBgAgGwAA3AYAIBwAAN0GACAgAADeBgAg1QIBAKEFACHbAkAAogUAIfACAgCyBQAh9gJAAKIFACH4AkAAowUAIZgDAgCyBQAhmQMBALMFACGaAwIAsgUAIZwDAADRBpwDIp0DIADSBgAhngMgANIGACGfAwIAtAUAIaADAgC0BQAhoQMCALQFACGiAwIAtAUAISEEAACECQAgBQAAhQkAIAYAAIYJACAgAACNCQAgIQAAhwkAICQAAIkJACAlAACKCQAgJgAAiwkAICcAAIwJACAoAACOCQAgKQAAjwkAINUCAQAAAAHbAkAAAAAB8AICAAAAAfYCQAAAAAH4AkAAAAABhgMBAAAAAYcDAQAAAAG5AwEAAAABugMgAAAAAbsDAQAAAAG9AwAAAL0DAr8DAAAAvwMCwAMgAAAAAcEDAQAAAAHCAwEAAAABwwOAAAAAAcQDQAAAAAHFAwEAAAABxgMgAAAAAccDAQAAAAHIA0AAAAAByQNAAAAAAQIAAAABACA2AACDCgAgGhEAAM4HACASAAC_BwAgEwAAwAcAIBQAAMEHACAVAADCBwAgFgAAwwcAIBkAAMUHACAaAADGBwAgGwAAxwcAIBwAAMgHACAgAADJBwAg1QIBAAAAAdsCQAAAAAHwAgIAAAAB9gJAAAAAAfgCQAAAAAGYAwIAAAABmQMBAAAAAZoDAgAAAAGcAwAAAJwDAp0DIAAAAAGeAyAAAAABnwMCAAAAAaADAgAAAAGhAwIAAAABogMCAAAAAQIAAAAaACA2AACFCgAgAwAAAA8AIDYAAIMKACA3AACJCgAgIwAAAA8AIAQAAP0HACAFAAD-BwAgBgAA_wcAICAAAIYIACAhAACACAAgJAAAgggAICUAAIMIACAmAACECAAgJwAAhQgAICgAAIcIACApAACICAAgLwAAiQoAINUCAQChBQAh2wJAAKIFACHwAgIAsgUAIfYCQACiBQAh-AJAAKMFACGGAwEAswUAIYcDAQCzBQAhuQMBALMFACG6AyAA0gYAIbsDAQCzBQAhvQMAAPoHvQMivwMAAPsHvwMiwAMgANIGACHBAwEAswUAIcIDAQCzBQAhwwOAAAAAAcQDQACjBQAhxQMBALMFACHGAyAA_AcAIccDAQCzBQAhyANAAKMFACHJA0AAowUAISEEAAD9BwAgBQAA_gcAIAYAAP8HACAgAACGCAAgIQAAgAgAICQAAIIIACAlAACDCAAgJgAAhAgAICcAAIUIACAoAACHCAAgKQAAiAgAINUCAQChBQAh2wJAAKIFACHwAgIAsgUAIfYCQACiBQAh-AJAAKMFACGGAwEAswUAIYcDAQCzBQAhuQMBALMFACG6AyAA0gYAIbsDAQCzBQAhvQMAAPoHvQMivwMAAPsHvwMiwAMgANIGACHBAwEAswUAIcIDAQCzBQAhwwOAAAAAAcQDQACjBQAhxQMBALMFACHGAyAA_AcAIccDAQCzBQAhyANAAKMFACHJA0AAowUAIQMAAAAXACA2AACFCgAgNwAAjAoAIBwAAAAXACARAADUBgAgEgAA1QYAIBMAANYGACAUAADUBwAgFQAA1wYAIBYAANgGACAZAADaBgAgGgAA2wYAIBsAANwGACAcAADdBgAgIAAA3gYAIC8AAIwKACDVAgEAoQUAIdsCQACiBQAh8AICALIFACH2AkAAogUAIfgCQACjBQAhmAMCALIFACGZAwEAswUAIZoDAgCyBQAhnAMAANEGnAMinQMgANIGACGeAyAA0gYAIZ8DAgC0BQAhoAMCALQFACGhAwIAtAUAIaIDAgC0BQAhGhEAANQGACASAADVBgAgEwAA1gYAIBQAANQHACAVAADXBgAgFgAA2AYAIBkAANoGACAaAADbBgAgGwAA3AYAIBwAAN0GACAgAADeBgAg1QIBAKEFACHbAkAAogUAIfACAgCyBQAh9gJAAKIFACH4AkAAowUAIZgDAgCyBQAhmQMBALMFACGaAwIAsgUAIZwDAADRBpwDIp0DIADSBgAhngMgANIGACGfAwIAtAUAIaADAgC0BQAhoQMCALQFACGiAwIAtAUAISEEAACECQAgBQAAhQkAIAYAAIYJACAgAACNCQAgIQAAhwkAICIAAIgJACAkAACJCQAgJgAAiwkAICcAAIwJACAoAACOCQAgKQAAjwkAINUCAQAAAAHbAkAAAAAB8AICAAAAAfYCQAAAAAH4AkAAAAABhgMBAAAAAYcDAQAAAAG5AwEAAAABugMgAAAAAbsDAQAAAAG9AwAAAL0DAr8DAAAAvwMCwAMgAAAAAcEDAQAAAAHCAwEAAAABwwOAAAAAAcQDQAAAAAHFAwEAAAABxgMgAAAAAccDAQAAAAHIA0AAAAAByQNAAAAAAQIAAAABACA2AACNCgAgGhEAAM4HACASAAC_BwAgEwAAwAcAIBQAAMEHACAVAADCBwAgFgAAwwcAIBgAAMQHACAZAADFBwAgGwAAxwcAIBwAAMgHACAgAADJBwAg1QIBAAAAAdsCQAAAAAHwAgIAAAAB9gJAAAAAAfgCQAAAAAGYAwIAAAABmQMBAAAAAZoDAgAAAAGcAwAAAJwDAp0DIAAAAAGeAyAAAAABnwMCAAAAAaADAgAAAAGhAwIAAAABogMCAAAAAQIAAAAaACA2AACPCgAgAwAAAA8AIDYAAI0KACA3AACTCgAgIwAAAA8AIAQAAP0HACAFAAD-BwAgBgAA_wcAICAAAIYIACAhAACACAAgIgAAgQgAICQAAIIIACAmAACECAAgJwAAhQgAICgAAIcIACApAACICAAgLwAAkwoAINUCAQChBQAh2wJAAKIFACHwAgIAsgUAIfYCQACiBQAh-AJAAKMFACGGAwEAswUAIYcDAQCzBQAhuQMBALMFACG6AyAA0gYAIbsDAQCzBQAhvQMAAPoHvQMivwMAAPsHvwMiwAMgANIGACHBAwEAswUAIcIDAQCzBQAhwwOAAAAAAcQDQACjBQAhxQMBALMFACHGAyAA_AcAIccDAQCzBQAhyANAAKMFACHJA0AAowUAISEEAAD9BwAgBQAA_gcAIAYAAP8HACAgAACGCAAgIQAAgAgAICIAAIEIACAkAACCCAAgJgAAhAgAICcAAIUIACAoAACHCAAgKQAAiAgAINUCAQChBQAh2wJAAKIFACHwAgIAsgUAIfYCQACiBQAh-AJAAKMFACGGAwEAswUAIYcDAQCzBQAhuQMBALMFACG6AyAA0gYAIbsDAQCzBQAhvQMAAPoHvQMivwMAAPsHvwMiwAMgANIGACHBAwEAswUAIcIDAQCzBQAhwwOAAAAAAcQDQACjBQAhxQMBALMFACHGAyAA_AcAIccDAQCzBQAhyANAAKMFACHJA0AAowUAIQMAAAAXACA2AACPCgAgNwAAlgoAIBwAAAAXACARAADUBgAgEgAA1QYAIBMAANYGACAUAADUBwAgFQAA1wYAIBYAANgGACAYAADZBgAgGQAA2gYAIBsAANwGACAcAADdBgAgIAAA3gYAIC8AAJYKACDVAgEAoQUAIdsCQACiBQAh8AICALIFACH2AkAAogUAIfgCQACjBQAhmAMCALIFACGZAwEAswUAIZoDAgCyBQAhnAMAANEGnAMinQMgANIGACGeAyAA0gYAIZ8DAgC0BQAhoAMCALQFACGhAwIAtAUAIaIDAgC0BQAhGhEAANQGACASAADVBgAgEwAA1gYAIBQAANQHACAVAADXBgAgFgAA2AYAIBgAANkGACAZAADaBgAgGwAA3AYAIBwAAN0GACAgAADeBgAg1QIBAKEFACHbAkAAogUAIfACAgCyBQAh9gJAAKIFACH4AkAAowUAIZgDAgCyBQAhmQMBALMFACGaAwIAsgUAIZwDAADRBpwDIp0DIADSBgAhngMgANIGACGfAwIAtAUAIaADAgC0BQAhoQMCALQFACGiAwIAtAUAISEEAACECQAgBQAAhQkAIAYAAIYJACAgAACNCQAgIQAAhwkAICIAAIgJACAkAACJCQAgJQAAigkAICcAAIwJACAoAACOCQAgKQAAjwkAINUCAQAAAAHbAkAAAAAB8AICAAAAAfYCQAAAAAH4AkAAAAABhgMBAAAAAYcDAQAAAAG5AwEAAAABugMgAAAAAbsDAQAAAAG9AwAAAL0DAr8DAAAAvwMCwAMgAAAAAcEDAQAAAAHCAwEAAAABwwOAAAAAAcQDQAAAAAHFAwEAAAABxgMgAAAAAccDAQAAAAHIA0AAAAAByQNAAAAAAQIAAAABACA2AACXCgAgGhEAAM4HACASAAC_BwAgEwAAwAcAIBQAAMEHACAVAADCBwAgFgAAwwcAIBgAAMQHACAZAADFBwAgGgAAxgcAIBwAAMgHACAgAADJBwAg1QIBAAAAAdsCQAAAAAHwAgIAAAAB9gJAAAAAAfgCQAAAAAGYAwIAAAABmQMBAAAAAZoDAgAAAAGcAwAAAJwDAp0DIAAAAAGeAyAAAAABnwMCAAAAAaADAgAAAAGhAwIAAAABogMCAAAAAQIAAAAaACA2AACZCgAgAwAAAA8AIDYAAJcKACA3AACdCgAgIwAAAA8AIAQAAP0HACAFAAD-BwAgBgAA_wcAICAAAIYIACAhAACACAAgIgAAgQgAICQAAIIIACAlAACDCAAgJwAAhQgAICgAAIcIACApAACICAAgLwAAnQoAINUCAQChBQAh2wJAAKIFACHwAgIAsgUAIfYCQACiBQAh-AJAAKMFACGGAwEAswUAIYcDAQCzBQAhuQMBALMFACG6AyAA0gYAIbsDAQCzBQAhvQMAAPoHvQMivwMAAPsHvwMiwAMgANIGACHBAwEAswUAIcIDAQCzBQAhwwOAAAAAAcQDQACjBQAhxQMBALMFACHGAyAA_AcAIccDAQCzBQAhyANAAKMFACHJA0AAowUAISEEAAD9BwAgBQAA_gcAIAYAAP8HACAgAACGCAAgIQAAgAgAICIAAIEIACAkAACCCAAgJQAAgwgAICcAAIUIACAoAACHCAAgKQAAiAgAINUCAQChBQAh2wJAAKIFACHwAgIAsgUAIfYCQACiBQAh-AJAAKMFACGGAwEAswUAIYcDAQCzBQAhuQMBALMFACG6AyAA0gYAIbsDAQCzBQAhvQMAAPoHvQMivwMAAPsHvwMiwAMgANIGACHBAwEAswUAIcIDAQCzBQAhwwOAAAAAAcQDQACjBQAhxQMBALMFACHGAyAA_AcAIccDAQCzBQAhyANAAKMFACHJA0AAowUAIQMAAAAXACA2AACZCgAgNwAAoAoAIBwAAAAXACARAADUBgAgEgAA1QYAIBMAANYGACAUAADUBwAgFQAA1wYAIBYAANgGACAYAADZBgAgGQAA2gYAIBoAANsGACAcAADdBgAgIAAA3gYAIC8AAKAKACDVAgEAoQUAIdsCQACiBQAh8AICALIFACH2AkAAogUAIfgCQACjBQAhmAMCALIFACGZAwEAswUAIZoDAgCyBQAhnAMAANEGnAMinQMgANIGACGeAyAA0gYAIZ8DAgC0BQAhoAMCALQFACGhAwIAtAUAIaIDAgC0BQAhGhEAANQGACASAADVBgAgEwAA1gYAIBQAANQHACAVAADXBgAgFgAA2AYAIBgAANkGACAZAADaBgAgGgAA2wYAIBwAAN0GACAgAADeBgAg1QIBAKEFACHbAkAAogUAIfACAgCyBQAh9gJAAKIFACH4AkAAowUAIZgDAgCyBQAhmQMBALMFACGaAwIAsgUAIZwDAADRBpwDIp0DIADSBgAhngMgANIGACGfAwIAtAUAIaADAgC0BQAhoQMCALQFACGiAwIAtAUAIRoRAADOBwAgEgAAvwcAIBMAAMAHACAUAADBBwAgFQAAwgcAIBYAAMMHACAYAADEBwAgGQAAxQcAIBoAAMYHACAbAADHBwAgIAAAyQcAINUCAQAAAAHbAkAAAAAB8AICAAAAAfYCQAAAAAH4AkAAAAABmAMCAAAAAZkDAQAAAAGaAwIAAAABnAMAAACcAwKdAyAAAAABngMgAAAAAZ8DAgAAAAGgAwIAAAABoQMCAAAAAaIDAgAAAAECAAAAGgAgNgAAoQoAICEEAACECQAgBQAAhQkAIAYAAIYJACAgAACNCQAgIQAAhwkAICIAAIgJACAkAACJCQAgJQAAigkAICYAAIsJACAoAACOCQAgKQAAjwkAINUCAQAAAAHbAkAAAAAB8AICAAAAAfYCQAAAAAH4AkAAAAABhgMBAAAAAYcDAQAAAAG5AwEAAAABugMgAAAAAbsDAQAAAAG9AwAAAL0DAr8DAAAAvwMCwAMgAAAAAcEDAQAAAAHCAwEAAAABwwOAAAAAAcQDQAAAAAHFAwEAAAABxgMgAAAAAccDAQAAAAHIA0AAAAAByQNAAAAAAQIAAAABACA2AACjCgAgAwAAABcAIDYAAKEKACA3AACnCgAgHAAAABcAIBEAANQGACASAADVBgAgEwAA1gYAIBQAANQHACAVAADXBgAgFgAA2AYAIBgAANkGACAZAADaBgAgGgAA2wYAIBsAANwGACAgAADeBgAgLwAApwoAINUCAQChBQAh2wJAAKIFACHwAgIAsgUAIfYCQACiBQAh-AJAAKMFACGYAwIAsgUAIZkDAQCzBQAhmgMCALIFACGcAwAA0QacAyKdAyAA0gYAIZ4DIADSBgAhnwMCALQFACGgAwIAtAUAIaEDAgC0BQAhogMCALQFACEaEQAA1AYAIBIAANUGACATAADWBgAgFAAA1AcAIBUAANcGACAWAADYBgAgGAAA2QYAIBkAANoGACAaAADbBgAgGwAA3AYAICAAAN4GACDVAgEAoQUAIdsCQACiBQAh8AICALIFACH2AkAAogUAIfgCQACjBQAhmAMCALIFACGZAwEAswUAIZoDAgCyBQAhnAMAANEGnAMinQMgANIGACGeAyAA0gYAIZ8DAgC0BQAhoAMCALQFACGhAwIAtAUAIaIDAgC0BQAhAwAAAA8AIDYAAKMKACA3AACqCgAgIwAAAA8AIAQAAP0HACAFAAD-BwAgBgAA_wcAICAAAIYIACAhAACACAAgIgAAgQgAICQAAIIIACAlAACDCAAgJgAAhAgAICgAAIcIACApAACICAAgLwAAqgoAINUCAQChBQAh2wJAAKIFACHwAgIAsgUAIfYCQACiBQAh-AJAAKMFACGGAwEAswUAIYcDAQCzBQAhuQMBALMFACG6AyAA0gYAIbsDAQCzBQAhvQMAAPoHvQMivwMAAPsHvwMiwAMgANIGACHBAwEAswUAIcIDAQCzBQAhwwOAAAAAAcQDQACjBQAhxQMBALMFACHGAyAA_AcAIccDAQCzBQAhyANAAKMFACHJA0AAowUAISEEAAD9BwAgBQAA_gcAIAYAAP8HACAgAACGCAAgIQAAgAgAICIAAIEIACAkAACCCAAgJQAAgwgAICYAAIQIACAoAACHCAAgKQAAiAgAINUCAQChBQAh2wJAAKIFACHwAgIAsgUAIfYCQACiBQAh-AJAAKMFACGGAwEAswUAIYcDAQCzBQAhuQMBALMFACG6AyAA0gYAIbsDAQCzBQAhvQMAAPoHvQMivwMAAPsHvwMiwAMgANIGACHBAwEAswUAIcIDAQCzBQAhwwOAAAAAAcQDQACjBQAhxQMBALMFACHGAyAA_AcAIccDAQCzBQAhyANAAKMFACHJA0AAowUAIRADAADTBQAgBwAA0gUAIBoAANUFACAdAADWBQAg1QIBAAAAAdsCQAAAAAHvAgEAAAAB8AICAAAAAfECAQAAAAHyAgEAAAAB8wICAAAAAfQCAQAAAAH1AgIAAAAB9gJAAAAAAfcCQAAAAAH4AkAAAAABAgAAAEYAIDYAAKsKACAhBAAAhAkAIAUAAIUJACAGAACGCQAgIQAAhwkAICIAAIgJACAkAACJCQAgJQAAigkAICYAAIsJACAnAACMCQAgKAAAjgkAICkAAI8JACDVAgEAAAAB2wJAAAAAAfACAgAAAAH2AkAAAAAB-AJAAAAAAYYDAQAAAAGHAwEAAAABuQMBAAAAAboDIAAAAAG7AwEAAAABvQMAAAC9AwK_AwAAAL8DAsADIAAAAAHBAwEAAAABwgMBAAAAAcMDgAAAAAHEA0AAAAABxQMBAAAAAcYDIAAAAAHHAwEAAAAByANAAAAAAckDQAAAAAECAAAAAQAgNgAArQoAIBoRAADOBwAgEgAAvwcAIBMAAMAHACAUAADBBwAgFQAAwgcAIBYAAMMHACAYAADEBwAgGQAAxQcAIBoAAMYHACAbAADHBwAgHAAAyAcAINUCAQAAAAHbAkAAAAAB8AICAAAAAfYCQAAAAAH4AkAAAAABmAMCAAAAAZkDAQAAAAGaAwIAAAABnAMAAACcAwKdAyAAAAABngMgAAAAAZ8DAgAAAAGgAwIAAAABoQMCAAAAAaIDAgAAAAECAAAAGgAgNgAArwoAIAvVAgEAAAAB2wJAAAAAAe8CAQAAAAHwAgIAAAAB8gIBAAAAAfMCAgAAAAH0AgEAAAAB9QICAAAAAfYCQAAAAAH3AkAAAAAB-AJAAAAAAQLbAkAAAAAB7wIBAAAAAQMAAABEACA2AACrCgAgNwAAtQoAIBIAAABEACADAAC2BQAgBwAAtQUAIBoAALkFACAdAAC3BQAgLwAAtQoAINUCAQChBQAh2wJAAKIFACHvAgEAswUAIfACAgCyBQAh8QIBALMFACHyAgEAoQUAIfMCAgCyBQAh9AIBALMFACH1AgIAtAUAIfYCQACiBQAh9wJAAKMFACH4AkAAowUAIRADAAC2BQAgBwAAtQUAIBoAALkFACAdAAC3BQAg1QIBAKEFACHbAkAAogUAIe8CAQCzBQAh8AICALIFACHxAgEAswUAIfICAQChBQAh8wICALIFACH0AgEAswUAIfUCAgC0BQAh9gJAAKIFACH3AkAAowUAIfgCQACjBQAhAwAAAA8AIDYAAK0KACA3AAC4CgAgIwAAAA8AIAQAAP0HACAFAAD-BwAgBgAA_wcAICEAAIAIACAiAACBCAAgJAAAgggAICUAAIMIACAmAACECAAgJwAAhQgAICgAAIcIACApAACICAAgLwAAuAoAINUCAQChBQAh2wJAAKIFACHwAgIAsgUAIfYCQACiBQAh-AJAAKMFACGGAwEAswUAIYcDAQCzBQAhuQMBALMFACG6AyAA0gYAIbsDAQCzBQAhvQMAAPoHvQMivwMAAPsHvwMiwAMgANIGACHBAwEAswUAIcIDAQCzBQAhwwOAAAAAAcQDQACjBQAhxQMBALMFACHGAyAA_AcAIccDAQCzBQAhyANAAKMFACHJA0AAowUAISEEAAD9BwAgBQAA_gcAIAYAAP8HACAhAACACAAgIgAAgQgAICQAAIIIACAlAACDCAAgJgAAhAgAICcAAIUIACAoAACHCAAgKQAAiAgAINUCAQChBQAh2wJAAKIFACHwAgIAsgUAIfYCQACiBQAh-AJAAKMFACGGAwEAswUAIYcDAQCzBQAhuQMBALMFACG6AyAA0gYAIbsDAQCzBQAhvQMAAPoHvQMivwMAAPsHvwMiwAMgANIGACHBAwEAswUAIcIDAQCzBQAhwwOAAAAAAcQDQACjBQAhxQMBALMFACHGAyAA_AcAIccDAQCzBQAhyANAAKMFACHJA0AAowUAIQMAAAAXACA2AACvCgAgNwAAuwoAIBwAAAAXACARAADUBgAgEgAA1QYAIBMAANYGACAUAADUBwAgFQAA1wYAIBYAANgGACAYAADZBgAgGQAA2gYAIBoAANsGACAbAADcBgAgHAAA3QYAIC8AALsKACDVAgEAoQUAIdsCQACiBQAh8AICALIFACH2AkAAogUAIfgCQACjBQAhmAMCALIFACGZAwEAswUAIZoDAgCyBQAhnAMAANEGnAMinQMgANIGACGeAyAA0gYAIZ8DAgC0BQAhoAMCALQFACGhAwIAtAUAIaIDAgC0BQAhGhEAANQGACASAADVBgAgEwAA1gYAIBQAANQHACAVAADXBgAgFgAA2AYAIBgAANkGACAZAADaBgAgGgAA2wYAIBsAANwGACAcAADdBgAg1QIBAKEFACHbAkAAogUAIfACAgCyBQAh9gJAAKIFACH4AkAAowUAIZgDAgCyBQAhmQMBALMFACGaAwIAsgUAIZwDAADRBpwDIp0DIADSBgAhngMgANIGACGfAwIAtAUAIaADAgC0BQAhoQMCALQFACGiAwIAtAUAISEEAACECQAgBQAAhQkAIAYAAIYJACAgAACNCQAgIQAAhwkAICIAAIgJACAkAACJCQAgJQAAigkAICYAAIsJACAnAACMCQAgKQAAjwkAINUCAQAAAAHbAkAAAAAB8AICAAAAAfYCQAAAAAH4AkAAAAABhgMBAAAAAYcDAQAAAAG5AwEAAAABugMgAAAAAbsDAQAAAAG9AwAAAL0DAr8DAAAAvwMCwAMgAAAAAcEDAQAAAAHCAwEAAAABwwOAAAAAAcQDQAAAAAHFAwEAAAABxgMgAAAAAccDAQAAAAHIA0AAAAAByQNAAAAAAQIAAAABACA2AAC8CgAgEAMAANMFACAHAADSBQAgHQAA1gUAIB4AANQFACDVAgEAAAAB2wJAAAAAAe8CAQAAAAHwAgIAAAAB8QIBAAAAAfICAQAAAAHzAgIAAAAB9AIBAAAAAfUCAgAAAAH2AkAAAAAB9wJAAAAAAfgCQAAAAAECAAAARgAgNgAAvgoAIAMAAAAPACA2AAC8CgAgNwAAwgoAICMAAAAPACAEAAD9BwAgBQAA_gcAIAYAAP8HACAgAACGCAAgIQAAgAgAICIAAIEIACAkAACCCAAgJQAAgwgAICYAAIQIACAnAACFCAAgKQAAiAgAIC8AAMIKACDVAgEAoQUAIdsCQACiBQAh8AICALIFACH2AkAAogUAIfgCQACjBQAhhgMBALMFACGHAwEAswUAIbkDAQCzBQAhugMgANIGACG7AwEAswUAIb0DAAD6B70DIr8DAAD7B78DIsADIADSBgAhwQMBALMFACHCAwEAswUAIcMDgAAAAAHEA0AAowUAIcUDAQCzBQAhxgMgAPwHACHHAwEAswUAIcgDQACjBQAhyQNAAKMFACEhBAAA_QcAIAUAAP4HACAGAAD_BwAgIAAAhggAICEAAIAIACAiAACBCAAgJAAAgggAICUAAIMIACAmAACECAAgJwAAhQgAICkAAIgIACDVAgEAoQUAIdsCQACiBQAh8AICALIFACH2AkAAogUAIfgCQACjBQAhhgMBALMFACGHAwEAswUAIbkDAQCzBQAhugMgANIGACG7AwEAswUAIb0DAAD6B70DIr8DAAD7B78DIsADIADSBgAhwQMBALMFACHCAwEAswUAIcMDgAAAAAHEA0AAowUAIcUDAQCzBQAhxgMgAPwHACHHAwEAswUAIcgDQACjBQAhyQNAAKMFACEDAAAARAAgNgAAvgoAIDcAAMUKACASAAAARAAgAwAAtgUAIAcAALUFACAdAAC3BQAgHgAAuAUAIC8AAMUKACDVAgEAoQUAIdsCQACiBQAh7wIBALMFACHwAgIAsgUAIfECAQCzBQAh8gIBAKEFACHzAgIAsgUAIfQCAQCzBQAh9QICALQFACH2AkAAogUAIfcCQACjBQAh-AJAAKMFACEQAwAAtgUAIAcAALUFACAdAAC3BQAgHgAAuAUAINUCAQChBQAh2wJAAKIFACHvAgEAswUAIfACAgCyBQAh8QIBALMFACHyAgEAoQUAIfMCAgCyBQAh9AIBALMFACH1AgIAtAUAIfYCQACiBQAh9wJAAKMFACH4AkAAowUAISEEAACECQAgBQAAhQkAIAYAAIYJACAgAACNCQAgIQAAhwkAICIAAIgJACAlAACKCQAgJgAAiwkAICcAAIwJACAoAACOCQAgKQAAjwkAINUCAQAAAAHbAkAAAAAB8AICAAAAAfYCQAAAAAH4AkAAAAABhgMBAAAAAYcDAQAAAAG5AwEAAAABugMgAAAAAbsDAQAAAAG9AwAAAL0DAr8DAAAAvwMCwAMgAAAAAcEDAQAAAAHCAwEAAAABwwOAAAAAAcQDQAAAAAHFAwEAAAABxgMgAAAAAccDAQAAAAHIA0AAAAAByQNAAAAAAQIAAAABACA2AADGCgAgAwAAAA8AIDYAAMYKACA3AADKCgAgIwAAAA8AIAQAAP0HACAFAAD-BwAgBgAA_wcAICAAAIYIACAhAACACAAgIgAAgQgAICUAAIMIACAmAACECAAgJwAAhQgAICgAAIcIACApAACICAAgLwAAygoAINUCAQChBQAh2wJAAKIFACHwAgIAsgUAIfYCQACiBQAh-AJAAKMFACGGAwEAswUAIYcDAQCzBQAhuQMBALMFACG6AyAA0gYAIbsDAQCzBQAhvQMAAPoHvQMivwMAAPsHvwMiwAMgANIGACHBAwEAswUAIcIDAQCzBQAhwwOAAAAAAcQDQACjBQAhxQMBALMFACHGAyAA_AcAIccDAQCzBQAhyANAAKMFACHJA0AAowUAISEEAAD9BwAgBQAA_gcAIAYAAP8HACAgAACGCAAgIQAAgAgAICIAAIEIACAlAACDCAAgJgAAhAgAICcAAIUIACAoAACHCAAgKQAAiAgAINUCAQChBQAh2wJAAKIFACHwAgIAsgUAIfYCQACiBQAh-AJAAKMFACGGAwEAswUAIYcDAQCzBQAhuQMBALMFACG6AyAA0gYAIbsDAQCzBQAhvQMAAPoHvQMivwMAAPsHvwMiwAMgANIGACHBAwEAswUAIcIDAQCzBQAhwwOAAAAAAcQDQACjBQAhxQMBALMFACHGAyAA_AcAIccDAQCzBQAhyANAAKMFACHJA0AAowUAIQ0EBgIFCgMGDgQMABcgYhEhFAUiWg0kXhUlXw4mYA8nYRAoYxIpZxYBAwABAQMAAQEDEAECAwABBwAGDQwAFBEWBxIpBhMqBhQrBxUuBxYvBRgzDRk1Cxo5Dhs9DxxCECBHEQYHAAYIGAYJGwYMAAwOHwgQJQsCCgAHDQAJAgsgCAwACgELIQACBwAGDwAHAwkmAA4nABAoAAIHAAYXNAECAwABBwAGAgM-AQcABgIDAAEHQwYGA0gBBwAGDAATGk4SHUkRHkoRAgMAAR8AEQIaUAAeTwAJE1EAFVIAFlMAGFQAGVUAGlYAG1cAHFgAIFkAASMAAQEDAAEMBGgABWkABmoAIHEAIWsAImwAJG0AJW4AJm8AJ3AAKHIAKXMAAAAABQwAHDwAHT0AHj4AHz8AIAAAAAAABQwAHDwAHT0AHj4AHz8AIAEDAAEBAwABAwwAJT4AJj8AJwAAAAMMACU-ACY_ACcBAwABAQMAAQMMACw-AC0_AC4AAAADDAAsPgAtPwAuAQPAAQEBA8YBAQMMADM-ADQ_ADUAAAADDAAzPgA0PwA1AQMAAQEDAAEFDAA6PAA7PQA8PgA9PwA-AAAAAAAFDAA6PAA7PQA8PgA9PwA-AxHuAQcS7wEGFPABBwMR9gEHEvcBBhT4AQcFDABDPABEPQBFPgBGPwBHAAAAAAAFDABDPABEPQBFPgBGPwBHAQcABgEHAAYFDABMPABNPQBOPgBPPwBQAAAAAAAFDABMPABNPQBOPgBPPwBQAgoABw0ACQIKAAcNAAkFDABVPABWPQBXPgBYPwBZAAAAAAAFDABVPABWPQBXPgBYPwBZAgcABg8ABwIHAAYPAAcFDABePABfPQBgPgBhPwBiAAAAAAAFDABePABfPQBgPgBhPwBiAAAFDABnPABoPQBpPgBqPwBrAAAAAAAFDABnPABoPQBpPgBqPwBrAgMAAQcABgIDAAEHAAYDDABwPgBxPwByAAAAAwwAcD4AcT8AcgIHAAYX-wIBAgcABheBAwEDDAB3PgB4PwB5AAAAAwwAdz4AeD8AeQIDAAEHAAYCAwABBwAGAwwAfj4Afz8AgAEAAAADDAB-PgB_PwCAAQIDqQMBBwAGAgOvAwEHAAYFDACFATwAhgE9AIcBPgCIAT8AiQEAAAAAAAUMAIUBPACGAT0AhwE-AIgBPwCJAQIDAAEHwQMGAgMAAQfHAwYFDACOATwAjwE9AJABPgCRAT8AkgEAAAAAAAUMAI4BPACPAT0AkAE-AJEBPwCSAQMD2QMBBwAGHdoDEQMD4AMBBwAGHeEDEQUMAJcBPACYAT0AmQE-AJoBPwCbAQAAAAAABQwAlwE8AJgBPQCZAT4AmgE_AJsBAgMAAR8AEQIDAAEfABEDDACgAT4AoQE_AKIBAAAAAwwAoAE-AKEBPwCiAQEjAAEBIwABAwwApwE-AKgBPwCpAQAAAAMMAKcBPgCoAT8AqQEqAgErdAEsdgEtdwEueAEwegExfBgyfRkzfwE0gQEYNYIBGjiDAQE5hAEBOoUBGECIARtBiQEhQooBAkOLAQJEjAECRY0BAkaOAQJHkAECSJIBGEmTASJKlQECS5cBGEyYASNNmQECTpoBAk-bARhQngEkUZ8BKFKgAQNToQEDVKIBA1WjAQNWpAEDV6YBA1ioARhZqQEpWqsBA1utARhcrgEqXa8BA16wAQNfsQEYYLQBK2G1AS9itgEEY7cBBGS4AQRluQEEZroBBGe8AQRovgEYab8BMGrCAQRrxAEYbMUBMW3HAQRuyAEEb8kBGHDMATJxzQE2cs4BFnPPARZ00AEWddEBFnbSARZ31AEWeNYBGHnXATd62QEWe9sBGHzcATh93QEWft4BFn_fARiAAeIBOYEB4wE_ggHkAQaDAeUBBoQB5gEGhQHnAQaGAegBBocB6gEGiAHsARiJAe0BQIoB8gEGiwH0ARiMAfUBQY0B-QEGjgH6AQaPAfsBGJAB_gFCkQH_AUiSAYACB5MBgQIHlAGCAgeVAYMCB5YBhAIHlwGGAgeYAYgCGJkBiQJJmgGLAgebAY0CGJwBjgJKnQGPAgeeAZACB58BkQIYoAGUAkuhAZUCUaIBlgIIowGXAgikAZgCCKUBmQIIpgGaAginAZwCCKgBngIYqQGfAlKqAaECCKsBowIYrAGkAlOtAaUCCK4BpgIIrwGnAhiwAaoCVLEBqwJasgGsAguzAa0CC7QBrgILtQGvAgu2AbACC7cBsgILuAG0Ahi5AbUCW7oBtwILuwG5Ahi8AboCXL0BuwILvgG8Agu_Ab0CGMABwAJdwQHBAmPCAcMCCcMBxAIJxAHHAgnFAcgCCcYByQIJxwHLAgnIAc0CGMkBzgJkygHQAgnLAdICGMwB0wJlzQHUAgnOAdUCCc8B1gIY0AHZAmbRAdoCbNIB2wIF0wHcAgXUAd0CBdUB3gIF1gHfAgXXAeECBdgB4wIY2QHkAm3aAeYCBdsB6AIY3AHpAm7dAeoCBd4B6wIF3wHsAhjgAe8Cb-EB8AJz4gHxAg3jAfICDeQB8wIN5QH0Ag3mAfUCDecB9wIN6AH5AhjpAfoCdOoB_QIN6wH_AhjsAYADde0BggMN7gGDAw3vAYQDGPABhwN28QGIA3ryAYkDDvMBigMO9AGLAw71AYwDDvYBjQMO9wGPAw74AZEDGPkBkgN7-gGUAw77AZYDGPwBlwN8_QGYAw7-AZkDDv8BmgMYgAKdA32BAp4DgQGCAp8DD4MCoAMPhAKhAw-FAqIDD4YCowMPhwKlAw-IAqcDGIkCqAOCAYoCqwMPiwKtAxiMAq4DgwGNArADD44CsQMPjwKyAxiQArUDhAGRArYDigGSArcDEJMCuAMQlAK5AxCVAroDEJYCuwMQlwK9AxCYAr8DGJkCwAOLAZoCwwMQmwLFAxicAsYDjAGdAsgDEJ4CyQMQnwLKAxigAs0DjQGhAs4DkwGiAs8DEaMC0AMRpALRAxGlAtIDEaYC0wMRpwLVAxGoAtcDGKkC2AOUAaoC3AMRqwLeAxisAt8DlQGtAuIDEa4C4wMRrwLkAxiwAucDlgGxAugDnAGyAukDErMC6gMStALrAxK1AuwDErYC7QMStwLvAxK4AvEDGLkC8gOdAboC9AMSuwL2Axi8AvcDngG9AvgDEr4C-QMSvwL6AxjAAv0DnwHBAv4DowHCAv8DFcMCgAQVxAKBBBXFAoIEFcYCgwQVxwKFBBXIAocEGMkCiASkAcoCigQVywKMBBjMAo0EpQHNAo4EFc4CjwQVzwKQBBjQApMEpgHRApQEqgE" } config.compilerWasm = { getRuntime: async () => require('./query_compiler_fast_bg.js'), diff --git a/apps/modeling-commons-backend/generated/prisma/index-browser.js b/apps/modeling-commons-backend/generated/prisma/index-browser.js index 336afaf2..47e49635 100644 --- a/apps/modeling-commons-backend/generated/prisma/index-browser.js +++ b/apps/modeling-commons-backend/generated/prisma/index-browser.js @@ -299,6 +299,27 @@ exports.Prisma.ModelDraftScalarFieldEnum = { updatedAt: 'updatedAt' }; +exports.Prisma.ModelCommentScalarFieldEnum = { + id: 'id', + legacyId: 'legacyId', + parentId: 'parentId', + userId: 'userId', + modelId: 'modelId', + versionNumber: 'versionNumber', + content: 'content', + likesCount: 'likesCount', + createdAt: 'createdAt', + updatedAt: 'updatedAt', + editedAt: 'editedAt', + deletedAt: 'deletedAt' +}; + +exports.Prisma.ModelCommentLikeScalarFieldEnum = { + modelCommentId: 'modelCommentId', + userId: 'userId', + createdAt: 'createdAt' +}; + exports.Prisma.EventScalarFieldEnum = { id: 'id', type: 'type', @@ -397,6 +418,8 @@ exports.Prisma.ModelName = { ModelLike: 'ModelLike', ModelInteraction: 'ModelInteraction', ModelDraft: 'ModelDraft', + ModelComment: 'ModelComment', + ModelCommentLike: 'ModelCommentLike', Event: 'Event' }; diff --git a/apps/modeling-commons-backend/generated/prisma/index.d.ts b/apps/modeling-commons-backend/generated/prisma/index.d.ts index 19fb5cd9..9b83f918 100644 --- a/apps/modeling-commons-backend/generated/prisma/index.d.ts +++ b/apps/modeling-commons-backend/generated/prisma/index.d.ts @@ -88,6 +88,16 @@ export type ModelInteraction = $Result.DefaultSelection +/** + * Model ModelComment + * + */ +export type ModelComment = $Result.DefaultSelection +/** + * Model ModelCommentLike + * + */ +export type ModelCommentLike = $Result.DefaultSelection /** * Model Event * @@ -461,6 +471,26 @@ export class PrismaClient< */ get modelDraft(): Prisma.ModelDraftDelegate; + /** + * `prisma.modelComment`: Exposes CRUD operations for the **ModelComment** model. + * Example usage: + * ```ts + * // Fetch zero or more ModelComments + * const modelComments = await prisma.modelComment.findMany() + * ``` + */ + get modelComment(): Prisma.ModelCommentDelegate; + + /** + * `prisma.modelCommentLike`: Exposes CRUD operations for the **ModelCommentLike** model. + * Example usage: + * ```ts + * // Fetch zero or more ModelCommentLikes + * const modelCommentLikes = await prisma.modelCommentLike.findMany() + * ``` + */ + get modelCommentLike(): Prisma.ModelCommentLikeDelegate; + /** * `prisma.event`: Exposes CRUD operations for the **Event** model. * Example usage: @@ -919,6 +949,8 @@ export namespace Prisma { ModelLike: 'ModelLike', ModelInteraction: 'ModelInteraction', ModelDraft: 'ModelDraft', + ModelComment: 'ModelComment', + ModelCommentLike: 'ModelCommentLike', Event: 'Event' }; @@ -935,7 +967,7 @@ export namespace Prisma { omit: GlobalOmitOptions } meta: { - modelProps: "user" | "account" | "session" | "verification" | "passkey" | "model" | "modelVersion" | "modelVersionTag" | "modelAdditionalFile" | "tag" | "modelAuthor" | "modelPermission" | "modelLike" | "modelInteraction" | "modelDraft" | "event" + modelProps: "user" | "account" | "session" | "verification" | "passkey" | "model" | "modelVersion" | "modelVersionTag" | "modelAdditionalFile" | "tag" | "modelAuthor" | "modelPermission" | "modelLike" | "modelInteraction" | "modelDraft" | "modelComment" | "modelCommentLike" | "event" txIsolationLevel: Prisma.TransactionIsolationLevel } model: { @@ -2049,6 +2081,154 @@ export namespace Prisma { } } } + ModelComment: { + payload: Prisma.$ModelCommentPayload + fields: Prisma.ModelCommentFieldRefs + operations: { + findUnique: { + args: Prisma.ModelCommentFindUniqueArgs + result: $Utils.PayloadToResult | null + } + findUniqueOrThrow: { + args: Prisma.ModelCommentFindUniqueOrThrowArgs + result: $Utils.PayloadToResult + } + findFirst: { + args: Prisma.ModelCommentFindFirstArgs + result: $Utils.PayloadToResult | null + } + findFirstOrThrow: { + args: Prisma.ModelCommentFindFirstOrThrowArgs + result: $Utils.PayloadToResult + } + findMany: { + args: Prisma.ModelCommentFindManyArgs + result: $Utils.PayloadToResult[] + } + create: { + args: Prisma.ModelCommentCreateArgs + result: $Utils.PayloadToResult + } + createMany: { + args: Prisma.ModelCommentCreateManyArgs + result: BatchPayload + } + createManyAndReturn: { + args: Prisma.ModelCommentCreateManyAndReturnArgs + result: $Utils.PayloadToResult[] + } + delete: { + args: Prisma.ModelCommentDeleteArgs + result: $Utils.PayloadToResult + } + update: { + args: Prisma.ModelCommentUpdateArgs + result: $Utils.PayloadToResult + } + deleteMany: { + args: Prisma.ModelCommentDeleteManyArgs + result: BatchPayload + } + updateMany: { + args: Prisma.ModelCommentUpdateManyArgs + result: BatchPayload + } + updateManyAndReturn: { + args: Prisma.ModelCommentUpdateManyAndReturnArgs + result: $Utils.PayloadToResult[] + } + upsert: { + args: Prisma.ModelCommentUpsertArgs + result: $Utils.PayloadToResult + } + aggregate: { + args: Prisma.ModelCommentAggregateArgs + result: $Utils.Optional + } + groupBy: { + args: Prisma.ModelCommentGroupByArgs + result: $Utils.Optional[] + } + count: { + args: Prisma.ModelCommentCountArgs + result: $Utils.Optional | number + } + } + } + ModelCommentLike: { + payload: Prisma.$ModelCommentLikePayload + fields: Prisma.ModelCommentLikeFieldRefs + operations: { + findUnique: { + args: Prisma.ModelCommentLikeFindUniqueArgs + result: $Utils.PayloadToResult | null + } + findUniqueOrThrow: { + args: Prisma.ModelCommentLikeFindUniqueOrThrowArgs + result: $Utils.PayloadToResult + } + findFirst: { + args: Prisma.ModelCommentLikeFindFirstArgs + result: $Utils.PayloadToResult | null + } + findFirstOrThrow: { + args: Prisma.ModelCommentLikeFindFirstOrThrowArgs + result: $Utils.PayloadToResult + } + findMany: { + args: Prisma.ModelCommentLikeFindManyArgs + result: $Utils.PayloadToResult[] + } + create: { + args: Prisma.ModelCommentLikeCreateArgs + result: $Utils.PayloadToResult + } + createMany: { + args: Prisma.ModelCommentLikeCreateManyArgs + result: BatchPayload + } + createManyAndReturn: { + args: Prisma.ModelCommentLikeCreateManyAndReturnArgs + result: $Utils.PayloadToResult[] + } + delete: { + args: Prisma.ModelCommentLikeDeleteArgs + result: $Utils.PayloadToResult + } + update: { + args: Prisma.ModelCommentLikeUpdateArgs + result: $Utils.PayloadToResult + } + deleteMany: { + args: Prisma.ModelCommentLikeDeleteManyArgs + result: BatchPayload + } + updateMany: { + args: Prisma.ModelCommentLikeUpdateManyArgs + result: BatchPayload + } + updateManyAndReturn: { + args: Prisma.ModelCommentLikeUpdateManyAndReturnArgs + result: $Utils.PayloadToResult[] + } + upsert: { + args: Prisma.ModelCommentLikeUpsertArgs + result: $Utils.PayloadToResult + } + aggregate: { + args: Prisma.ModelCommentLikeAggregateArgs + result: $Utils.Optional + } + groupBy: { + args: Prisma.ModelCommentLikeGroupByArgs + result: $Utils.Optional[] + } + count: { + args: Prisma.ModelCommentLikeCountArgs + result: $Utils.Optional | number + } + } + } Event: { payload: Prisma.$EventPayload fields: Prisma.EventFieldRefs @@ -2246,6 +2426,8 @@ export namespace Prisma { modelLike?: ModelLikeOmit modelInteraction?: ModelInteractionOmit modelDraft?: ModelDraftOmit + modelComment?: ModelCommentOmit + modelCommentLike?: ModelCommentLikeOmit event?: EventOmit } @@ -2336,6 +2518,8 @@ export namespace Prisma { modelLikes: number modelInteractions: number modelDrafts: number + comments: number + commentLikes: number passkeys: number } @@ -2349,6 +2533,8 @@ export namespace Prisma { modelLikes?: boolean | UserCountOutputTypeCountModelLikesArgs modelInteractions?: boolean | UserCountOutputTypeCountModelInteractionsArgs modelDrafts?: boolean | UserCountOutputTypeCountModelDraftsArgs + comments?: boolean | UserCountOutputTypeCountCommentsArgs + commentLikes?: boolean | UserCountOutputTypeCountCommentLikesArgs passkeys?: boolean | UserCountOutputTypeCountPasskeysArgs } @@ -2426,6 +2612,20 @@ export namespace Prisma { where?: ModelDraftWhereInput } + /** + * UserCountOutputType without action + */ + export type UserCountOutputTypeCountCommentsArgs = { + where?: ModelCommentWhereInput + } + + /** + * UserCountOutputType without action + */ + export type UserCountOutputTypeCountCommentLikesArgs = { + where?: ModelCommentLikeWhereInput + } + /** * UserCountOutputType without action */ @@ -2447,6 +2647,7 @@ export namespace Prisma { likes: number interactions: number drafts: number + comments: number } export type ModelCountOutputTypeSelect = { @@ -2458,6 +2659,7 @@ export namespace Prisma { likes?: boolean | ModelCountOutputTypeCountLikesArgs interactions?: boolean | ModelCountOutputTypeCountInteractionsArgs drafts?: boolean | ModelCountOutputTypeCountDraftsArgs + comments?: boolean | ModelCountOutputTypeCountCommentsArgs } // Custom InputTypes @@ -2527,6 +2729,13 @@ export namespace Prisma { where?: ModelDraftWhereInput } + /** + * ModelCountOutputType without action + */ + export type ModelCountOutputTypeCountCommentsArgs = { + where?: ModelCommentWhereInput + } + /** * Count Type ModelVersionCountOutputType @@ -2608,6 +2817,46 @@ export namespace Prisma { } + /** + * Count Type ModelCommentCountOutputType + */ + + export type ModelCommentCountOutputType = { + replies: number + likes: number + } + + export type ModelCommentCountOutputTypeSelect = { + replies?: boolean | ModelCommentCountOutputTypeCountRepliesArgs + likes?: boolean | ModelCommentCountOutputTypeCountLikesArgs + } + + // Custom InputTypes + /** + * ModelCommentCountOutputType without action + */ + export type ModelCommentCountOutputTypeDefaultArgs = { + /** + * Select specific fields to fetch from the ModelCommentCountOutputType + */ + select?: ModelCommentCountOutputTypeSelect | null + } + + /** + * ModelCommentCountOutputType without action + */ + export type ModelCommentCountOutputTypeCountRepliesArgs = { + where?: ModelCommentWhereInput + } + + /** + * ModelCommentCountOutputType without action + */ + export type ModelCommentCountOutputTypeCountLikesArgs = { + where?: ModelCommentLikeWhereInput + } + + /** * Models */ @@ -2951,6 +3200,8 @@ export namespace Prisma { modelLikes?: boolean | User$modelLikesArgs modelInteractions?: boolean | User$modelInteractionsArgs modelDrafts?: boolean | User$modelDraftsArgs + comments?: boolean | User$commentsArgs + commentLikes?: boolean | User$commentLikesArgs passkeys?: boolean | User$passkeysArgs _count?: boolean | UserCountOutputTypeDefaultArgs }, ExtArgs["result"]["user"]> @@ -3041,6 +3292,8 @@ export namespace Prisma { modelLikes?: boolean | User$modelLikesArgs modelInteractions?: boolean | User$modelInteractionsArgs modelDrafts?: boolean | User$modelDraftsArgs + comments?: boolean | User$commentsArgs + commentLikes?: boolean | User$commentLikesArgs passkeys?: boolean | User$passkeysArgs _count?: boolean | UserCountOutputTypeDefaultArgs } @@ -3059,6 +3312,8 @@ export namespace Prisma { modelLikes: Prisma.$ModelLikePayload[] modelInteractions: Prisma.$ModelInteractionPayload[] modelDrafts: Prisma.$ModelDraftPayload[] + comments: Prisma.$ModelCommentPayload[] + commentLikes: Prisma.$ModelCommentLikePayload[] passkeys: Prisma.$PasskeyPayload[] } scalars: $Extensions.GetPayloadResult<{ @@ -3487,6 +3742,8 @@ export namespace Prisma { modelLikes = {}>(args?: Subset>): Prisma.PrismaPromise<$Result.GetResult, T, "findMany", GlobalOmitOptions> | Null> modelInteractions = {}>(args?: Subset>): Prisma.PrismaPromise<$Result.GetResult, T, "findMany", GlobalOmitOptions> | Null> modelDrafts = {}>(args?: Subset>): Prisma.PrismaPromise<$Result.GetResult, T, "findMany", GlobalOmitOptions> | Null> + comments = {}>(args?: Subset>): Prisma.PrismaPromise<$Result.GetResult, T, "findMany", GlobalOmitOptions> | Null> + commentLikes = {}>(args?: Subset>): Prisma.PrismaPromise<$Result.GetResult, T, "findMany", GlobalOmitOptions> | Null> passkeys = {}>(args?: Subset>): Prisma.PrismaPromise<$Result.GetResult, T, "findMany", GlobalOmitOptions> | Null> /** * Attaches callbacks for the resolution and/or rejection of the Promise. @@ -4147,6 +4404,54 @@ export namespace Prisma { distinct?: ModelDraftScalarFieldEnum | ModelDraftScalarFieldEnum[] } + /** + * User.comments + */ + export type User$commentsArgs = { + /** + * Select specific fields to fetch from the ModelComment + */ + select?: ModelCommentSelect | null + /** + * Omit specific fields from the ModelComment + */ + omit?: ModelCommentOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: ModelCommentInclude | null + where?: ModelCommentWhereInput + orderBy?: ModelCommentOrderByWithRelationInput | ModelCommentOrderByWithRelationInput[] + cursor?: ModelCommentWhereUniqueInput + take?: number + skip?: number + distinct?: ModelCommentScalarFieldEnum | ModelCommentScalarFieldEnum[] + } + + /** + * User.commentLikes + */ + export type User$commentLikesArgs = { + /** + * Select specific fields to fetch from the ModelCommentLike + */ + select?: ModelCommentLikeSelect | null + /** + * Omit specific fields from the ModelCommentLike + */ + omit?: ModelCommentLikeOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: ModelCommentLikeInclude | null + where?: ModelCommentLikeWhereInput + orderBy?: ModelCommentLikeOrderByWithRelationInput | ModelCommentLikeOrderByWithRelationInput[] + cursor?: ModelCommentLikeWhereUniqueInput + take?: number + skip?: number + distinct?: ModelCommentLikeScalarFieldEnum | ModelCommentLikeScalarFieldEnum[] + } + /** * User.passkeys */ @@ -9068,6 +9373,7 @@ export namespace Prisma { likes?: boolean | Model$likesArgs interactions?: boolean | Model$interactionsArgs drafts?: boolean | Model$draftsArgs + comments?: boolean | Model$commentsArgs _count?: boolean | ModelCountOutputTypeDefaultArgs }, ExtArgs["result"]["model"]> @@ -9144,6 +9450,7 @@ export namespace Prisma { likes?: boolean | Model$likesArgs interactions?: boolean | Model$interactionsArgs drafts?: boolean | Model$draftsArgs + comments?: boolean | Model$commentsArgs _count?: boolean | ModelCountOutputTypeDefaultArgs } export type ModelIncludeCreateManyAndReturn = { @@ -9171,6 +9478,7 @@ export namespace Prisma { likes: Prisma.$ModelLikePayload[] interactions: Prisma.$ModelInteractionPayload[] drafts: Prisma.$ModelDraftPayload[] + comments: Prisma.$ModelCommentPayload[] } scalars: $Extensions.GetPayloadResult<{ id: string @@ -9593,6 +9901,7 @@ export namespace Prisma { likes = {}>(args?: Subset>): Prisma.PrismaPromise<$Result.GetResult, T, "findMany", GlobalOmitOptions> | Null> interactions = {}>(args?: Subset>): Prisma.PrismaPromise<$Result.GetResult, T, "findMany", GlobalOmitOptions> | Null> drafts = {}>(args?: Subset>): Prisma.PrismaPromise<$Result.GetResult, T, "findMany", GlobalOmitOptions> | Null> + comments = {}>(args?: Subset>): Prisma.PrismaPromise<$Result.GetResult, T, "findMany", GlobalOmitOptions> | Null> /** * Attaches callbacks for the resolution and/or rejection of the Promise. * @param onfulfilled The callback to execute when the Promise is resolved. @@ -10286,6 +10595,30 @@ export namespace Prisma { distinct?: ModelDraftScalarFieldEnum | ModelDraftScalarFieldEnum[] } + /** + * Model.comments + */ + export type Model$commentsArgs = { + /** + * Select specific fields to fetch from the ModelComment + */ + select?: ModelCommentSelect | null + /** + * Omit specific fields from the ModelComment + */ + omit?: ModelCommentOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: ModelCommentInclude | null + where?: ModelCommentWhereInput + orderBy?: ModelCommentOrderByWithRelationInput | ModelCommentOrderByWithRelationInput[] + cursor?: ModelCommentWhereUniqueInput + take?: number + skip?: number + distinct?: ModelCommentScalarFieldEnum | ModelCommentScalarFieldEnum[] + } + /** * Model without action */ @@ -20459,382 +20792,498 @@ export namespace Prisma { /** - * Model Event + * Model ModelComment */ - export type AggregateEvent = { - _count: EventCountAggregateOutputType | null - _min: EventMinAggregateOutputType | null - _max: EventMaxAggregateOutputType | null + export type AggregateModelComment = { + _count: ModelCommentCountAggregateOutputType | null + _avg: ModelCommentAvgAggregateOutputType | null + _sum: ModelCommentSumAggregateOutputType | null + _min: ModelCommentMinAggregateOutputType | null + _max: ModelCommentMaxAggregateOutputType | null } - export type EventMinAggregateOutputType = { + export type ModelCommentAvgAggregateOutputType = { + legacyId: number | null + versionNumber: number | null + likesCount: number | null + } + + export type ModelCommentSumAggregateOutputType = { + legacyId: number | null + versionNumber: number | null + likesCount: number | null + } + + export type ModelCommentMinAggregateOutputType = { id: string | null - type: string | null - actorId: string | null - resourceType: string | null - resourceId: string | null + legacyId: number | null + parentId: string | null + userId: string | null + modelId: string | null + versionNumber: number | null + content: string | null + likesCount: number | null createdAt: Date | null - processedAt: Date | null + updatedAt: Date | null + editedAt: Date | null + deletedAt: Date | null } - export type EventMaxAggregateOutputType = { + export type ModelCommentMaxAggregateOutputType = { id: string | null - type: string | null - actorId: string | null - resourceType: string | null - resourceId: string | null + legacyId: number | null + parentId: string | null + userId: string | null + modelId: string | null + versionNumber: number | null + content: string | null + likesCount: number | null createdAt: Date | null - processedAt: Date | null + updatedAt: Date | null + editedAt: Date | null + deletedAt: Date | null } - export type EventCountAggregateOutputType = { + export type ModelCommentCountAggregateOutputType = { id: number - type: number - actorId: number - resourceType: number - resourceId: number - payload: number + legacyId: number + parentId: number + userId: number + modelId: number + versionNumber: number + content: number + likesCount: number createdAt: number - processedAt: number + updatedAt: number + editedAt: number + deletedAt: number _all: number } - export type EventMinAggregateInputType = { + export type ModelCommentAvgAggregateInputType = { + legacyId?: true + versionNumber?: true + likesCount?: true + } + + export type ModelCommentSumAggregateInputType = { + legacyId?: true + versionNumber?: true + likesCount?: true + } + + export type ModelCommentMinAggregateInputType = { id?: true - type?: true - actorId?: true - resourceType?: true - resourceId?: true + legacyId?: true + parentId?: true + userId?: true + modelId?: true + versionNumber?: true + content?: true + likesCount?: true createdAt?: true - processedAt?: true + updatedAt?: true + editedAt?: true + deletedAt?: true } - export type EventMaxAggregateInputType = { + export type ModelCommentMaxAggregateInputType = { id?: true - type?: true - actorId?: true - resourceType?: true - resourceId?: true + legacyId?: true + parentId?: true + userId?: true + modelId?: true + versionNumber?: true + content?: true + likesCount?: true createdAt?: true - processedAt?: true + updatedAt?: true + editedAt?: true + deletedAt?: true } - export type EventCountAggregateInputType = { + export type ModelCommentCountAggregateInputType = { id?: true - type?: true - actorId?: true - resourceType?: true - resourceId?: true - payload?: true + legacyId?: true + parentId?: true + userId?: true + modelId?: true + versionNumber?: true + content?: true + likesCount?: true createdAt?: true - processedAt?: true + updatedAt?: true + editedAt?: true + deletedAt?: true _all?: true } - export type EventAggregateArgs = { + export type ModelCommentAggregateArgs = { /** - * Filter which Event to aggregate. + * Filter which ModelComment to aggregate. */ - where?: EventWhereInput + where?: ModelCommentWhereInput /** * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} * - * Determine the order of Events to fetch. + * Determine the order of ModelComments to fetch. */ - orderBy?: EventOrderByWithRelationInput | EventOrderByWithRelationInput[] + orderBy?: ModelCommentOrderByWithRelationInput | ModelCommentOrderByWithRelationInput[] /** * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} * * Sets the start position */ - cursor?: EventWhereUniqueInput + cursor?: ModelCommentWhereUniqueInput /** * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} * - * Take `±n` Events from the position of the cursor. + * Take `±n` ModelComments 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` Events. + * Skip the first `n` ModelComments. */ skip?: number /** * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} * - * Count returned Events + * Count returned ModelComments **/ - _count?: true | EventCountAggregateInputType + _count?: true | ModelCommentCountAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to average + **/ + _avg?: ModelCommentAvgAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to sum + **/ + _sum?: ModelCommentSumAggregateInputType /** * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} * * Select which fields to find the minimum value **/ - _min?: EventMinAggregateInputType + _min?: ModelCommentMinAggregateInputType /** * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} * * Select which fields to find the maximum value **/ - _max?: EventMaxAggregateInputType + _max?: ModelCommentMaxAggregateInputType } - export type GetEventAggregateType = { - [P in keyof T & keyof AggregateEvent]: P extends '_count' | 'count' + export type GetModelCommentAggregateType = { + [P in keyof T & keyof AggregateModelComment]: P extends '_count' | 'count' ? T[P] extends true ? number - : GetScalarType - : GetScalarType + : GetScalarType + : GetScalarType } - export type EventGroupByArgs = { - where?: EventWhereInput - orderBy?: EventOrderByWithAggregationInput | EventOrderByWithAggregationInput[] - by: EventScalarFieldEnum[] | EventScalarFieldEnum - having?: EventScalarWhereWithAggregatesInput + export type ModelCommentGroupByArgs = { + where?: ModelCommentWhereInput + orderBy?: ModelCommentOrderByWithAggregationInput | ModelCommentOrderByWithAggregationInput[] + by: ModelCommentScalarFieldEnum[] | ModelCommentScalarFieldEnum + having?: ModelCommentScalarWhereWithAggregatesInput take?: number skip?: number - _count?: EventCountAggregateInputType | true - _min?: EventMinAggregateInputType - _max?: EventMaxAggregateInputType + _count?: ModelCommentCountAggregateInputType | true + _avg?: ModelCommentAvgAggregateInputType + _sum?: ModelCommentSumAggregateInputType + _min?: ModelCommentMinAggregateInputType + _max?: ModelCommentMaxAggregateInputType } - export type EventGroupByOutputType = { + export type ModelCommentGroupByOutputType = { id: string - type: string - actorId: string - resourceType: string - resourceId: string - payload: JsonValue + legacyId: number | null + parentId: string | null + userId: string | null + modelId: string + versionNumber: number | null + content: string | null + likesCount: number createdAt: Date - processedAt: Date | null - _count: EventCountAggregateOutputType | null - _min: EventMinAggregateOutputType | null - _max: EventMaxAggregateOutputType | null + updatedAt: Date + editedAt: Date | null + deletedAt: Date | null + _count: ModelCommentCountAggregateOutputType | null + _avg: ModelCommentAvgAggregateOutputType | null + _sum: ModelCommentSumAggregateOutputType | null + _min: ModelCommentMinAggregateOutputType | null + _max: ModelCommentMaxAggregateOutputType | null } - type GetEventGroupByPayload = Prisma.PrismaPromise< + type GetModelCommentGroupByPayload = Prisma.PrismaPromise< Array< - PickEnumerable & + PickEnumerable & { - [P in ((keyof T) & (keyof EventGroupByOutputType))]: P extends '_count' + [P in ((keyof T) & (keyof ModelCommentGroupByOutputType))]: P extends '_count' ? T[P] extends boolean ? number - : GetScalarType - : GetScalarType + : GetScalarType + : GetScalarType } > > - export type EventSelect = $Extensions.GetSelect<{ + export type ModelCommentSelect = $Extensions.GetSelect<{ id?: boolean - type?: boolean - actorId?: boolean - resourceType?: boolean - resourceId?: boolean - payload?: boolean + legacyId?: boolean + parentId?: boolean + userId?: boolean + modelId?: boolean + versionNumber?: boolean + content?: boolean + likesCount?: boolean createdAt?: boolean - processedAt?: boolean - actor?: boolean | UserDefaultArgs - }, ExtArgs["result"]["event"]> - - export type EventSelectCreateManyAndReturn = $Extensions.GetSelect<{ + updatedAt?: boolean + editedAt?: boolean + deletedAt?: boolean + model?: boolean | ModelDefaultArgs + user?: boolean | ModelComment$userArgs + parent?: boolean | ModelComment$parentArgs + replies?: boolean | ModelComment$repliesArgs + likes?: boolean | ModelComment$likesArgs + _count?: boolean | ModelCommentCountOutputTypeDefaultArgs + }, ExtArgs["result"]["modelComment"]> + + export type ModelCommentSelectCreateManyAndReturn = $Extensions.GetSelect<{ id?: boolean - type?: boolean - actorId?: boolean - resourceType?: boolean - resourceId?: boolean - payload?: boolean + legacyId?: boolean + parentId?: boolean + userId?: boolean + modelId?: boolean + versionNumber?: boolean + content?: boolean + likesCount?: boolean createdAt?: boolean - processedAt?: boolean - actor?: boolean | UserDefaultArgs - }, ExtArgs["result"]["event"]> + updatedAt?: boolean + editedAt?: boolean + deletedAt?: boolean + model?: boolean | ModelDefaultArgs + user?: boolean | ModelComment$userArgs + parent?: boolean | ModelComment$parentArgs + }, ExtArgs["result"]["modelComment"]> - export type EventSelectUpdateManyAndReturn = $Extensions.GetSelect<{ + export type ModelCommentSelectUpdateManyAndReturn = $Extensions.GetSelect<{ id?: boolean - type?: boolean - actorId?: boolean - resourceType?: boolean - resourceId?: boolean - payload?: boolean + legacyId?: boolean + parentId?: boolean + userId?: boolean + modelId?: boolean + versionNumber?: boolean + content?: boolean + likesCount?: boolean createdAt?: boolean - processedAt?: boolean - actor?: boolean | UserDefaultArgs - }, ExtArgs["result"]["event"]> + updatedAt?: boolean + editedAt?: boolean + deletedAt?: boolean + model?: boolean | ModelDefaultArgs + user?: boolean | ModelComment$userArgs + parent?: boolean | ModelComment$parentArgs + }, ExtArgs["result"]["modelComment"]> - export type EventSelectScalar = { + export type ModelCommentSelectScalar = { id?: boolean - type?: boolean - actorId?: boolean - resourceType?: boolean - resourceId?: boolean - payload?: boolean + legacyId?: boolean + parentId?: boolean + userId?: boolean + modelId?: boolean + versionNumber?: boolean + content?: boolean + likesCount?: boolean createdAt?: boolean - processedAt?: boolean + updatedAt?: boolean + editedAt?: boolean + deletedAt?: boolean } - export type EventOmit = $Extensions.GetOmit<"id" | "type" | "actorId" | "resourceType" | "resourceId" | "payload" | "createdAt" | "processedAt", ExtArgs["result"]["event"]> - export type EventInclude = { - actor?: boolean | UserDefaultArgs + export type ModelCommentOmit = $Extensions.GetOmit<"id" | "legacyId" | "parentId" | "userId" | "modelId" | "versionNumber" | "content" | "likesCount" | "createdAt" | "updatedAt" | "editedAt" | "deletedAt", ExtArgs["result"]["modelComment"]> + export type ModelCommentInclude = { + model?: boolean | ModelDefaultArgs + user?: boolean | ModelComment$userArgs + parent?: boolean | ModelComment$parentArgs + replies?: boolean | ModelComment$repliesArgs + likes?: boolean | ModelComment$likesArgs + _count?: boolean | ModelCommentCountOutputTypeDefaultArgs } - export type EventIncludeCreateManyAndReturn = { - actor?: boolean | UserDefaultArgs + export type ModelCommentIncludeCreateManyAndReturn = { + model?: boolean | ModelDefaultArgs + user?: boolean | ModelComment$userArgs + parent?: boolean | ModelComment$parentArgs } - export type EventIncludeUpdateManyAndReturn = { - actor?: boolean | UserDefaultArgs + export type ModelCommentIncludeUpdateManyAndReturn = { + model?: boolean | ModelDefaultArgs + user?: boolean | ModelComment$userArgs + parent?: boolean | ModelComment$parentArgs } - export type $EventPayload = { - name: "Event" + export type $ModelCommentPayload = { + name: "ModelComment" objects: { - actor: Prisma.$UserPayload + model: Prisma.$ModelPayload + user: Prisma.$UserPayload | null + parent: Prisma.$ModelCommentPayload | null + replies: Prisma.$ModelCommentPayload[] + likes: Prisma.$ModelCommentLikePayload[] } scalars: $Extensions.GetPayloadResult<{ id: string - type: string - actorId: string - resourceType: string - resourceId: string - payload: Prisma.JsonValue + legacyId: number | null + parentId: string | null + userId: string | null + modelId: string + versionNumber: number | null + content: string | null + likesCount: number createdAt: Date - processedAt: Date | null - }, ExtArgs["result"]["event"]> + updatedAt: Date + editedAt: Date | null + deletedAt: Date | null + }, ExtArgs["result"]["modelComment"]> composites: {} } - type EventGetPayload = $Result.GetResult + type ModelCommentGetPayload = $Result.GetResult - type EventCountArgs = - Omit & { - select?: EventCountAggregateInputType | true + type ModelCommentCountArgs = + Omit & { + select?: ModelCommentCountAggregateInputType | true } - export interface EventDelegate { - [K: symbol]: { types: Prisma.TypeMap['model']['Event'], meta: { name: 'Event' } } + export interface ModelCommentDelegate { + [K: symbol]: { types: Prisma.TypeMap['model']['ModelComment'], meta: { name: 'ModelComment' } } /** - * Find zero or one Event that matches the filter. - * @param {EventFindUniqueArgs} args - Arguments to find a Event + * Find zero or one ModelComment that matches the filter. + * @param {ModelCommentFindUniqueArgs} args - Arguments to find a ModelComment * @example - * // Get one Event - * const event = await prisma.event.findUnique({ + * // Get one ModelComment + * const modelComment = await prisma.modelComment.findUnique({ * where: { * // ... provide filter here * } * }) */ - findUnique(args: SelectSubset>): Prisma__EventClient<$Result.GetResult, T, "findUnique", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> + findUnique(args: SelectSubset>): Prisma__ModelCommentClient<$Result.GetResult, T, "findUnique", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> /** - * Find one Event that matches the filter or throw an error with `error.code='P2025'` + * Find one ModelComment that matches the filter or throw an error with `error.code='P2025'` * if no matches were found. - * @param {EventFindUniqueOrThrowArgs} args - Arguments to find a Event + * @param {ModelCommentFindUniqueOrThrowArgs} args - Arguments to find a ModelComment * @example - * // Get one Event - * const event = await prisma.event.findUniqueOrThrow({ + * // Get one ModelComment + * const modelComment = await prisma.modelComment.findUniqueOrThrow({ * where: { * // ... provide filter here * } * }) */ - findUniqueOrThrow(args: SelectSubset>): Prisma__EventClient<$Result.GetResult, T, "findUniqueOrThrow", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + findUniqueOrThrow(args: SelectSubset>): Prisma__ModelCommentClient<$Result.GetResult, T, "findUniqueOrThrow", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> /** - * Find the first Event that matches the filter. + * Find the first ModelComment 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 {EventFindFirstArgs} args - Arguments to find a Event + * @param {ModelCommentFindFirstArgs} args - Arguments to find a ModelComment * @example - * // Get one Event - * const event = await prisma.event.findFirst({ + * // Get one ModelComment + * const modelComment = await prisma.modelComment.findFirst({ * where: { * // ... provide filter here * } * }) */ - findFirst(args?: SelectSubset>): Prisma__EventClient<$Result.GetResult, T, "findFirst", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> + findFirst(args?: SelectSubset>): Prisma__ModelCommentClient<$Result.GetResult, T, "findFirst", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> /** - * Find the first Event that matches the filter or + * Find the first ModelComment 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 {EventFindFirstOrThrowArgs} args - Arguments to find a Event + * @param {ModelCommentFindFirstOrThrowArgs} args - Arguments to find a ModelComment * @example - * // Get one Event - * const event = await prisma.event.findFirstOrThrow({ + * // Get one ModelComment + * const modelComment = await prisma.modelComment.findFirstOrThrow({ * where: { * // ... provide filter here * } * }) */ - findFirstOrThrow(args?: SelectSubset>): Prisma__EventClient<$Result.GetResult, T, "findFirstOrThrow", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + findFirstOrThrow(args?: SelectSubset>): Prisma__ModelCommentClient<$Result.GetResult, T, "findFirstOrThrow", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> /** - * Find zero or more Events that matches the filter. + * Find zero or more ModelComments 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 {EventFindManyArgs} args - Arguments to filter and select certain fields only. + * @param {ModelCommentFindManyArgs} args - Arguments to filter and select certain fields only. * @example - * // Get all Events - * const events = await prisma.event.findMany() + * // Get all ModelComments + * const modelComments = await prisma.modelComment.findMany() * - * // Get first 10 Events - * const events = await prisma.event.findMany({ take: 10 }) + * // Get first 10 ModelComments + * const modelComments = await prisma.modelComment.findMany({ take: 10 }) * * // Only select the `id` - * const eventWithIdOnly = await prisma.event.findMany({ select: { id: true } }) + * const modelCommentWithIdOnly = await prisma.modelComment.findMany({ select: { id: true } }) * */ - findMany(args?: SelectSubset>): Prisma.PrismaPromise<$Result.GetResult, T, "findMany", GlobalOmitOptions>> + findMany(args?: SelectSubset>): Prisma.PrismaPromise<$Result.GetResult, T, "findMany", GlobalOmitOptions>> /** - * Create a Event. - * @param {EventCreateArgs} args - Arguments to create a Event. + * Create a ModelComment. + * @param {ModelCommentCreateArgs} args - Arguments to create a ModelComment. * @example - * // Create one Event - * const Event = await prisma.event.create({ + * // Create one ModelComment + * const ModelComment = await prisma.modelComment.create({ * data: { - * // ... data to create a Event + * // ... data to create a ModelComment * } * }) * */ - create(args: SelectSubset>): Prisma__EventClient<$Result.GetResult, T, "create", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + create(args: SelectSubset>): Prisma__ModelCommentClient<$Result.GetResult, T, "create", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> /** - * Create many Events. - * @param {EventCreateManyArgs} args - Arguments to create many Events. + * Create many ModelComments. + * @param {ModelCommentCreateManyArgs} args - Arguments to create many ModelComments. * @example - * // Create many Events - * const event = await prisma.event.createMany({ + * // Create many ModelComments + * const modelComment = await prisma.modelComment.createMany({ * data: [ * // ... provide data here * ] * }) * */ - createMany(args?: SelectSubset>): Prisma.PrismaPromise + createMany(args?: SelectSubset>): Prisma.PrismaPromise /** - * Create many Events and returns the data saved in the database. - * @param {EventCreateManyAndReturnArgs} args - Arguments to create many Events. + * Create many ModelComments and returns the data saved in the database. + * @param {ModelCommentCreateManyAndReturnArgs} args - Arguments to create many ModelComments. * @example - * // Create many Events - * const event = await prisma.event.createManyAndReturn({ + * // Create many ModelComments + * const modelComment = await prisma.modelComment.createManyAndReturn({ * data: [ * // ... provide data here * ] * }) * - * // Create many Events and only return the `id` - * const eventWithIdOnly = await prisma.event.createManyAndReturn({ + * // Create many ModelComments and only return the `id` + * const modelCommentWithIdOnly = await prisma.modelComment.createManyAndReturn({ * select: { id: true }, * data: [ * // ... provide data here @@ -20844,28 +21293,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 Event. - * @param {EventDeleteArgs} args - Arguments to delete one Event. + * Delete a ModelComment. + * @param {ModelCommentDeleteArgs} args - Arguments to delete one ModelComment. * @example - * // Delete one Event - * const Event = await prisma.event.delete({ + * // Delete one ModelComment + * const ModelComment = await prisma.modelComment.delete({ * where: { - * // ... filter to delete one Event + * // ... filter to delete one ModelComment * } * }) * */ - delete(args: SelectSubset>): Prisma__EventClient<$Result.GetResult, T, "delete", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + delete(args: SelectSubset>): Prisma__ModelCommentClient<$Result.GetResult, T, "delete", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> /** - * Update one Event. - * @param {EventUpdateArgs} args - Arguments to update one Event. + * Update one ModelComment. + * @param {ModelCommentUpdateArgs} args - Arguments to update one ModelComment. * @example - * // Update one Event - * const event = await prisma.event.update({ + * // Update one ModelComment + * const modelComment = await prisma.modelComment.update({ * where: { * // ... provide filter here * }, @@ -20875,30 +21324,30 @@ export namespace Prisma { * }) * */ - update(args: SelectSubset>): Prisma__EventClient<$Result.GetResult, T, "update", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + update(args: SelectSubset>): Prisma__ModelCommentClient<$Result.GetResult, T, "update", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> /** - * Delete zero or more Events. - * @param {EventDeleteManyArgs} args - Arguments to filter Events to delete. + * Delete zero or more ModelComments. + * @param {ModelCommentDeleteManyArgs} args - Arguments to filter ModelComments to delete. * @example - * // Delete a few Events - * const { count } = await prisma.event.deleteMany({ + * // Delete a few ModelComments + * const { count } = await prisma.modelComment.deleteMany({ * where: { * // ... provide filter here * } * }) * */ - deleteMany(args?: SelectSubset>): Prisma.PrismaPromise + deleteMany(args?: SelectSubset>): Prisma.PrismaPromise /** - * Update zero or more Events. + * Update zero or more ModelComments. * Note, that providing `undefined` is treated as the value not being there. * Read more here: https://pris.ly/d/null-undefined - * @param {EventUpdateManyArgs} args - Arguments to update one or more rows. + * @param {ModelCommentUpdateManyArgs} args - Arguments to update one or more rows. * @example - * // Update many Events - * const event = await prisma.event.updateMany({ + * // Update many ModelComments + * const modelComment = await prisma.modelComment.updateMany({ * where: { * // ... provide filter here * }, @@ -20908,14 +21357,14 @@ export namespace Prisma { * }) * */ - updateMany(args: SelectSubset>): Prisma.PrismaPromise + updateMany(args: SelectSubset>): Prisma.PrismaPromise /** - * Update zero or more Events and returns the data updated in the database. - * @param {EventUpdateManyAndReturnArgs} args - Arguments to update many Events. + * Update zero or more ModelComments and returns the data updated in the database. + * @param {ModelCommentUpdateManyAndReturnArgs} args - Arguments to update many ModelComments. * @example - * // Update many Events - * const event = await prisma.event.updateManyAndReturn({ + * // Update many ModelComments + * const modelComment = await prisma.modelComment.updateManyAndReturn({ * where: { * // ... provide filter here * }, @@ -20924,8 +21373,8 @@ export namespace Prisma { * ] * }) * - * // Update zero or more Events and only return the `id` - * const eventWithIdOnly = await prisma.event.updateManyAndReturn({ + * // Update zero or more ModelComments and only return the `id` + * const modelCommentWithIdOnly = await prisma.modelComment.updateManyAndReturn({ * select: { id: true }, * where: { * // ... provide filter here @@ -20938,56 +21387,56 @@ 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 Event. - * @param {EventUpsertArgs} args - Arguments to update or create a Event. + * Create or update one ModelComment. + * @param {ModelCommentUpsertArgs} args - Arguments to update or create a ModelComment. * @example - * // Update or create a Event - * const event = await prisma.event.upsert({ + * // Update or create a ModelComment + * const modelComment = await prisma.modelComment.upsert({ * create: { - * // ... data to create a Event + * // ... data to create a ModelComment * }, * update: { * // ... in case it already exists, update * }, * where: { - * // ... the filter for the Event we want to update + * // ... the filter for the ModelComment we want to update * } * }) */ - upsert(args: SelectSubset>): Prisma__EventClient<$Result.GetResult, T, "upsert", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + upsert(args: SelectSubset>): Prisma__ModelCommentClient<$Result.GetResult, T, "upsert", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> /** - * Count the number of Events. + * Count the number of ModelComments. * Note, that providing `undefined` is treated as the value not being there. * Read more here: https://pris.ly/d/null-undefined - * @param {EventCountArgs} args - Arguments to filter Events to count. + * @param {ModelCommentCountArgs} args - Arguments to filter ModelComments to count. * @example - * // Count the number of Events - * const count = await prisma.event.count({ + * // Count the number of ModelComments + * const count = await prisma.modelComment.count({ * where: { - * // ... the filter for the Events we want to count + * // ... the filter for the ModelComments we want to count * } * }) **/ - count( - args?: Subset, + count( + args?: Subset, ): Prisma.PrismaPromise< T extends $Utils.Record<'select', any> ? T['select'] extends true ? number - : GetScalarType + : GetScalarType : number > /** - * Allows you to perform aggregations operations on a Event. + * Allows you to perform aggregations operations on a ModelComment. * Note, that providing `undefined` is treated as the value not being there. * Read more here: https://pris.ly/d/null-undefined - * @param {EventAggregateArgs} args - Select which aggregations you would like to apply and on what fields. + * @param {ModelCommentAggregateArgs} args - Select which aggregations you would like to apply and on what fields. * @example * // Ordered by age ascending * // Where email contains prisma.io @@ -21007,13 +21456,13 @@ export namespace Prisma { * take: 10, * }) **/ - aggregate(args: Subset): Prisma.PrismaPromise> + aggregate(args: Subset): Prisma.PrismaPromise> /** - * Group by Event. + * Group by ModelComment. * Note, that providing `undefined` is treated as the value not being there. * Read more here: https://pris.ly/d/null-undefined - * @param {EventGroupByArgs} args - Group by arguments. + * @param {ModelCommentGroupByArgs} args - Group by arguments. * @example * // Group by city, order by createdAt, get count * const result = await prisma.user.groupBy({ @@ -21028,14 +21477,14 @@ export namespace Prisma { * **/ groupBy< - T extends EventGroupByArgs, + T extends ModelCommentGroupByArgs, HasSelectOrTake extends Or< Extends<'skip', Keys>, Extends<'take', Keys> >, OrderByArg extends True extends HasSelectOrTake - ? { orderBy: EventGroupByArgs['orderBy'] } - : { orderBy?: EventGroupByArgs['orderBy'] }, + ? { orderBy: ModelCommentGroupByArgs['orderBy'] } + : { orderBy?: ModelCommentGroupByArgs['orderBy'] }, OrderFields extends ExcludeUnderscoreKeys>>, ByFields extends MaybeTupleToUnion, ByValid extends Has, @@ -21084,22 +21533,26 @@ export namespace Prisma { ? never : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` }[OrderFields] - >(args: SubsetIntersection & InputErrors): {} extends InputErrors ? GetEventGroupByPayload : Prisma.PrismaPromise + >(args: SubsetIntersection & InputErrors): {} extends InputErrors ? GetModelCommentGroupByPayload : Prisma.PrismaPromise /** - * Fields of the Event model + * Fields of the ModelComment model */ - readonly fields: EventFieldRefs; + readonly fields: ModelCommentFieldRefs; } /** - * The delegate class that acts as a "Promise-like" for Event. + * The delegate class that acts as a "Promise-like" for ModelComment. * 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__EventClient extends Prisma.PrismaPromise { + export interface Prisma__ModelCommentClient extends Prisma.PrismaPromise { readonly [Symbol.toStringTag]: "PrismaPromise" - actor = {}>(args?: Subset>): Prisma__UserClient<$Result.GetResult, T, "findUniqueOrThrow", GlobalOmitOptions> | Null, Null, ExtArgs, GlobalOmitOptions> + model = {}>(args?: Subset>): Prisma__ModelClient<$Result.GetResult, T, "findUniqueOrThrow", GlobalOmitOptions> | Null, Null, ExtArgs, GlobalOmitOptions> + user = {}>(args?: Subset>): Prisma__UserClient<$Result.GetResult, T, "findUniqueOrThrow", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> + parent = {}>(args?: Subset>): Prisma__ModelCommentClient<$Result.GetResult, T, "findUniqueOrThrow", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> + replies = {}>(args?: Subset>): Prisma.PrismaPromise<$Result.GetResult, T, "findMany", GlobalOmitOptions> | Null> + likes = {}>(args?: Subset>): Prisma.PrismaPromise<$Result.GetResult, T, "findMany", GlobalOmitOptions> | Null> /** * Attaches callbacks for the resolution and/or rejection of the Promise. * @param onfulfilled The callback to execute when the Promise is resolved. @@ -21126,621 +21579,2854 @@ export namespace Prisma { /** - * Fields of the Event model + * Fields of the ModelComment model */ - interface EventFieldRefs { - readonly id: FieldRef<"Event", 'String'> - readonly type: FieldRef<"Event", 'String'> - readonly actorId: FieldRef<"Event", 'String'> - readonly resourceType: FieldRef<"Event", 'String'> - readonly resourceId: FieldRef<"Event", 'String'> - readonly payload: FieldRef<"Event", 'Json'> - readonly createdAt: FieldRef<"Event", 'DateTime'> - readonly processedAt: FieldRef<"Event", 'DateTime'> + interface ModelCommentFieldRefs { + readonly id: FieldRef<"ModelComment", 'String'> + readonly legacyId: FieldRef<"ModelComment", 'Int'> + readonly parentId: FieldRef<"ModelComment", 'String'> + readonly userId: FieldRef<"ModelComment", 'String'> + readonly modelId: FieldRef<"ModelComment", 'String'> + readonly versionNumber: FieldRef<"ModelComment", 'Int'> + readonly content: FieldRef<"ModelComment", 'String'> + readonly likesCount: FieldRef<"ModelComment", 'Int'> + readonly createdAt: FieldRef<"ModelComment", 'DateTime'> + readonly updatedAt: FieldRef<"ModelComment", 'DateTime'> + readonly editedAt: FieldRef<"ModelComment", 'DateTime'> + readonly deletedAt: FieldRef<"ModelComment", 'DateTime'> } // Custom InputTypes /** - * Event findUnique + * ModelComment findUnique */ - export type EventFindUniqueArgs = { + export type ModelCommentFindUniqueArgs = { /** - * Select specific fields to fetch from the Event + * Select specific fields to fetch from the ModelComment */ - select?: EventSelect | null + select?: ModelCommentSelect | null /** - * Omit specific fields from the Event + * Omit specific fields from the ModelComment */ - omit?: EventOmit | null + omit?: ModelCommentOmit | null /** * Choose, which related nodes to fetch as well */ - include?: EventInclude | null + include?: ModelCommentInclude | null /** - * Filter, which Event to fetch. + * Filter, which ModelComment to fetch. */ - where: EventWhereUniqueInput + where: ModelCommentWhereUniqueInput } /** - * Event findUniqueOrThrow + * ModelComment findUniqueOrThrow */ - export type EventFindUniqueOrThrowArgs = { + export type ModelCommentFindUniqueOrThrowArgs = { /** - * Select specific fields to fetch from the Event + * Select specific fields to fetch from the ModelComment */ - select?: EventSelect | null + select?: ModelCommentSelect | null /** - * Omit specific fields from the Event + * Omit specific fields from the ModelComment */ - omit?: EventOmit | null + omit?: ModelCommentOmit | null /** * Choose, which related nodes to fetch as well */ - include?: EventInclude | null + include?: ModelCommentInclude | null /** - * Filter, which Event to fetch. + * Filter, which ModelComment to fetch. */ - where: EventWhereUniqueInput + where: ModelCommentWhereUniqueInput } /** - * Event findFirst + * ModelComment findFirst */ - export type EventFindFirstArgs = { + export type ModelCommentFindFirstArgs = { /** - * Select specific fields to fetch from the Event + * Select specific fields to fetch from the ModelComment */ - select?: EventSelect | null + select?: ModelCommentSelect | null /** - * Omit specific fields from the Event + * Omit specific fields from the ModelComment */ - omit?: EventOmit | null + omit?: ModelCommentOmit | null /** * Choose, which related nodes to fetch as well */ - include?: EventInclude | null + include?: ModelCommentInclude | null /** - * Filter, which Event to fetch. + * Filter, which ModelComment to fetch. */ - where?: EventWhereInput + where?: ModelCommentWhereInput /** * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} * - * Determine the order of Events to fetch. + * Determine the order of ModelComments to fetch. */ - orderBy?: EventOrderByWithRelationInput | EventOrderByWithRelationInput[] + orderBy?: ModelCommentOrderByWithRelationInput | ModelCommentOrderByWithRelationInput[] /** * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} * - * Sets the position for searching for Events. + * Sets the position for searching for ModelComments. */ - cursor?: EventWhereUniqueInput + cursor?: ModelCommentWhereUniqueInput /** * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} * - * Take `±n` Events from the position of the cursor. + * Take `±n` ModelComments 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` Events. + * Skip the first `n` ModelComments. */ skip?: number /** * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} * - * Filter by unique combinations of Events. + * Filter by unique combinations of ModelComments. */ - distinct?: EventScalarFieldEnum | EventScalarFieldEnum[] + distinct?: ModelCommentScalarFieldEnum | ModelCommentScalarFieldEnum[] } /** - * Event findFirstOrThrow + * ModelComment findFirstOrThrow */ - export type EventFindFirstOrThrowArgs = { + export type ModelCommentFindFirstOrThrowArgs = { /** - * Select specific fields to fetch from the Event + * Select specific fields to fetch from the ModelComment */ - select?: EventSelect | null + select?: ModelCommentSelect | null /** - * Omit specific fields from the Event + * Omit specific fields from the ModelComment */ - omit?: EventOmit | null + omit?: ModelCommentOmit | null /** * Choose, which related nodes to fetch as well */ - include?: EventInclude | null + include?: ModelCommentInclude | null /** - * Filter, which Event to fetch. + * Filter, which ModelComment to fetch. */ - where?: EventWhereInput + where?: ModelCommentWhereInput /** * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} * - * Determine the order of Events to fetch. + * Determine the order of ModelComments to fetch. */ - orderBy?: EventOrderByWithRelationInput | EventOrderByWithRelationInput[] + orderBy?: ModelCommentOrderByWithRelationInput | ModelCommentOrderByWithRelationInput[] /** * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} * - * Sets the position for searching for Events. + * Sets the position for searching for ModelComments. */ - cursor?: EventWhereUniqueInput + cursor?: ModelCommentWhereUniqueInput /** * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} * - * Take `±n` Events from the position of the cursor. + * Take `±n` ModelComments 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` Events. + * Skip the first `n` ModelComments. */ skip?: number /** * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} * - * Filter by unique combinations of Events. + * Filter by unique combinations of ModelComments. */ - distinct?: EventScalarFieldEnum | EventScalarFieldEnum[] + distinct?: ModelCommentScalarFieldEnum | ModelCommentScalarFieldEnum[] } /** - * Event findMany + * ModelComment findMany */ - export type EventFindManyArgs = { + export type ModelCommentFindManyArgs = { /** - * Select specific fields to fetch from the Event + * Select specific fields to fetch from the ModelComment */ - select?: EventSelect | null + select?: ModelCommentSelect | null /** - * Omit specific fields from the Event + * Omit specific fields from the ModelComment */ - omit?: EventOmit | null + omit?: ModelCommentOmit | null /** * Choose, which related nodes to fetch as well */ - include?: EventInclude | null + include?: ModelCommentInclude | null /** - * Filter, which Events to fetch. + * Filter, which ModelComments to fetch. */ - where?: EventWhereInput + where?: ModelCommentWhereInput /** * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} * - * Determine the order of Events to fetch. + * Determine the order of ModelComments to fetch. */ - orderBy?: EventOrderByWithRelationInput | EventOrderByWithRelationInput[] + orderBy?: ModelCommentOrderByWithRelationInput | ModelCommentOrderByWithRelationInput[] /** * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} * - * Sets the position for listing Events. + * Sets the position for listing ModelComments. */ - cursor?: EventWhereUniqueInput + cursor?: ModelCommentWhereUniqueInput /** * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} * - * Take `±n` Events from the position of the cursor. + * Take `±n` ModelComments 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` Events. + * Skip the first `n` ModelComments. */ skip?: number /** * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} * - * Filter by unique combinations of Events. + * Filter by unique combinations of ModelComments. */ - distinct?: EventScalarFieldEnum | EventScalarFieldEnum[] + distinct?: ModelCommentScalarFieldEnum | ModelCommentScalarFieldEnum[] } /** - * Event create + * ModelComment create */ - export type EventCreateArgs = { + export type ModelCommentCreateArgs = { /** - * Select specific fields to fetch from the Event + * Select specific fields to fetch from the ModelComment */ - select?: EventSelect | null + select?: ModelCommentSelect | null /** - * Omit specific fields from the Event + * Omit specific fields from the ModelComment */ - omit?: EventOmit | null + omit?: ModelCommentOmit | null /** * Choose, which related nodes to fetch as well */ - include?: EventInclude | null + include?: ModelCommentInclude | null /** - * The data needed to create a Event. + * The data needed to create a ModelComment. */ - data: XOR + data: XOR } /** - * Event createMany + * ModelComment createMany */ - export type EventCreateManyArgs = { + export type ModelCommentCreateManyArgs = { /** - * The data used to create many Events. + * The data used to create many ModelComments. */ - data: EventCreateManyInput | EventCreateManyInput[] + data: ModelCommentCreateManyInput | ModelCommentCreateManyInput[] skipDuplicates?: boolean } /** - * Event createManyAndReturn + * ModelComment createManyAndReturn */ - export type EventCreateManyAndReturnArgs = { + export type ModelCommentCreateManyAndReturnArgs = { /** - * Select specific fields to fetch from the Event + * Select specific fields to fetch from the ModelComment */ - select?: EventSelectCreateManyAndReturn | null + select?: ModelCommentSelectCreateManyAndReturn | null /** - * Omit specific fields from the Event + * Omit specific fields from the ModelComment */ - omit?: EventOmit | null + omit?: ModelCommentOmit | null /** - * The data used to create many Events. + * The data used to create many ModelComments. */ - data: EventCreateManyInput | EventCreateManyInput[] + data: ModelCommentCreateManyInput | ModelCommentCreateManyInput[] skipDuplicates?: boolean /** * Choose, which related nodes to fetch as well */ - include?: EventIncludeCreateManyAndReturn | null + include?: ModelCommentIncludeCreateManyAndReturn | null } /** - * Event update + * ModelComment update */ - export type EventUpdateArgs = { + export type ModelCommentUpdateArgs = { /** - * Select specific fields to fetch from the Event + * Select specific fields to fetch from the ModelComment */ - select?: EventSelect | null + select?: ModelCommentSelect | null /** - * Omit specific fields from the Event + * Omit specific fields from the ModelComment */ - omit?: EventOmit | null + omit?: ModelCommentOmit | null /** * Choose, which related nodes to fetch as well */ - include?: EventInclude | null + include?: ModelCommentInclude | null /** - * The data needed to update a Event. + * The data needed to update a ModelComment. */ - data: XOR + data: XOR /** - * Choose, which Event to update. + * Choose, which ModelComment to update. */ - where: EventWhereUniqueInput + where: ModelCommentWhereUniqueInput } /** - * Event updateMany + * ModelComment updateMany */ - export type EventUpdateManyArgs = { + export type ModelCommentUpdateManyArgs = { /** - * The data used to update Events. + * The data used to update ModelComments. */ - data: XOR + data: XOR /** - * Filter which Events to update + * Filter which ModelComments to update */ - where?: EventWhereInput + where?: ModelCommentWhereInput /** - * Limit how many Events to update. + * Limit how many ModelComments to update. */ limit?: number } /** - * Event updateManyAndReturn + * ModelComment updateManyAndReturn */ - export type EventUpdateManyAndReturnArgs = { + export type ModelCommentUpdateManyAndReturnArgs = { /** - * Select specific fields to fetch from the Event + * Select specific fields to fetch from the ModelComment */ - select?: EventSelectUpdateManyAndReturn | null + select?: ModelCommentSelectUpdateManyAndReturn | null /** - * Omit specific fields from the Event + * Omit specific fields from the ModelComment */ - omit?: EventOmit | null + omit?: ModelCommentOmit | null /** - * The data used to update Events. + * The data used to update ModelComments. */ - data: XOR + data: XOR /** - * Filter which Events to update + * Filter which ModelComments to update */ - where?: EventWhereInput + where?: ModelCommentWhereInput /** - * Limit how many Events to update. + * Limit how many ModelComments to update. */ limit?: number /** * Choose, which related nodes to fetch as well */ - include?: EventIncludeUpdateManyAndReturn | null + include?: ModelCommentIncludeUpdateManyAndReturn | null } /** - * Event upsert + * ModelComment upsert */ - export type EventUpsertArgs = { + export type ModelCommentUpsertArgs = { /** - * Select specific fields to fetch from the Event + * Select specific fields to fetch from the ModelComment */ - select?: EventSelect | null + select?: ModelCommentSelect | null /** - * Omit specific fields from the Event + * Omit specific fields from the ModelComment */ - omit?: EventOmit | null + omit?: ModelCommentOmit | null /** * Choose, which related nodes to fetch as well */ - include?: EventInclude | null + include?: ModelCommentInclude | null /** - * The filter to search for the Event to update in case it exists. + * The filter to search for the ModelComment to update in case it exists. */ - where: EventWhereUniqueInput + where: ModelCommentWhereUniqueInput /** - * In case the Event found by the `where` argument doesn't exist, create a new Event with this data. + * In case the ModelComment found by the `where` argument doesn't exist, create a new ModelComment with this data. */ - create: XOR + create: XOR /** - * In case the Event was found with the provided `where` argument, update it with this data. + * In case the ModelComment was found with the provided `where` argument, update it with this data. */ - update: XOR + update: XOR } /** - * Event delete + * ModelComment delete */ - export type EventDeleteArgs = { + export type ModelCommentDeleteArgs = { /** - * Select specific fields to fetch from the Event + * Select specific fields to fetch from the ModelComment */ - select?: EventSelect | null + select?: ModelCommentSelect | null /** - * Omit specific fields from the Event + * Omit specific fields from the ModelComment */ - omit?: EventOmit | null + omit?: ModelCommentOmit | null /** * Choose, which related nodes to fetch as well */ - include?: EventInclude | null + include?: ModelCommentInclude | null /** - * Filter which Event to delete. + * Filter which ModelComment to delete. */ - where: EventWhereUniqueInput + where: ModelCommentWhereUniqueInput } /** - * Event deleteMany + * ModelComment deleteMany */ - export type EventDeleteManyArgs = { + export type ModelCommentDeleteManyArgs = { /** - * Filter which Events to delete + * Filter which ModelComments to delete */ - where?: EventWhereInput + where?: ModelCommentWhereInput /** - * Limit how many Events to delete. + * Limit how many ModelComments to delete. */ limit?: number } /** - * Event without action + * ModelComment.user */ - export type EventDefaultArgs = { + export type ModelComment$userArgs = { /** - * Select specific fields to fetch from the Event + * Select specific fields to fetch from the User */ - select?: EventSelect | null + select?: UserSelect | null /** - * Omit specific fields from the Event + * Omit specific fields from the User */ - omit?: EventOmit | null + omit?: UserOmit | null /** * Choose, which related nodes to fetch as well */ - include?: EventInclude | null + include?: UserInclude | null + where?: UserWhereInput } - /** - * Enums + * ModelComment.parent */ + export type ModelComment$parentArgs = { + /** + * Select specific fields to fetch from the ModelComment + */ + select?: ModelCommentSelect | null + /** + * Omit specific fields from the ModelComment + */ + omit?: ModelCommentOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: ModelCommentInclude | null + where?: ModelCommentWhereInput + } - export const TransactionIsolationLevel: { - ReadUncommitted: 'ReadUncommitted', - ReadCommitted: 'ReadCommitted', - RepeatableRead: 'RepeatableRead', - Serializable: 'Serializable' - }; + /** + * ModelComment.replies + */ + export type ModelComment$repliesArgs = { + /** + * Select specific fields to fetch from the ModelComment + */ + select?: ModelCommentSelect | null + /** + * Omit specific fields from the ModelComment + */ + omit?: ModelCommentOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: ModelCommentInclude | null + where?: ModelCommentWhereInput + orderBy?: ModelCommentOrderByWithRelationInput | ModelCommentOrderByWithRelationInput[] + cursor?: ModelCommentWhereUniqueInput + take?: number + skip?: number + distinct?: ModelCommentScalarFieldEnum | ModelCommentScalarFieldEnum[] + } - export type TransactionIsolationLevel = (typeof TransactionIsolationLevel)[keyof typeof TransactionIsolationLevel] + /** + * ModelComment.likes + */ + export type ModelComment$likesArgs = { + /** + * Select specific fields to fetch from the ModelCommentLike + */ + select?: ModelCommentLikeSelect | null + /** + * Omit specific fields from the ModelCommentLike + */ + omit?: ModelCommentLikeOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: ModelCommentLikeInclude | null + where?: ModelCommentLikeWhereInput + orderBy?: ModelCommentLikeOrderByWithRelationInput | ModelCommentLikeOrderByWithRelationInput[] + cursor?: ModelCommentLikeWhereUniqueInput + take?: number + skip?: number + distinct?: ModelCommentLikeScalarFieldEnum | ModelCommentLikeScalarFieldEnum[] + } + /** + * ModelComment without action + */ + export type ModelCommentDefaultArgs = { + /** + * Select specific fields to fetch from the ModelComment + */ + select?: ModelCommentSelect | null + /** + * Omit specific fields from the ModelComment + */ + omit?: ModelCommentOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: ModelCommentInclude | null + } - export const UserScalarFieldEnum: { - id: 'id', - name: 'name', - email: 'email', - emailVerified: 'emailVerified', - image: 'image', - createdAt: 'createdAt', - updatedAt: 'updatedAt', - systemRole: 'systemRole', - userKind: 'userKind', - isProfilePublic: 'isProfilePublic', - deletedAt: 'deletedAt', - bio: 'bio', - country: 'country', - socialLinks: 'socialLinks', - dob: 'dob', - affiliation: 'affiliation', - role: 'role', - banned: 'banned', - banReason: 'banReason', - banExpires: 'banExpires', - onboardedAt: 'onboardedAt', - legacyId: 'legacyId' - }; - export type UserScalarFieldEnum = (typeof UserScalarFieldEnum)[keyof typeof UserScalarFieldEnum] + /** + * Model ModelCommentLike + */ + export type AggregateModelCommentLike = { + _count: ModelCommentLikeCountAggregateOutputType | null + _min: ModelCommentLikeMinAggregateOutputType | null + _max: ModelCommentLikeMaxAggregateOutputType | null + } - export const AccountScalarFieldEnum: { - id: 'id', - userId: 'userId', - accountId: 'accountId', - providerId: 'providerId', - accessToken: 'accessToken', - refreshToken: 'refreshToken', - accessTokenExpiresAt: 'accessTokenExpiresAt', - refreshTokenExpiresAt: 'refreshTokenExpiresAt', - scope: 'scope', - idToken: 'idToken', - password: 'password', - createdAt: 'createdAt', - updatedAt: 'updatedAt' - }; + export type ModelCommentLikeMinAggregateOutputType = { + modelCommentId: string | null + userId: string | null + createdAt: Date | null + } - export type AccountScalarFieldEnum = (typeof AccountScalarFieldEnum)[keyof typeof AccountScalarFieldEnum] + export type ModelCommentLikeMaxAggregateOutputType = { + modelCommentId: string | null + userId: string | null + createdAt: Date | null + } + export type ModelCommentLikeCountAggregateOutputType = { + modelCommentId: number + userId: number + createdAt: number + _all: number + } - export const SessionScalarFieldEnum: { - id: 'id', - userId: 'userId', - expiresAt: 'expiresAt', - token: 'token', - ipAddress: 'ipAddress', - userAgent: 'userAgent', - createdAt: 'createdAt', - updatedAt: 'updatedAt', - impersonatedBy: 'impersonatedBy' - }; - export type SessionScalarFieldEnum = (typeof SessionScalarFieldEnum)[keyof typeof SessionScalarFieldEnum] + export type ModelCommentLikeMinAggregateInputType = { + modelCommentId?: true + userId?: true + createdAt?: true + } + export type ModelCommentLikeMaxAggregateInputType = { + modelCommentId?: true + userId?: true + createdAt?: true + } - export const VerificationScalarFieldEnum: { - id: 'id', - identifier: 'identifier', - value: 'value', - expiresAt: 'expiresAt', - createdAt: 'createdAt', - updatedAt: 'updatedAt', - userId: 'userId' - }; + export type ModelCommentLikeCountAggregateInputType = { + modelCommentId?: true + userId?: true + createdAt?: true + _all?: true + } - export type VerificationScalarFieldEnum = (typeof VerificationScalarFieldEnum)[keyof typeof VerificationScalarFieldEnum] + export type ModelCommentLikeAggregateArgs = { + /** + * Filter which ModelCommentLike to aggregate. + */ + where?: ModelCommentLikeWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of ModelCommentLikes to fetch. + */ + orderBy?: ModelCommentLikeOrderByWithRelationInput | ModelCommentLikeOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the start position + */ + cursor?: ModelCommentLikeWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` ModelCommentLikes 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` ModelCommentLikes. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Count returned ModelCommentLikes + **/ + _count?: true | ModelCommentLikeCountAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to find the minimum value + **/ + _min?: ModelCommentLikeMinAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to find the maximum value + **/ + _max?: ModelCommentLikeMaxAggregateInputType + } + export type GetModelCommentLikeAggregateType = { + [P in keyof T & keyof AggregateModelCommentLike]: P extends '_count' | 'count' + ? T[P] extends true + ? number + : GetScalarType + : GetScalarType + } - export const PasskeyScalarFieldEnum: { - id: 'id', - name: 'name', - publicKey: 'publicKey', - userId: 'userId', - credentialID: 'credentialID', - counter: 'counter', - deviceType: 'deviceType', - backedUp: 'backedUp', - transports: 'transports', - createdAt: 'createdAt', - aaguid: 'aaguid' - }; - export type PasskeyScalarFieldEnum = (typeof PasskeyScalarFieldEnum)[keyof typeof PasskeyScalarFieldEnum] - export const ModelScalarFieldEnum: { - id: 'id', - legacyId: 'legacyId', - latestVersionNumber: 'latestVersionNumber', - parentModelId: 'parentModelId', - parentVersionNumber: 'parentVersionNumber', - visibility: 'visibility', - isEndorsed: 'isEndorsed', - isLibraryModel: 'isLibraryModel', - viewCount: 'viewCount', - runCount: 'runCount', - downloadCount: 'downloadCount', - shareCount: 'shareCount', - createdAt: 'createdAt', - updatedAt: 'updatedAt', - deletedAt: 'deletedAt' - }; + export type ModelCommentLikeGroupByArgs = { + where?: ModelCommentLikeWhereInput + orderBy?: ModelCommentLikeOrderByWithAggregationInput | ModelCommentLikeOrderByWithAggregationInput[] + by: ModelCommentLikeScalarFieldEnum[] | ModelCommentLikeScalarFieldEnum + having?: ModelCommentLikeScalarWhereWithAggregatesInput + take?: number + skip?: number + _count?: ModelCommentLikeCountAggregateInputType | true + _min?: ModelCommentLikeMinAggregateInputType + _max?: ModelCommentLikeMaxAggregateInputType + } - export type ModelScalarFieldEnum = (typeof ModelScalarFieldEnum)[keyof typeof ModelScalarFieldEnum] + export type ModelCommentLikeGroupByOutputType = { + modelCommentId: string + userId: string + createdAt: Date + _count: ModelCommentLikeCountAggregateOutputType | null + _min: ModelCommentLikeMinAggregateOutputType | null + _max: ModelCommentLikeMaxAggregateOutputType | null + } + type GetModelCommentLikeGroupByPayload = Prisma.PrismaPromise< + Array< + PickEnumerable & + { + [P in ((keyof T) & (keyof ModelCommentLikeGroupByOutputType))]: P extends '_count' + ? T[P] extends boolean + ? number + : GetScalarType + : GetScalarType + } + > + > - export const ModelVersionScalarFieldEnum: { - modelId: 'modelId', - versionNumber: 'versionNumber', - title: 'title', - description: 'description', - changeSummary: 'changeSummary', - previewImageFileKey: 'previewImageFileKey', - netlogoFileKey: 'netlogoFileKey', - netlogoVersion: 'netlogoVersion', - infoTab: 'infoTab', - createdAt: 'createdAt', - finalizedAt: 'finalizedAt' - }; - export type ModelVersionScalarFieldEnum = (typeof ModelVersionScalarFieldEnum)[keyof typeof ModelVersionScalarFieldEnum] + export type ModelCommentLikeSelect = $Extensions.GetSelect<{ + modelCommentId?: boolean + userId?: boolean + createdAt?: boolean + modelComment?: boolean | ModelCommentDefaultArgs + user?: boolean | UserDefaultArgs + }, ExtArgs["result"]["modelCommentLike"]> + export type ModelCommentLikeSelectCreateManyAndReturn = $Extensions.GetSelect<{ + modelCommentId?: boolean + userId?: boolean + createdAt?: boolean + modelComment?: boolean | ModelCommentDefaultArgs + user?: boolean | UserDefaultArgs + }, ExtArgs["result"]["modelCommentLike"]> - export const ModelVersionTagScalarFieldEnum: { - modelId: 'modelId', - versionNumber: 'versionNumber', - tagId: 'tagId', - createdAt: 'createdAt' - }; + export type ModelCommentLikeSelectUpdateManyAndReturn = $Extensions.GetSelect<{ + modelCommentId?: boolean + userId?: boolean + createdAt?: boolean + modelComment?: boolean | ModelCommentDefaultArgs + user?: boolean | UserDefaultArgs + }, ExtArgs["result"]["modelCommentLike"]> - export type ModelVersionTagScalarFieldEnum = (typeof ModelVersionTagScalarFieldEnum)[keyof typeof ModelVersionTagScalarFieldEnum] + export type ModelCommentLikeSelectScalar = { + modelCommentId?: boolean + userId?: boolean + createdAt?: boolean + } + export type ModelCommentLikeOmit = $Extensions.GetOmit<"modelCommentId" | "userId" | "createdAt", ExtArgs["result"]["modelCommentLike"]> + export type ModelCommentLikeInclude = { + modelComment?: boolean | ModelCommentDefaultArgs + user?: boolean | UserDefaultArgs + } + export type ModelCommentLikeIncludeCreateManyAndReturn = { + modelComment?: boolean | ModelCommentDefaultArgs + user?: boolean | UserDefaultArgs + } + export type ModelCommentLikeIncludeUpdateManyAndReturn = { + modelComment?: boolean | ModelCommentDefaultArgs + user?: boolean | UserDefaultArgs + } - export const ModelAdditionalFileScalarFieldEnum: { - id: 'id', - modelId: 'modelId', - taggedVersionNumber: 'taggedVersionNumber', - fileKey: 'fileKey', - kind: 'kind', - createdAt: 'createdAt' - }; + export type $ModelCommentLikePayload = { + name: "ModelCommentLike" + objects: { + modelComment: Prisma.$ModelCommentPayload + user: Prisma.$UserPayload + } + scalars: $Extensions.GetPayloadResult<{ + modelCommentId: string + userId: string + createdAt: Date + }, ExtArgs["result"]["modelCommentLike"]> + composites: {} + } - export type ModelAdditionalFileScalarFieldEnum = (typeof ModelAdditionalFileScalarFieldEnum)[keyof typeof ModelAdditionalFileScalarFieldEnum] + type ModelCommentLikeGetPayload = $Result.GetResult + type ModelCommentLikeCountArgs = + Omit & { + select?: ModelCommentLikeCountAggregateInputType | true + } - export const TagScalarFieldEnum: { - id: 'id', - legacyId: 'legacyId', - name: 'name', - displayName: 'displayName', - createdAt: 'createdAt' - }; + export interface ModelCommentLikeDelegate { + [K: symbol]: { types: Prisma.TypeMap['model']['ModelCommentLike'], meta: { name: 'ModelCommentLike' } } + /** + * Find zero or one ModelCommentLike that matches the filter. + * @param {ModelCommentLikeFindUniqueArgs} args - Arguments to find a ModelCommentLike + * @example + * // Get one ModelCommentLike + * const modelCommentLike = await prisma.modelCommentLike.findUnique({ + * where: { + * // ... provide filter here + * } + * }) + */ + findUnique(args: SelectSubset>): Prisma__ModelCommentLikeClient<$Result.GetResult, T, "findUnique", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> - export type TagScalarFieldEnum = (typeof TagScalarFieldEnum)[keyof typeof TagScalarFieldEnum] + /** + * Find one ModelCommentLike that matches the filter or throw an error with `error.code='P2025'` + * if no matches were found. + * @param {ModelCommentLikeFindUniqueOrThrowArgs} args - Arguments to find a ModelCommentLike + * @example + * // Get one ModelCommentLike + * const modelCommentLike = await prisma.modelCommentLike.findUniqueOrThrow({ + * where: { + * // ... provide filter here + * } + * }) + */ + findUniqueOrThrow(args: SelectSubset>): Prisma__ModelCommentLikeClient<$Result.GetResult, T, "findUniqueOrThrow", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + /** + * Find the first ModelCommentLike 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 {ModelCommentLikeFindFirstArgs} args - Arguments to find a ModelCommentLike + * @example + * // Get one ModelCommentLike + * const modelCommentLike = await prisma.modelCommentLike.findFirst({ + * where: { + * // ... provide filter here + * } + * }) + */ + findFirst(args?: SelectSubset>): Prisma__ModelCommentLikeClient<$Result.GetResult, T, "findFirst", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> - export const ModelAuthorScalarFieldEnum: { - modelId: 'modelId', - userId: 'userId', - role: 'role', - createdAt: 'createdAt' - }; + /** + * Find the first ModelCommentLike 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 {ModelCommentLikeFindFirstOrThrowArgs} args - Arguments to find a ModelCommentLike + * @example + * // Get one ModelCommentLike + * const modelCommentLike = await prisma.modelCommentLike.findFirstOrThrow({ + * where: { + * // ... provide filter here + * } + * }) + */ + findFirstOrThrow(args?: SelectSubset>): Prisma__ModelCommentLikeClient<$Result.GetResult, T, "findFirstOrThrow", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Find zero or more ModelCommentLikes 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 {ModelCommentLikeFindManyArgs} args - Arguments to filter and select certain fields only. + * @example + * // Get all ModelCommentLikes + * const modelCommentLikes = await prisma.modelCommentLike.findMany() + * + * // Get first 10 ModelCommentLikes + * const modelCommentLikes = await prisma.modelCommentLike.findMany({ take: 10 }) + * + * // Only select the `modelCommentId` + * const modelCommentLikeWithModelCommentIdOnly = await prisma.modelCommentLike.findMany({ select: { modelCommentId: true } }) + * + */ + findMany(args?: SelectSubset>): Prisma.PrismaPromise<$Result.GetResult, T, "findMany", GlobalOmitOptions>> + + /** + * Create a ModelCommentLike. + * @param {ModelCommentLikeCreateArgs} args - Arguments to create a ModelCommentLike. + * @example + * // Create one ModelCommentLike + * const ModelCommentLike = await prisma.modelCommentLike.create({ + * data: { + * // ... data to create a ModelCommentLike + * } + * }) + * + */ + create(args: SelectSubset>): Prisma__ModelCommentLikeClient<$Result.GetResult, T, "create", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Create many ModelCommentLikes. + * @param {ModelCommentLikeCreateManyArgs} args - Arguments to create many ModelCommentLikes. + * @example + * // Create many ModelCommentLikes + * const modelCommentLike = await prisma.modelCommentLike.createMany({ + * data: [ + * // ... provide data here + * ] + * }) + * + */ + createMany(args?: SelectSubset>): Prisma.PrismaPromise + + /** + * Create many ModelCommentLikes and returns the data saved in the database. + * @param {ModelCommentLikeCreateManyAndReturnArgs} args - Arguments to create many ModelCommentLikes. + * @example + * // Create many ModelCommentLikes + * const modelCommentLike = await prisma.modelCommentLike.createManyAndReturn({ + * data: [ + * // ... provide data here + * ] + * }) + * + * // Create many ModelCommentLikes and only return the `modelCommentId` + * const modelCommentLikeWithModelCommentIdOnly = await prisma.modelCommentLike.createManyAndReturn({ + * select: { modelCommentId: 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 ModelCommentLike. + * @param {ModelCommentLikeDeleteArgs} args - Arguments to delete one ModelCommentLike. + * @example + * // Delete one ModelCommentLike + * const ModelCommentLike = await prisma.modelCommentLike.delete({ + * where: { + * // ... filter to delete one ModelCommentLike + * } + * }) + * + */ + delete(args: SelectSubset>): Prisma__ModelCommentLikeClient<$Result.GetResult, T, "delete", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Update one ModelCommentLike. + * @param {ModelCommentLikeUpdateArgs} args - Arguments to update one ModelCommentLike. + * @example + * // Update one ModelCommentLike + * const modelCommentLike = await prisma.modelCommentLike.update({ + * where: { + * // ... provide filter here + * }, + * data: { + * // ... provide data here + * } + * }) + * + */ + update(args: SelectSubset>): Prisma__ModelCommentLikeClient<$Result.GetResult, T, "update", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Delete zero or more ModelCommentLikes. + * @param {ModelCommentLikeDeleteManyArgs} args - Arguments to filter ModelCommentLikes to delete. + * @example + * // Delete a few ModelCommentLikes + * const { count } = await prisma.modelCommentLike.deleteMany({ + * where: { + * // ... provide filter here + * } + * }) + * + */ + deleteMany(args?: SelectSubset>): Prisma.PrismaPromise + + /** + * Update zero or more ModelCommentLikes. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {ModelCommentLikeUpdateManyArgs} args - Arguments to update one or more rows. + * @example + * // Update many ModelCommentLikes + * const modelCommentLike = await prisma.modelCommentLike.updateMany({ + * where: { + * // ... provide filter here + * }, + * data: { + * // ... provide data here + * } + * }) + * + */ + updateMany(args: SelectSubset>): Prisma.PrismaPromise + + /** + * Update zero or more ModelCommentLikes and returns the data updated in the database. + * @param {ModelCommentLikeUpdateManyAndReturnArgs} args - Arguments to update many ModelCommentLikes. + * @example + * // Update many ModelCommentLikes + * const modelCommentLike = await prisma.modelCommentLike.updateManyAndReturn({ + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * + * // Update zero or more ModelCommentLikes and only return the `modelCommentId` + * const modelCommentLikeWithModelCommentIdOnly = await prisma.modelCommentLike.updateManyAndReturn({ + * select: { modelCommentId: 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 ModelCommentLike. + * @param {ModelCommentLikeUpsertArgs} args - Arguments to update or create a ModelCommentLike. + * @example + * // Update or create a ModelCommentLike + * const modelCommentLike = await prisma.modelCommentLike.upsert({ + * create: { + * // ... data to create a ModelCommentLike + * }, + * update: { + * // ... in case it already exists, update + * }, + * where: { + * // ... the filter for the ModelCommentLike we want to update + * } + * }) + */ + upsert(args: SelectSubset>): Prisma__ModelCommentLikeClient<$Result.GetResult, T, "upsert", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + + /** + * Count the number of ModelCommentLikes. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {ModelCommentLikeCountArgs} args - Arguments to filter ModelCommentLikes to count. + * @example + * // Count the number of ModelCommentLikes + * const count = await prisma.modelCommentLike.count({ + * where: { + * // ... the filter for the ModelCommentLikes 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 ModelCommentLike. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {ModelCommentLikeAggregateArgs} 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 ModelCommentLike. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {ModelCommentLikeGroupByArgs} 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 ModelCommentLikeGroupByArgs, + HasSelectOrTake extends Or< + Extends<'skip', Keys>, + Extends<'take', Keys> + >, + OrderByArg extends True extends HasSelectOrTake + ? { orderBy: ModelCommentLikeGroupByArgs['orderBy'] } + : { orderBy?: ModelCommentLikeGroupByArgs['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 ? GetModelCommentLikeGroupByPayload : Prisma.PrismaPromise + /** + * Fields of the ModelCommentLike model + */ + readonly fields: ModelCommentLikeFieldRefs; + } + + /** + * The delegate class that acts as a "Promise-like" for ModelCommentLike. + * 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__ModelCommentLikeClient extends Prisma.PrismaPromise { + readonly [Symbol.toStringTag]: "PrismaPromise" + modelComment = {}>(args?: Subset>): Prisma__ModelCommentClient<$Result.GetResult, T, "findUniqueOrThrow", GlobalOmitOptions> | Null, Null, ExtArgs, GlobalOmitOptions> + user = {}>(args?: Subset>): Prisma__UserClient<$Result.GetResult, T, "findUniqueOrThrow", GlobalOmitOptions> | Null, Null, ExtArgs, GlobalOmitOptions> + /** + * 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 ModelCommentLike model + */ + interface ModelCommentLikeFieldRefs { + readonly modelCommentId: FieldRef<"ModelCommentLike", 'String'> + readonly userId: FieldRef<"ModelCommentLike", 'String'> + readonly createdAt: FieldRef<"ModelCommentLike", 'DateTime'> + } + + + // Custom InputTypes + /** + * ModelCommentLike findUnique + */ + export type ModelCommentLikeFindUniqueArgs = { + /** + * Select specific fields to fetch from the ModelCommentLike + */ + select?: ModelCommentLikeSelect | null + /** + * Omit specific fields from the ModelCommentLike + */ + omit?: ModelCommentLikeOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: ModelCommentLikeInclude | null + /** + * Filter, which ModelCommentLike to fetch. + */ + where: ModelCommentLikeWhereUniqueInput + } + + /** + * ModelCommentLike findUniqueOrThrow + */ + export type ModelCommentLikeFindUniqueOrThrowArgs = { + /** + * Select specific fields to fetch from the ModelCommentLike + */ + select?: ModelCommentLikeSelect | null + /** + * Omit specific fields from the ModelCommentLike + */ + omit?: ModelCommentLikeOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: ModelCommentLikeInclude | null + /** + * Filter, which ModelCommentLike to fetch. + */ + where: ModelCommentLikeWhereUniqueInput + } + + /** + * ModelCommentLike findFirst + */ + export type ModelCommentLikeFindFirstArgs = { + /** + * Select specific fields to fetch from the ModelCommentLike + */ + select?: ModelCommentLikeSelect | null + /** + * Omit specific fields from the ModelCommentLike + */ + omit?: ModelCommentLikeOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: ModelCommentLikeInclude | null + /** + * Filter, which ModelCommentLike to fetch. + */ + where?: ModelCommentLikeWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of ModelCommentLikes to fetch. + */ + orderBy?: ModelCommentLikeOrderByWithRelationInput | ModelCommentLikeOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for searching for ModelCommentLikes. + */ + cursor?: ModelCommentLikeWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` ModelCommentLikes 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` ModelCommentLikes. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of ModelCommentLikes. + */ + distinct?: ModelCommentLikeScalarFieldEnum | ModelCommentLikeScalarFieldEnum[] + } + + /** + * ModelCommentLike findFirstOrThrow + */ + export type ModelCommentLikeFindFirstOrThrowArgs = { + /** + * Select specific fields to fetch from the ModelCommentLike + */ + select?: ModelCommentLikeSelect | null + /** + * Omit specific fields from the ModelCommentLike + */ + omit?: ModelCommentLikeOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: ModelCommentLikeInclude | null + /** + * Filter, which ModelCommentLike to fetch. + */ + where?: ModelCommentLikeWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of ModelCommentLikes to fetch. + */ + orderBy?: ModelCommentLikeOrderByWithRelationInput | ModelCommentLikeOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for searching for ModelCommentLikes. + */ + cursor?: ModelCommentLikeWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` ModelCommentLikes 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` ModelCommentLikes. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of ModelCommentLikes. + */ + distinct?: ModelCommentLikeScalarFieldEnum | ModelCommentLikeScalarFieldEnum[] + } + + /** + * ModelCommentLike findMany + */ + export type ModelCommentLikeFindManyArgs = { + /** + * Select specific fields to fetch from the ModelCommentLike + */ + select?: ModelCommentLikeSelect | null + /** + * Omit specific fields from the ModelCommentLike + */ + omit?: ModelCommentLikeOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: ModelCommentLikeInclude | null + /** + * Filter, which ModelCommentLikes to fetch. + */ + where?: ModelCommentLikeWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of ModelCommentLikes to fetch. + */ + orderBy?: ModelCommentLikeOrderByWithRelationInput | ModelCommentLikeOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for listing ModelCommentLikes. + */ + cursor?: ModelCommentLikeWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` ModelCommentLikes 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` ModelCommentLikes. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of ModelCommentLikes. + */ + distinct?: ModelCommentLikeScalarFieldEnum | ModelCommentLikeScalarFieldEnum[] + } + + /** + * ModelCommentLike create + */ + export type ModelCommentLikeCreateArgs = { + /** + * Select specific fields to fetch from the ModelCommentLike + */ + select?: ModelCommentLikeSelect | null + /** + * Omit specific fields from the ModelCommentLike + */ + omit?: ModelCommentLikeOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: ModelCommentLikeInclude | null + /** + * The data needed to create a ModelCommentLike. + */ + data: XOR + } + + /** + * ModelCommentLike createMany + */ + export type ModelCommentLikeCreateManyArgs = { + /** + * The data used to create many ModelCommentLikes. + */ + data: ModelCommentLikeCreateManyInput | ModelCommentLikeCreateManyInput[] + skipDuplicates?: boolean + } + + /** + * ModelCommentLike createManyAndReturn + */ + export type ModelCommentLikeCreateManyAndReturnArgs = { + /** + * Select specific fields to fetch from the ModelCommentLike + */ + select?: ModelCommentLikeSelectCreateManyAndReturn | null + /** + * Omit specific fields from the ModelCommentLike + */ + omit?: ModelCommentLikeOmit | null + /** + * The data used to create many ModelCommentLikes. + */ + data: ModelCommentLikeCreateManyInput | ModelCommentLikeCreateManyInput[] + skipDuplicates?: boolean + /** + * Choose, which related nodes to fetch as well + */ + include?: ModelCommentLikeIncludeCreateManyAndReturn | null + } + + /** + * ModelCommentLike update + */ + export type ModelCommentLikeUpdateArgs = { + /** + * Select specific fields to fetch from the ModelCommentLike + */ + select?: ModelCommentLikeSelect | null + /** + * Omit specific fields from the ModelCommentLike + */ + omit?: ModelCommentLikeOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: ModelCommentLikeInclude | null + /** + * The data needed to update a ModelCommentLike. + */ + data: XOR + /** + * Choose, which ModelCommentLike to update. + */ + where: ModelCommentLikeWhereUniqueInput + } + + /** + * ModelCommentLike updateMany + */ + export type ModelCommentLikeUpdateManyArgs = { + /** + * The data used to update ModelCommentLikes. + */ + data: XOR + /** + * Filter which ModelCommentLikes to update + */ + where?: ModelCommentLikeWhereInput + /** + * Limit how many ModelCommentLikes to update. + */ + limit?: number + } + + /** + * ModelCommentLike updateManyAndReturn + */ + export type ModelCommentLikeUpdateManyAndReturnArgs = { + /** + * Select specific fields to fetch from the ModelCommentLike + */ + select?: ModelCommentLikeSelectUpdateManyAndReturn | null + /** + * Omit specific fields from the ModelCommentLike + */ + omit?: ModelCommentLikeOmit | null + /** + * The data used to update ModelCommentLikes. + */ + data: XOR + /** + * Filter which ModelCommentLikes to update + */ + where?: ModelCommentLikeWhereInput + /** + * Limit how many ModelCommentLikes to update. + */ + limit?: number + /** + * Choose, which related nodes to fetch as well + */ + include?: ModelCommentLikeIncludeUpdateManyAndReturn | null + } + + /** + * ModelCommentLike upsert + */ + export type ModelCommentLikeUpsertArgs = { + /** + * Select specific fields to fetch from the ModelCommentLike + */ + select?: ModelCommentLikeSelect | null + /** + * Omit specific fields from the ModelCommentLike + */ + omit?: ModelCommentLikeOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: ModelCommentLikeInclude | null + /** + * The filter to search for the ModelCommentLike to update in case it exists. + */ + where: ModelCommentLikeWhereUniqueInput + /** + * In case the ModelCommentLike found by the `where` argument doesn't exist, create a new ModelCommentLike with this data. + */ + create: XOR + /** + * In case the ModelCommentLike was found with the provided `where` argument, update it with this data. + */ + update: XOR + } + + /** + * ModelCommentLike delete + */ + export type ModelCommentLikeDeleteArgs = { + /** + * Select specific fields to fetch from the ModelCommentLike + */ + select?: ModelCommentLikeSelect | null + /** + * Omit specific fields from the ModelCommentLike + */ + omit?: ModelCommentLikeOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: ModelCommentLikeInclude | null + /** + * Filter which ModelCommentLike to delete. + */ + where: ModelCommentLikeWhereUniqueInput + } + + /** + * ModelCommentLike deleteMany + */ + export type ModelCommentLikeDeleteManyArgs = { + /** + * Filter which ModelCommentLikes to delete + */ + where?: ModelCommentLikeWhereInput + /** + * Limit how many ModelCommentLikes to delete. + */ + limit?: number + } + + /** + * ModelCommentLike without action + */ + export type ModelCommentLikeDefaultArgs = { + /** + * Select specific fields to fetch from the ModelCommentLike + */ + select?: ModelCommentLikeSelect | null + /** + * Omit specific fields from the ModelCommentLike + */ + omit?: ModelCommentLikeOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: ModelCommentLikeInclude | null + } + + + /** + * Model Event + */ + + export type AggregateEvent = { + _count: EventCountAggregateOutputType | null + _min: EventMinAggregateOutputType | null + _max: EventMaxAggregateOutputType | null + } + + export type EventMinAggregateOutputType = { + id: string | null + type: string | null + actorId: string | null + resourceType: string | null + resourceId: string | null + createdAt: Date | null + processedAt: Date | null + } + + export type EventMaxAggregateOutputType = { + id: string | null + type: string | null + actorId: string | null + resourceType: string | null + resourceId: string | null + createdAt: Date | null + processedAt: Date | null + } + + export type EventCountAggregateOutputType = { + id: number + type: number + actorId: number + resourceType: number + resourceId: number + payload: number + createdAt: number + processedAt: number + _all: number + } + + + export type EventMinAggregateInputType = { + id?: true + type?: true + actorId?: true + resourceType?: true + resourceId?: true + createdAt?: true + processedAt?: true + } + + export type EventMaxAggregateInputType = { + id?: true + type?: true + actorId?: true + resourceType?: true + resourceId?: true + createdAt?: true + processedAt?: true + } + + export type EventCountAggregateInputType = { + id?: true + type?: true + actorId?: true + resourceType?: true + resourceId?: true + payload?: true + createdAt?: true + processedAt?: true + _all?: true + } + + export type EventAggregateArgs = { + /** + * Filter which Event to aggregate. + */ + where?: EventWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of Events to fetch. + */ + orderBy?: EventOrderByWithRelationInput | EventOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the start position + */ + cursor?: EventWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` Events 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` Events. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Count returned Events + **/ + _count?: true | EventCountAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to find the minimum value + **/ + _min?: EventMinAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to find the maximum value + **/ + _max?: EventMaxAggregateInputType + } + + export type GetEventAggregateType = { + [P in keyof T & keyof AggregateEvent]: P extends '_count' | 'count' + ? T[P] extends true + ? number + : GetScalarType + : GetScalarType + } + + + + + export type EventGroupByArgs = { + where?: EventWhereInput + orderBy?: EventOrderByWithAggregationInput | EventOrderByWithAggregationInput[] + by: EventScalarFieldEnum[] | EventScalarFieldEnum + having?: EventScalarWhereWithAggregatesInput + take?: number + skip?: number + _count?: EventCountAggregateInputType | true + _min?: EventMinAggregateInputType + _max?: EventMaxAggregateInputType + } + + export type EventGroupByOutputType = { + id: string + type: string + actorId: string + resourceType: string + resourceId: string + payload: JsonValue + createdAt: Date + processedAt: Date | null + _count: EventCountAggregateOutputType | null + _min: EventMinAggregateOutputType | null + _max: EventMaxAggregateOutputType | null + } + + type GetEventGroupByPayload = Prisma.PrismaPromise< + Array< + PickEnumerable & + { + [P in ((keyof T) & (keyof EventGroupByOutputType))]: P extends '_count' + ? T[P] extends boolean + ? number + : GetScalarType + : GetScalarType + } + > + > + + + export type EventSelect = $Extensions.GetSelect<{ + id?: boolean + type?: boolean + actorId?: boolean + resourceType?: boolean + resourceId?: boolean + payload?: boolean + createdAt?: boolean + processedAt?: boolean + actor?: boolean | UserDefaultArgs + }, ExtArgs["result"]["event"]> + + export type EventSelectCreateManyAndReturn = $Extensions.GetSelect<{ + id?: boolean + type?: boolean + actorId?: boolean + resourceType?: boolean + resourceId?: boolean + payload?: boolean + createdAt?: boolean + processedAt?: boolean + actor?: boolean | UserDefaultArgs + }, ExtArgs["result"]["event"]> + + export type EventSelectUpdateManyAndReturn = $Extensions.GetSelect<{ + id?: boolean + type?: boolean + actorId?: boolean + resourceType?: boolean + resourceId?: boolean + payload?: boolean + createdAt?: boolean + processedAt?: boolean + actor?: boolean | UserDefaultArgs + }, ExtArgs["result"]["event"]> + + export type EventSelectScalar = { + id?: boolean + type?: boolean + actorId?: boolean + resourceType?: boolean + resourceId?: boolean + payload?: boolean + createdAt?: boolean + processedAt?: boolean + } + + export type EventOmit = $Extensions.GetOmit<"id" | "type" | "actorId" | "resourceType" | "resourceId" | "payload" | "createdAt" | "processedAt", ExtArgs["result"]["event"]> + export type EventInclude = { + actor?: boolean | UserDefaultArgs + } + export type EventIncludeCreateManyAndReturn = { + actor?: boolean | UserDefaultArgs + } + export type EventIncludeUpdateManyAndReturn = { + actor?: boolean | UserDefaultArgs + } + + export type $EventPayload = { + name: "Event" + objects: { + actor: Prisma.$UserPayload + } + scalars: $Extensions.GetPayloadResult<{ + id: string + type: string + actorId: string + resourceType: string + resourceId: string + payload: Prisma.JsonValue + createdAt: Date + processedAt: Date | null + }, ExtArgs["result"]["event"]> + composites: {} + } + + type EventGetPayload = $Result.GetResult + + type EventCountArgs = + Omit & { + select?: EventCountAggregateInputType | true + } + + export interface EventDelegate { + [K: symbol]: { types: Prisma.TypeMap['model']['Event'], meta: { name: 'Event' } } + /** + * Find zero or one Event that matches the filter. + * @param {EventFindUniqueArgs} args - Arguments to find a Event + * @example + * // Get one Event + * const event = await prisma.event.findUnique({ + * where: { + * // ... provide filter here + * } + * }) + */ + findUnique(args: SelectSubset>): Prisma__EventClient<$Result.GetResult, T, "findUnique", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> + + /** + * Find one Event that matches the filter or throw an error with `error.code='P2025'` + * if no matches were found. + * @param {EventFindUniqueOrThrowArgs} args - Arguments to find a Event + * @example + * // Get one Event + * const event = await prisma.event.findUniqueOrThrow({ + * where: { + * // ... provide filter here + * } + * }) + */ + findUniqueOrThrow(args: SelectSubset>): Prisma__EventClient<$Result.GetResult, T, "findUniqueOrThrow", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Find the first Event 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 {EventFindFirstArgs} args - Arguments to find a Event + * @example + * // Get one Event + * const event = await prisma.event.findFirst({ + * where: { + * // ... provide filter here + * } + * }) + */ + findFirst(args?: SelectSubset>): Prisma__EventClient<$Result.GetResult, T, "findFirst", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> + + /** + * Find the first Event 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 {EventFindFirstOrThrowArgs} args - Arguments to find a Event + * @example + * // Get one Event + * const event = await prisma.event.findFirstOrThrow({ + * where: { + * // ... provide filter here + * } + * }) + */ + findFirstOrThrow(args?: SelectSubset>): Prisma__EventClient<$Result.GetResult, T, "findFirstOrThrow", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Find zero or more Events 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 {EventFindManyArgs} args - Arguments to filter and select certain fields only. + * @example + * // Get all Events + * const events = await prisma.event.findMany() + * + * // Get first 10 Events + * const events = await prisma.event.findMany({ take: 10 }) + * + * // Only select the `id` + * const eventWithIdOnly = await prisma.event.findMany({ select: { id: true } }) + * + */ + findMany(args?: SelectSubset>): Prisma.PrismaPromise<$Result.GetResult, T, "findMany", GlobalOmitOptions>> + + /** + * Create a Event. + * @param {EventCreateArgs} args - Arguments to create a Event. + * @example + * // Create one Event + * const Event = await prisma.event.create({ + * data: { + * // ... data to create a Event + * } + * }) + * + */ + create(args: SelectSubset>): Prisma__EventClient<$Result.GetResult, T, "create", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Create many Events. + * @param {EventCreateManyArgs} args - Arguments to create many Events. + * @example + * // Create many Events + * const event = await prisma.event.createMany({ + * data: [ + * // ... provide data here + * ] + * }) + * + */ + createMany(args?: SelectSubset>): Prisma.PrismaPromise + + /** + * Create many Events and returns the data saved in the database. + * @param {EventCreateManyAndReturnArgs} args - Arguments to create many Events. + * @example + * // Create many Events + * const event = await prisma.event.createManyAndReturn({ + * data: [ + * // ... provide data here + * ] + * }) + * + * // Create many Events and only return the `id` + * const eventWithIdOnly = await prisma.event.createManyAndReturn({ + * select: { id: 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 Event. + * @param {EventDeleteArgs} args - Arguments to delete one Event. + * @example + * // Delete one Event + * const Event = await prisma.event.delete({ + * where: { + * // ... filter to delete one Event + * } + * }) + * + */ + delete(args: SelectSubset>): Prisma__EventClient<$Result.GetResult, T, "delete", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Update one Event. + * @param {EventUpdateArgs} args - Arguments to update one Event. + * @example + * // Update one Event + * const event = await prisma.event.update({ + * where: { + * // ... provide filter here + * }, + * data: { + * // ... provide data here + * } + * }) + * + */ + update(args: SelectSubset>): Prisma__EventClient<$Result.GetResult, T, "update", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Delete zero or more Events. + * @param {EventDeleteManyArgs} args - Arguments to filter Events to delete. + * @example + * // Delete a few Events + * const { count } = await prisma.event.deleteMany({ + * where: { + * // ... provide filter here + * } + * }) + * + */ + deleteMany(args?: SelectSubset>): Prisma.PrismaPromise + + /** + * Update zero or more Events. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {EventUpdateManyArgs} args - Arguments to update one or more rows. + * @example + * // Update many Events + * const event = await prisma.event.updateMany({ + * where: { + * // ... provide filter here + * }, + * data: { + * // ... provide data here + * } + * }) + * + */ + updateMany(args: SelectSubset>): Prisma.PrismaPromise + + /** + * Update zero or more Events and returns the data updated in the database. + * @param {EventUpdateManyAndReturnArgs} args - Arguments to update many Events. + * @example + * // Update many Events + * const event = await prisma.event.updateManyAndReturn({ + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * + * // Update zero or more Events and only return the `id` + * const eventWithIdOnly = await prisma.event.updateManyAndReturn({ + * select: { id: 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 Event. + * @param {EventUpsertArgs} args - Arguments to update or create a Event. + * @example + * // Update or create a Event + * const event = await prisma.event.upsert({ + * create: { + * // ... data to create a Event + * }, + * update: { + * // ... in case it already exists, update + * }, + * where: { + * // ... the filter for the Event we want to update + * } + * }) + */ + upsert(args: SelectSubset>): Prisma__EventClient<$Result.GetResult, T, "upsert", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + + /** + * Count the number of Events. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {EventCountArgs} args - Arguments to filter Events to count. + * @example + * // Count the number of Events + * const count = await prisma.event.count({ + * where: { + * // ... the filter for the Events 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 Event. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {EventAggregateArgs} 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 Event. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {EventGroupByArgs} 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 EventGroupByArgs, + HasSelectOrTake extends Or< + Extends<'skip', Keys>, + Extends<'take', Keys> + >, + OrderByArg extends True extends HasSelectOrTake + ? { orderBy: EventGroupByArgs['orderBy'] } + : { orderBy?: EventGroupByArgs['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 ? GetEventGroupByPayload : Prisma.PrismaPromise + /** + * Fields of the Event model + */ + readonly fields: EventFieldRefs; + } + + /** + * The delegate class that acts as a "Promise-like" for Event. + * 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__EventClient extends Prisma.PrismaPromise { + readonly [Symbol.toStringTag]: "PrismaPromise" + actor = {}>(args?: Subset>): Prisma__UserClient<$Result.GetResult, T, "findUniqueOrThrow", GlobalOmitOptions> | Null, Null, ExtArgs, GlobalOmitOptions> + /** + * 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 Event model + */ + interface EventFieldRefs { + readonly id: FieldRef<"Event", 'String'> + readonly type: FieldRef<"Event", 'String'> + readonly actorId: FieldRef<"Event", 'String'> + readonly resourceType: FieldRef<"Event", 'String'> + readonly resourceId: FieldRef<"Event", 'String'> + readonly payload: FieldRef<"Event", 'Json'> + readonly createdAt: FieldRef<"Event", 'DateTime'> + readonly processedAt: FieldRef<"Event", 'DateTime'> + } + + + // Custom InputTypes + /** + * Event findUnique + */ + export type EventFindUniqueArgs = { + /** + * Select specific fields to fetch from the Event + */ + select?: EventSelect | null + /** + * Omit specific fields from the Event + */ + omit?: EventOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: EventInclude | null + /** + * Filter, which Event to fetch. + */ + where: EventWhereUniqueInput + } + + /** + * Event findUniqueOrThrow + */ + export type EventFindUniqueOrThrowArgs = { + /** + * Select specific fields to fetch from the Event + */ + select?: EventSelect | null + /** + * Omit specific fields from the Event + */ + omit?: EventOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: EventInclude | null + /** + * Filter, which Event to fetch. + */ + where: EventWhereUniqueInput + } + + /** + * Event findFirst + */ + export type EventFindFirstArgs = { + /** + * Select specific fields to fetch from the Event + */ + select?: EventSelect | null + /** + * Omit specific fields from the Event + */ + omit?: EventOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: EventInclude | null + /** + * Filter, which Event to fetch. + */ + where?: EventWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of Events to fetch. + */ + orderBy?: EventOrderByWithRelationInput | EventOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for searching for Events. + */ + cursor?: EventWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` Events 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` Events. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of Events. + */ + distinct?: EventScalarFieldEnum | EventScalarFieldEnum[] + } + + /** + * Event findFirstOrThrow + */ + export type EventFindFirstOrThrowArgs = { + /** + * Select specific fields to fetch from the Event + */ + select?: EventSelect | null + /** + * Omit specific fields from the Event + */ + omit?: EventOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: EventInclude | null + /** + * Filter, which Event to fetch. + */ + where?: EventWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of Events to fetch. + */ + orderBy?: EventOrderByWithRelationInput | EventOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for searching for Events. + */ + cursor?: EventWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` Events 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` Events. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of Events. + */ + distinct?: EventScalarFieldEnum | EventScalarFieldEnum[] + } + + /** + * Event findMany + */ + export type EventFindManyArgs = { + /** + * Select specific fields to fetch from the Event + */ + select?: EventSelect | null + /** + * Omit specific fields from the Event + */ + omit?: EventOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: EventInclude | null + /** + * Filter, which Events to fetch. + */ + where?: EventWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of Events to fetch. + */ + orderBy?: EventOrderByWithRelationInput | EventOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for listing Events. + */ + cursor?: EventWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` Events 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` Events. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of Events. + */ + distinct?: EventScalarFieldEnum | EventScalarFieldEnum[] + } + + /** + * Event create + */ + export type EventCreateArgs = { + /** + * Select specific fields to fetch from the Event + */ + select?: EventSelect | null + /** + * Omit specific fields from the Event + */ + omit?: EventOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: EventInclude | null + /** + * The data needed to create a Event. + */ + data: XOR + } + + /** + * Event createMany + */ + export type EventCreateManyArgs = { + /** + * The data used to create many Events. + */ + data: EventCreateManyInput | EventCreateManyInput[] + skipDuplicates?: boolean + } + + /** + * Event createManyAndReturn + */ + export type EventCreateManyAndReturnArgs = { + /** + * Select specific fields to fetch from the Event + */ + select?: EventSelectCreateManyAndReturn | null + /** + * Omit specific fields from the Event + */ + omit?: EventOmit | null + /** + * The data used to create many Events. + */ + data: EventCreateManyInput | EventCreateManyInput[] + skipDuplicates?: boolean + /** + * Choose, which related nodes to fetch as well + */ + include?: EventIncludeCreateManyAndReturn | null + } + + /** + * Event update + */ + export type EventUpdateArgs = { + /** + * Select specific fields to fetch from the Event + */ + select?: EventSelect | null + /** + * Omit specific fields from the Event + */ + omit?: EventOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: EventInclude | null + /** + * The data needed to update a Event. + */ + data: XOR + /** + * Choose, which Event to update. + */ + where: EventWhereUniqueInput + } + + /** + * Event updateMany + */ + export type EventUpdateManyArgs = { + /** + * The data used to update Events. + */ + data: XOR + /** + * Filter which Events to update + */ + where?: EventWhereInput + /** + * Limit how many Events to update. + */ + limit?: number + } + + /** + * Event updateManyAndReturn + */ + export type EventUpdateManyAndReturnArgs = { + /** + * Select specific fields to fetch from the Event + */ + select?: EventSelectUpdateManyAndReturn | null + /** + * Omit specific fields from the Event + */ + omit?: EventOmit | null + /** + * The data used to update Events. + */ + data: XOR + /** + * Filter which Events to update + */ + where?: EventWhereInput + /** + * Limit how many Events to update. + */ + limit?: number + /** + * Choose, which related nodes to fetch as well + */ + include?: EventIncludeUpdateManyAndReturn | null + } + + /** + * Event upsert + */ + export type EventUpsertArgs = { + /** + * Select specific fields to fetch from the Event + */ + select?: EventSelect | null + /** + * Omit specific fields from the Event + */ + omit?: EventOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: EventInclude | null + /** + * The filter to search for the Event to update in case it exists. + */ + where: EventWhereUniqueInput + /** + * In case the Event found by the `where` argument doesn't exist, create a new Event with this data. + */ + create: XOR + /** + * In case the Event was found with the provided `where` argument, update it with this data. + */ + update: XOR + } + + /** + * Event delete + */ + export type EventDeleteArgs = { + /** + * Select specific fields to fetch from the Event + */ + select?: EventSelect | null + /** + * Omit specific fields from the Event + */ + omit?: EventOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: EventInclude | null + /** + * Filter which Event to delete. + */ + where: EventWhereUniqueInput + } + + /** + * Event deleteMany + */ + export type EventDeleteManyArgs = { + /** + * Filter which Events to delete + */ + where?: EventWhereInput + /** + * Limit how many Events to delete. + */ + limit?: number + } + + /** + * Event without action + */ + export type EventDefaultArgs = { + /** + * Select specific fields to fetch from the Event + */ + select?: EventSelect | null + /** + * Omit specific fields from the Event + */ + omit?: EventOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: EventInclude | null + } + + + /** + * Enums + */ + + export const TransactionIsolationLevel: { + ReadUncommitted: 'ReadUncommitted', + ReadCommitted: 'ReadCommitted', + RepeatableRead: 'RepeatableRead', + Serializable: 'Serializable' + }; + + export type TransactionIsolationLevel = (typeof TransactionIsolationLevel)[keyof typeof TransactionIsolationLevel] + + + export const UserScalarFieldEnum: { + id: 'id', + name: 'name', + email: 'email', + emailVerified: 'emailVerified', + image: 'image', + createdAt: 'createdAt', + updatedAt: 'updatedAt', + systemRole: 'systemRole', + userKind: 'userKind', + isProfilePublic: 'isProfilePublic', + deletedAt: 'deletedAt', + bio: 'bio', + country: 'country', + socialLinks: 'socialLinks', + dob: 'dob', + affiliation: 'affiliation', + role: 'role', + banned: 'banned', + banReason: 'banReason', + banExpires: 'banExpires', + onboardedAt: 'onboardedAt', + legacyId: 'legacyId' + }; + + export type UserScalarFieldEnum = (typeof UserScalarFieldEnum)[keyof typeof UserScalarFieldEnum] + + + export const AccountScalarFieldEnum: { + id: 'id', + userId: 'userId', + accountId: 'accountId', + providerId: 'providerId', + accessToken: 'accessToken', + refreshToken: 'refreshToken', + accessTokenExpiresAt: 'accessTokenExpiresAt', + refreshTokenExpiresAt: 'refreshTokenExpiresAt', + scope: 'scope', + idToken: 'idToken', + password: 'password', + createdAt: 'createdAt', + updatedAt: 'updatedAt' + }; + + export type AccountScalarFieldEnum = (typeof AccountScalarFieldEnum)[keyof typeof AccountScalarFieldEnum] + + + export const SessionScalarFieldEnum: { + id: 'id', + userId: 'userId', + expiresAt: 'expiresAt', + token: 'token', + ipAddress: 'ipAddress', + userAgent: 'userAgent', + createdAt: 'createdAt', + updatedAt: 'updatedAt', + impersonatedBy: 'impersonatedBy' + }; + + export type SessionScalarFieldEnum = (typeof SessionScalarFieldEnum)[keyof typeof SessionScalarFieldEnum] - export type ModelAuthorScalarFieldEnum = (typeof ModelAuthorScalarFieldEnum)[keyof typeof ModelAuthorScalarFieldEnum] + + export const VerificationScalarFieldEnum: { + id: 'id', + identifier: 'identifier', + value: 'value', + expiresAt: 'expiresAt', + createdAt: 'createdAt', + updatedAt: 'updatedAt', + userId: 'userId' + }; + + export type VerificationScalarFieldEnum = (typeof VerificationScalarFieldEnum)[keyof typeof VerificationScalarFieldEnum] + + + export const PasskeyScalarFieldEnum: { + id: 'id', + name: 'name', + publicKey: 'publicKey', + userId: 'userId', + credentialID: 'credentialID', + counter: 'counter', + deviceType: 'deviceType', + backedUp: 'backedUp', + transports: 'transports', + createdAt: 'createdAt', + aaguid: 'aaguid' + }; + + export type PasskeyScalarFieldEnum = (typeof PasskeyScalarFieldEnum)[keyof typeof PasskeyScalarFieldEnum] + + + export const ModelScalarFieldEnum: { + id: 'id', + legacyId: 'legacyId', + latestVersionNumber: 'latestVersionNumber', + parentModelId: 'parentModelId', + parentVersionNumber: 'parentVersionNumber', + visibility: 'visibility', + isEndorsed: 'isEndorsed', + isLibraryModel: 'isLibraryModel', + viewCount: 'viewCount', + runCount: 'runCount', + downloadCount: 'downloadCount', + shareCount: 'shareCount', + createdAt: 'createdAt', + updatedAt: 'updatedAt', + deletedAt: 'deletedAt' + }; + + export type ModelScalarFieldEnum = (typeof ModelScalarFieldEnum)[keyof typeof ModelScalarFieldEnum] + + + export const ModelVersionScalarFieldEnum: { + modelId: 'modelId', + versionNumber: 'versionNumber', + title: 'title', + description: 'description', + changeSummary: 'changeSummary', + previewImageFileKey: 'previewImageFileKey', + netlogoFileKey: 'netlogoFileKey', + netlogoVersion: 'netlogoVersion', + infoTab: 'infoTab', + createdAt: 'createdAt', + finalizedAt: 'finalizedAt' + }; + + export type ModelVersionScalarFieldEnum = (typeof ModelVersionScalarFieldEnum)[keyof typeof ModelVersionScalarFieldEnum] + + + export const ModelVersionTagScalarFieldEnum: { + modelId: 'modelId', + versionNumber: 'versionNumber', + tagId: 'tagId', + createdAt: 'createdAt' + }; + + export type ModelVersionTagScalarFieldEnum = (typeof ModelVersionTagScalarFieldEnum)[keyof typeof ModelVersionTagScalarFieldEnum] + + + export const ModelAdditionalFileScalarFieldEnum: { + id: 'id', + modelId: 'modelId', + taggedVersionNumber: 'taggedVersionNumber', + fileKey: 'fileKey', + kind: 'kind', + createdAt: 'createdAt' + }; + + export type ModelAdditionalFileScalarFieldEnum = (typeof ModelAdditionalFileScalarFieldEnum)[keyof typeof ModelAdditionalFileScalarFieldEnum] + + + export const TagScalarFieldEnum: { + id: 'id', + legacyId: 'legacyId', + name: 'name', + displayName: 'displayName', + createdAt: 'createdAt' + }; + + export type TagScalarFieldEnum = (typeof TagScalarFieldEnum)[keyof typeof TagScalarFieldEnum] + + + export const ModelAuthorScalarFieldEnum: { + modelId: 'modelId', + userId: 'userId', + role: 'role', + createdAt: 'createdAt' + }; + + export type ModelAuthorScalarFieldEnum = (typeof ModelAuthorScalarFieldEnum)[keyof typeof ModelAuthorScalarFieldEnum] export const ModelPermissionScalarFieldEnum: { @@ -21794,6 +24480,33 @@ export namespace Prisma { export type ModelDraftScalarFieldEnum = (typeof ModelDraftScalarFieldEnum)[keyof typeof ModelDraftScalarFieldEnum] + export const ModelCommentScalarFieldEnum: { + id: 'id', + legacyId: 'legacyId', + parentId: 'parentId', + userId: 'userId', + modelId: 'modelId', + versionNumber: 'versionNumber', + content: 'content', + likesCount: 'likesCount', + createdAt: 'createdAt', + updatedAt: 'updatedAt', + editedAt: 'editedAt', + deletedAt: 'deletedAt' + }; + + export type ModelCommentScalarFieldEnum = (typeof ModelCommentScalarFieldEnum)[keyof typeof ModelCommentScalarFieldEnum] + + + export const ModelCommentLikeScalarFieldEnum: { + modelCommentId: 'modelCommentId', + userId: 'userId', + createdAt: 'createdAt' + }; + + export type ModelCommentLikeScalarFieldEnum = (typeof ModelCommentLikeScalarFieldEnum)[keyof typeof ModelCommentLikeScalarFieldEnum] + + export const EventScalarFieldEnum: { id: 'id', type: 'type', @@ -22074,6 +24787,8 @@ export namespace Prisma { modelLikes?: ModelLikeListRelationFilter modelInteractions?: ModelInteractionListRelationFilter modelDrafts?: ModelDraftListRelationFilter + comments?: ModelCommentListRelationFilter + commentLikes?: ModelCommentLikeListRelationFilter passkeys?: PasskeyListRelationFilter } @@ -22109,6 +24824,8 @@ export namespace Prisma { modelLikes?: ModelLikeOrderByRelationAggregateInput modelInteractions?: ModelInteractionOrderByRelationAggregateInput modelDrafts?: ModelDraftOrderByRelationAggregateInput + comments?: ModelCommentOrderByRelationAggregateInput + commentLikes?: ModelCommentLikeOrderByRelationAggregateInput passkeys?: PasskeyOrderByRelationAggregateInput } @@ -22147,6 +24864,8 @@ export namespace Prisma { modelLikes?: ModelLikeListRelationFilter modelInteractions?: ModelInteractionListRelationFilter modelDrafts?: ModelDraftListRelationFilter + comments?: ModelCommentListRelationFilter + commentLikes?: ModelCommentLikeListRelationFilter passkeys?: PasskeyListRelationFilter }, "id" | "email" | "legacyId"> @@ -22560,6 +25279,7 @@ export namespace Prisma { likes?: ModelLikeListRelationFilter interactions?: ModelInteractionListRelationFilter drafts?: ModelDraftListRelationFilter + comments?: ModelCommentListRelationFilter } export type ModelOrderByWithRelationInput = { @@ -22589,6 +25309,7 @@ export namespace Prisma { likes?: ModelLikeOrderByRelationAggregateInput interactions?: ModelInteractionOrderByRelationAggregateInput drafts?: ModelDraftOrderByRelationAggregateInput + comments?: ModelCommentOrderByRelationAggregateInput } export type ModelWhereUniqueInput = Prisma.AtLeast<{ @@ -22622,6 +25343,7 @@ export namespace Prisma { likes?: ModelLikeListRelationFilter interactions?: ModelInteractionListRelationFilter drafts?: ModelDraftListRelationFilter + comments?: ModelCommentListRelationFilter }, "id" | "legacyId" | "id_latestVersionNumber"> export type ModelOrderByWithAggregationInput = { @@ -23273,6 +25995,159 @@ export namespace Prisma { updatedAt?: DateTimeWithAggregatesFilter<"ModelDraft"> | Date | string } + export type ModelCommentWhereInput = { + AND?: ModelCommentWhereInput | ModelCommentWhereInput[] + OR?: ModelCommentWhereInput[] + NOT?: ModelCommentWhereInput | ModelCommentWhereInput[] + id?: StringFilter<"ModelComment"> | string + legacyId?: IntNullableFilter<"ModelComment"> | number | null + parentId?: StringNullableFilter<"ModelComment"> | string | null + userId?: StringNullableFilter<"ModelComment"> | string | null + modelId?: StringFilter<"ModelComment"> | string + versionNumber?: IntNullableFilter<"ModelComment"> | number | null + content?: StringNullableFilter<"ModelComment"> | string | null + likesCount?: IntFilter<"ModelComment"> | number + createdAt?: DateTimeFilter<"ModelComment"> | Date | string + updatedAt?: DateTimeFilter<"ModelComment"> | Date | string + editedAt?: DateTimeNullableFilter<"ModelComment"> | Date | string | null + deletedAt?: DateTimeNullableFilter<"ModelComment"> | Date | string | null + model?: XOR + user?: XOR | null + parent?: XOR | null + replies?: ModelCommentListRelationFilter + likes?: ModelCommentLikeListRelationFilter + } + + export type ModelCommentOrderByWithRelationInput = { + id?: SortOrder + legacyId?: SortOrderInput | SortOrder + parentId?: SortOrderInput | SortOrder + userId?: SortOrderInput | SortOrder + modelId?: SortOrder + versionNumber?: SortOrderInput | SortOrder + content?: SortOrderInput | SortOrder + likesCount?: SortOrder + createdAt?: SortOrder + updatedAt?: SortOrder + editedAt?: SortOrderInput | SortOrder + deletedAt?: SortOrderInput | SortOrder + model?: ModelOrderByWithRelationInput + user?: UserOrderByWithRelationInput + parent?: ModelCommentOrderByWithRelationInput + replies?: ModelCommentOrderByRelationAggregateInput + likes?: ModelCommentLikeOrderByRelationAggregateInput + } + + export type ModelCommentWhereUniqueInput = Prisma.AtLeast<{ + id?: string + legacyId?: number + AND?: ModelCommentWhereInput | ModelCommentWhereInput[] + OR?: ModelCommentWhereInput[] + NOT?: ModelCommentWhereInput | ModelCommentWhereInput[] + parentId?: StringNullableFilter<"ModelComment"> | string | null + userId?: StringNullableFilter<"ModelComment"> | string | null + modelId?: StringFilter<"ModelComment"> | string + versionNumber?: IntNullableFilter<"ModelComment"> | number | null + content?: StringNullableFilter<"ModelComment"> | string | null + likesCount?: IntFilter<"ModelComment"> | number + createdAt?: DateTimeFilter<"ModelComment"> | Date | string + updatedAt?: DateTimeFilter<"ModelComment"> | Date | string + editedAt?: DateTimeNullableFilter<"ModelComment"> | Date | string | null + deletedAt?: DateTimeNullableFilter<"ModelComment"> | Date | string | null + model?: XOR + user?: XOR | null + parent?: XOR | null + replies?: ModelCommentListRelationFilter + likes?: ModelCommentLikeListRelationFilter + }, "id" | "legacyId"> + + export type ModelCommentOrderByWithAggregationInput = { + id?: SortOrder + legacyId?: SortOrderInput | SortOrder + parentId?: SortOrderInput | SortOrder + userId?: SortOrderInput | SortOrder + modelId?: SortOrder + versionNumber?: SortOrderInput | SortOrder + content?: SortOrderInput | SortOrder + likesCount?: SortOrder + createdAt?: SortOrder + updatedAt?: SortOrder + editedAt?: SortOrderInput | SortOrder + deletedAt?: SortOrderInput | SortOrder + _count?: ModelCommentCountOrderByAggregateInput + _avg?: ModelCommentAvgOrderByAggregateInput + _max?: ModelCommentMaxOrderByAggregateInput + _min?: ModelCommentMinOrderByAggregateInput + _sum?: ModelCommentSumOrderByAggregateInput + } + + export type ModelCommentScalarWhereWithAggregatesInput = { + AND?: ModelCommentScalarWhereWithAggregatesInput | ModelCommentScalarWhereWithAggregatesInput[] + OR?: ModelCommentScalarWhereWithAggregatesInput[] + NOT?: ModelCommentScalarWhereWithAggregatesInput | ModelCommentScalarWhereWithAggregatesInput[] + id?: StringWithAggregatesFilter<"ModelComment"> | string + legacyId?: IntNullableWithAggregatesFilter<"ModelComment"> | number | null + parentId?: StringNullableWithAggregatesFilter<"ModelComment"> | string | null + userId?: StringNullableWithAggregatesFilter<"ModelComment"> | string | null + modelId?: StringWithAggregatesFilter<"ModelComment"> | string + versionNumber?: IntNullableWithAggregatesFilter<"ModelComment"> | number | null + content?: StringNullableWithAggregatesFilter<"ModelComment"> | string | null + likesCount?: IntWithAggregatesFilter<"ModelComment"> | number + createdAt?: DateTimeWithAggregatesFilter<"ModelComment"> | Date | string + updatedAt?: DateTimeWithAggregatesFilter<"ModelComment"> | Date | string + editedAt?: DateTimeNullableWithAggregatesFilter<"ModelComment"> | Date | string | null + deletedAt?: DateTimeNullableWithAggregatesFilter<"ModelComment"> | Date | string | null + } + + export type ModelCommentLikeWhereInput = { + AND?: ModelCommentLikeWhereInput | ModelCommentLikeWhereInput[] + OR?: ModelCommentLikeWhereInput[] + NOT?: ModelCommentLikeWhereInput | ModelCommentLikeWhereInput[] + modelCommentId?: StringFilter<"ModelCommentLike"> | string + userId?: StringFilter<"ModelCommentLike"> | string + createdAt?: DateTimeFilter<"ModelCommentLike"> | Date | string + modelComment?: XOR + user?: XOR + } + + export type ModelCommentLikeOrderByWithRelationInput = { + modelCommentId?: SortOrder + userId?: SortOrder + createdAt?: SortOrder + modelComment?: ModelCommentOrderByWithRelationInput + user?: UserOrderByWithRelationInput + } + + export type ModelCommentLikeWhereUniqueInput = Prisma.AtLeast<{ + modelCommentId_userId?: ModelCommentLikeModelCommentIdUserIdCompoundUniqueInput + AND?: ModelCommentLikeWhereInput | ModelCommentLikeWhereInput[] + OR?: ModelCommentLikeWhereInput[] + NOT?: ModelCommentLikeWhereInput | ModelCommentLikeWhereInput[] + modelCommentId?: StringFilter<"ModelCommentLike"> | string + userId?: StringFilter<"ModelCommentLike"> | string + createdAt?: DateTimeFilter<"ModelCommentLike"> | Date | string + modelComment?: XOR + user?: XOR + }, "modelCommentId_userId"> + + export type ModelCommentLikeOrderByWithAggregationInput = { + modelCommentId?: SortOrder + userId?: SortOrder + createdAt?: SortOrder + _count?: ModelCommentLikeCountOrderByAggregateInput + _max?: ModelCommentLikeMaxOrderByAggregateInput + _min?: ModelCommentLikeMinOrderByAggregateInput + } + + export type ModelCommentLikeScalarWhereWithAggregatesInput = { + AND?: ModelCommentLikeScalarWhereWithAggregatesInput | ModelCommentLikeScalarWhereWithAggregatesInput[] + OR?: ModelCommentLikeScalarWhereWithAggregatesInput[] + NOT?: ModelCommentLikeScalarWhereWithAggregatesInput | ModelCommentLikeScalarWhereWithAggregatesInput[] + modelCommentId?: StringWithAggregatesFilter<"ModelCommentLike"> | string + userId?: StringWithAggregatesFilter<"ModelCommentLike"> | string + createdAt?: DateTimeWithAggregatesFilter<"ModelCommentLike"> | Date | string + } + export type EventWhereInput = { AND?: EventWhereInput | EventWhereInput[] OR?: EventWhereInput[] @@ -23375,6 +26250,8 @@ export namespace Prisma { modelLikes?: ModelLikeCreateNestedManyWithoutUserInput modelInteractions?: ModelInteractionCreateNestedManyWithoutUserInput modelDrafts?: ModelDraftCreateNestedManyWithoutUserInput + comments?: ModelCommentCreateNestedManyWithoutUserInput + commentLikes?: ModelCommentLikeCreateNestedManyWithoutUserInput passkeys?: PasskeyCreateNestedManyWithoutUserInput } @@ -23410,6 +26287,8 @@ export namespace Prisma { modelLikes?: ModelLikeUncheckedCreateNestedManyWithoutUserInput modelInteractions?: ModelInteractionUncheckedCreateNestedManyWithoutUserInput modelDrafts?: ModelDraftUncheckedCreateNestedManyWithoutUserInput + comments?: ModelCommentUncheckedCreateNestedManyWithoutUserInput + commentLikes?: ModelCommentLikeUncheckedCreateNestedManyWithoutUserInput passkeys?: PasskeyUncheckedCreateNestedManyWithoutUserInput } @@ -23445,6 +26324,8 @@ export namespace Prisma { modelLikes?: ModelLikeUpdateManyWithoutUserNestedInput modelInteractions?: ModelInteractionUpdateManyWithoutUserNestedInput modelDrafts?: ModelDraftUpdateManyWithoutUserNestedInput + comments?: ModelCommentUpdateManyWithoutUserNestedInput + commentLikes?: ModelCommentLikeUpdateManyWithoutUserNestedInput passkeys?: PasskeyUpdateManyWithoutUserNestedInput } @@ -23480,6 +26361,8 @@ export namespace Prisma { modelLikes?: ModelLikeUncheckedUpdateManyWithoutUserNestedInput modelInteractions?: ModelInteractionUncheckedUpdateManyWithoutUserNestedInput modelDrafts?: ModelDraftUncheckedUpdateManyWithoutUserNestedInput + comments?: ModelCommentUncheckedUpdateManyWithoutUserNestedInput + commentLikes?: ModelCommentLikeUncheckedUpdateManyWithoutUserNestedInput passkeys?: PasskeyUncheckedUpdateManyWithoutUserNestedInput } @@ -23941,6 +26824,7 @@ export namespace Prisma { likes?: ModelLikeCreateNestedManyWithoutModelInput interactions?: ModelInteractionCreateNestedManyWithoutModelInput drafts?: ModelDraftCreateNestedManyWithoutModelInput + comments?: ModelCommentCreateNestedManyWithoutModelInput } export type ModelUncheckedCreateInput = { @@ -23967,6 +26851,7 @@ export namespace Prisma { likes?: ModelLikeUncheckedCreateNestedManyWithoutModelInput interactions?: ModelInteractionUncheckedCreateNestedManyWithoutModelInput drafts?: ModelDraftUncheckedCreateNestedManyWithoutModelInput + comments?: ModelCommentUncheckedCreateNestedManyWithoutModelInput } export type ModelUpdateInput = { @@ -23992,6 +26877,7 @@ export namespace Prisma { likes?: ModelLikeUpdateManyWithoutModelNestedInput interactions?: ModelInteractionUpdateManyWithoutModelNestedInput drafts?: ModelDraftUpdateManyWithoutModelNestedInput + comments?: ModelCommentUpdateManyWithoutModelNestedInput } export type ModelUncheckedUpdateInput = { @@ -24018,6 +26904,7 @@ export namespace Prisma { likes?: ModelLikeUncheckedUpdateManyWithoutModelNestedInput interactions?: ModelInteractionUncheckedUpdateManyWithoutModelNestedInput drafts?: ModelDraftUncheckedUpdateManyWithoutModelNestedInput + comments?: ModelCommentUncheckedUpdateManyWithoutModelNestedInput } export type ModelCreateManyInput = { @@ -24660,6 +27547,156 @@ export namespace Prisma { updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string } + export type ModelCommentCreateInput = { + id?: string + legacyId?: number | null + versionNumber?: number | null + content?: string | null + likesCount?: number + createdAt?: Date | string + updatedAt?: Date | string + editedAt?: Date | string | null + deletedAt?: Date | string | null + model: ModelCreateNestedOneWithoutCommentsInput + user?: UserCreateNestedOneWithoutCommentsInput + parent?: ModelCommentCreateNestedOneWithoutRepliesInput + replies?: ModelCommentCreateNestedManyWithoutParentInput + likes?: ModelCommentLikeCreateNestedManyWithoutModelCommentInput + } + + export type ModelCommentUncheckedCreateInput = { + id?: string + legacyId?: number | null + parentId?: string | null + userId?: string | null + modelId: string + versionNumber?: number | null + content?: string | null + likesCount?: number + createdAt?: Date | string + updatedAt?: Date | string + editedAt?: Date | string | null + deletedAt?: Date | string | null + replies?: ModelCommentUncheckedCreateNestedManyWithoutParentInput + likes?: ModelCommentLikeUncheckedCreateNestedManyWithoutModelCommentInput + } + + export type ModelCommentUpdateInput = { + id?: StringFieldUpdateOperationsInput | string + legacyId?: NullableIntFieldUpdateOperationsInput | number | null + versionNumber?: NullableIntFieldUpdateOperationsInput | number | null + content?: NullableStringFieldUpdateOperationsInput | string | null + likesCount?: IntFieldUpdateOperationsInput | number + createdAt?: DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string + editedAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + deletedAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + model?: ModelUpdateOneRequiredWithoutCommentsNestedInput + user?: UserUpdateOneWithoutCommentsNestedInput + parent?: ModelCommentUpdateOneWithoutRepliesNestedInput + replies?: ModelCommentUpdateManyWithoutParentNestedInput + likes?: ModelCommentLikeUpdateManyWithoutModelCommentNestedInput + } + + export type ModelCommentUncheckedUpdateInput = { + id?: StringFieldUpdateOperationsInput | string + legacyId?: NullableIntFieldUpdateOperationsInput | number | null + parentId?: NullableStringFieldUpdateOperationsInput | string | null + userId?: NullableStringFieldUpdateOperationsInput | string | null + modelId?: StringFieldUpdateOperationsInput | string + versionNumber?: NullableIntFieldUpdateOperationsInput | number | null + content?: NullableStringFieldUpdateOperationsInput | string | null + likesCount?: IntFieldUpdateOperationsInput | number + createdAt?: DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string + editedAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + deletedAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + replies?: ModelCommentUncheckedUpdateManyWithoutParentNestedInput + likes?: ModelCommentLikeUncheckedUpdateManyWithoutModelCommentNestedInput + } + + export type ModelCommentCreateManyInput = { + id?: string + legacyId?: number | null + parentId?: string | null + userId?: string | null + modelId: string + versionNumber?: number | null + content?: string | null + likesCount?: number + createdAt?: Date | string + updatedAt?: Date | string + editedAt?: Date | string | null + deletedAt?: Date | string | null + } + + export type ModelCommentUpdateManyMutationInput = { + id?: StringFieldUpdateOperationsInput | string + legacyId?: NullableIntFieldUpdateOperationsInput | number | null + versionNumber?: NullableIntFieldUpdateOperationsInput | number | null + content?: NullableStringFieldUpdateOperationsInput | string | null + likesCount?: IntFieldUpdateOperationsInput | number + createdAt?: DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string + editedAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + deletedAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + } + + export type ModelCommentUncheckedUpdateManyInput = { + id?: StringFieldUpdateOperationsInput | string + legacyId?: NullableIntFieldUpdateOperationsInput | number | null + parentId?: NullableStringFieldUpdateOperationsInput | string | null + userId?: NullableStringFieldUpdateOperationsInput | string | null + modelId?: StringFieldUpdateOperationsInput | string + versionNumber?: NullableIntFieldUpdateOperationsInput | number | null + content?: NullableStringFieldUpdateOperationsInput | string | null + likesCount?: IntFieldUpdateOperationsInput | number + createdAt?: DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string + editedAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + deletedAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + } + + export type ModelCommentLikeCreateInput = { + createdAt?: Date | string + modelComment: ModelCommentCreateNestedOneWithoutLikesInput + user: UserCreateNestedOneWithoutCommentLikesInput + } + + export type ModelCommentLikeUncheckedCreateInput = { + modelCommentId: string + userId: string + createdAt?: Date | string + } + + export type ModelCommentLikeUpdateInput = { + createdAt?: DateTimeFieldUpdateOperationsInput | Date | string + modelComment?: ModelCommentUpdateOneRequiredWithoutLikesNestedInput + user?: UserUpdateOneRequiredWithoutCommentLikesNestedInput + } + + export type ModelCommentLikeUncheckedUpdateInput = { + modelCommentId?: StringFieldUpdateOperationsInput | string + userId?: StringFieldUpdateOperationsInput | string + createdAt?: DateTimeFieldUpdateOperationsInput | Date | string + } + + export type ModelCommentLikeCreateManyInput = { + modelCommentId: string + userId: string + createdAt?: Date | string + } + + export type ModelCommentLikeUpdateManyMutationInput = { + createdAt?: DateTimeFieldUpdateOperationsInput | Date | string + } + + export type ModelCommentLikeUncheckedUpdateManyInput = { + modelCommentId?: StringFieldUpdateOperationsInput | string + userId?: StringFieldUpdateOperationsInput | string + createdAt?: DateTimeFieldUpdateOperationsInput | Date | string + } + export type EventCreateInput = { id?: string type: string @@ -24900,6 +27937,18 @@ export namespace Prisma { none?: ModelDraftWhereInput } + export type ModelCommentListRelationFilter = { + every?: ModelCommentWhereInput + some?: ModelCommentWhereInput + none?: ModelCommentWhereInput + } + + export type ModelCommentLikeListRelationFilter = { + every?: ModelCommentLikeWhereInput + some?: ModelCommentLikeWhereInput + none?: ModelCommentLikeWhereInput + } + export type PasskeyListRelationFilter = { every?: PasskeyWhereInput some?: PasskeyWhereInput @@ -24947,6 +27996,14 @@ export namespace Prisma { _count?: SortOrder } + export type ModelCommentOrderByRelationAggregateInput = { + _count?: SortOrder + } + + export type ModelCommentLikeOrderByRelationAggregateInput = { + _count?: SortOrder + } + export type PasskeyOrderByRelationAggregateInput = { _count?: SortOrder } @@ -25975,6 +29032,96 @@ export namespace Prisma { _max?: NestedJsonFilter<$PrismaModel> } + export type ModelCommentNullableScalarRelationFilter = { + is?: ModelCommentWhereInput | null + isNot?: ModelCommentWhereInput | null + } + + export type ModelCommentCountOrderByAggregateInput = { + id?: SortOrder + legacyId?: SortOrder + parentId?: SortOrder + userId?: SortOrder + modelId?: SortOrder + versionNumber?: SortOrder + content?: SortOrder + likesCount?: SortOrder + createdAt?: SortOrder + updatedAt?: SortOrder + editedAt?: SortOrder + deletedAt?: SortOrder + } + + export type ModelCommentAvgOrderByAggregateInput = { + legacyId?: SortOrder + versionNumber?: SortOrder + likesCount?: SortOrder + } + + export type ModelCommentMaxOrderByAggregateInput = { + id?: SortOrder + legacyId?: SortOrder + parentId?: SortOrder + userId?: SortOrder + modelId?: SortOrder + versionNumber?: SortOrder + content?: SortOrder + likesCount?: SortOrder + createdAt?: SortOrder + updatedAt?: SortOrder + editedAt?: SortOrder + deletedAt?: SortOrder + } + + export type ModelCommentMinOrderByAggregateInput = { + id?: SortOrder + legacyId?: SortOrder + parentId?: SortOrder + userId?: SortOrder + modelId?: SortOrder + versionNumber?: SortOrder + content?: SortOrder + likesCount?: SortOrder + createdAt?: SortOrder + updatedAt?: SortOrder + editedAt?: SortOrder + deletedAt?: SortOrder + } + + export type ModelCommentSumOrderByAggregateInput = { + legacyId?: SortOrder + versionNumber?: SortOrder + likesCount?: SortOrder + } + + export type ModelCommentScalarRelationFilter = { + is?: ModelCommentWhereInput + isNot?: ModelCommentWhereInput + } + + export type ModelCommentLikeModelCommentIdUserIdCompoundUniqueInput = { + modelCommentId: string + userId: string + } + + export type ModelCommentLikeCountOrderByAggregateInput = { + modelCommentId?: SortOrder + userId?: SortOrder + createdAt?: SortOrder + } + + export type ModelCommentLikeMaxOrderByAggregateInput = { + modelCommentId?: SortOrder + userId?: SortOrder + createdAt?: SortOrder + } + + export type ModelCommentLikeMinOrderByAggregateInput = { + modelCommentId?: SortOrder + userId?: SortOrder + createdAt?: SortOrder + } + export type EventCountOrderByAggregateInput = { id?: SortOrder type?: SortOrder @@ -26069,6 +29216,20 @@ export namespace Prisma { connect?: ModelDraftWhereUniqueInput | ModelDraftWhereUniqueInput[] } + export type ModelCommentCreateNestedManyWithoutUserInput = { + create?: XOR | ModelCommentCreateWithoutUserInput[] | ModelCommentUncheckedCreateWithoutUserInput[] + connectOrCreate?: ModelCommentCreateOrConnectWithoutUserInput | ModelCommentCreateOrConnectWithoutUserInput[] + createMany?: ModelCommentCreateManyUserInputEnvelope + connect?: ModelCommentWhereUniqueInput | ModelCommentWhereUniqueInput[] + } + + export type ModelCommentLikeCreateNestedManyWithoutUserInput = { + create?: XOR | ModelCommentLikeCreateWithoutUserInput[] | ModelCommentLikeUncheckedCreateWithoutUserInput[] + connectOrCreate?: ModelCommentLikeCreateOrConnectWithoutUserInput | ModelCommentLikeCreateOrConnectWithoutUserInput[] + createMany?: ModelCommentLikeCreateManyUserInputEnvelope + connect?: ModelCommentLikeWhereUniqueInput | ModelCommentLikeWhereUniqueInput[] + } + export type PasskeyCreateNestedManyWithoutUserInput = { create?: XOR | PasskeyCreateWithoutUserInput[] | PasskeyUncheckedCreateWithoutUserInput[] connectOrCreate?: PasskeyCreateOrConnectWithoutUserInput | PasskeyCreateOrConnectWithoutUserInput[] @@ -26139,6 +29300,20 @@ export namespace Prisma { connect?: ModelDraftWhereUniqueInput | ModelDraftWhereUniqueInput[] } + export type ModelCommentUncheckedCreateNestedManyWithoutUserInput = { + create?: XOR | ModelCommentCreateWithoutUserInput[] | ModelCommentUncheckedCreateWithoutUserInput[] + connectOrCreate?: ModelCommentCreateOrConnectWithoutUserInput | ModelCommentCreateOrConnectWithoutUserInput[] + createMany?: ModelCommentCreateManyUserInputEnvelope + connect?: ModelCommentWhereUniqueInput | ModelCommentWhereUniqueInput[] + } + + export type ModelCommentLikeUncheckedCreateNestedManyWithoutUserInput = { + create?: XOR | ModelCommentLikeCreateWithoutUserInput[] | ModelCommentLikeUncheckedCreateWithoutUserInput[] + connectOrCreate?: ModelCommentLikeCreateOrConnectWithoutUserInput | ModelCommentLikeCreateOrConnectWithoutUserInput[] + createMany?: ModelCommentLikeCreateManyUserInputEnvelope + connect?: ModelCommentLikeWhereUniqueInput | ModelCommentLikeWhereUniqueInput[] + } + export type PasskeyUncheckedCreateNestedManyWithoutUserInput = { create?: XOR | PasskeyCreateWithoutUserInput[] | PasskeyUncheckedCreateWithoutUserInput[] connectOrCreate?: PasskeyCreateOrConnectWithoutUserInput | PasskeyCreateOrConnectWithoutUserInput[] @@ -26312,6 +29487,34 @@ export namespace Prisma { deleteMany?: ModelDraftScalarWhereInput | ModelDraftScalarWhereInput[] } + export type ModelCommentUpdateManyWithoutUserNestedInput = { + create?: XOR | ModelCommentCreateWithoutUserInput[] | ModelCommentUncheckedCreateWithoutUserInput[] + connectOrCreate?: ModelCommentCreateOrConnectWithoutUserInput | ModelCommentCreateOrConnectWithoutUserInput[] + upsert?: ModelCommentUpsertWithWhereUniqueWithoutUserInput | ModelCommentUpsertWithWhereUniqueWithoutUserInput[] + createMany?: ModelCommentCreateManyUserInputEnvelope + set?: ModelCommentWhereUniqueInput | ModelCommentWhereUniqueInput[] + disconnect?: ModelCommentWhereUniqueInput | ModelCommentWhereUniqueInput[] + delete?: ModelCommentWhereUniqueInput | ModelCommentWhereUniqueInput[] + connect?: ModelCommentWhereUniqueInput | ModelCommentWhereUniqueInput[] + update?: ModelCommentUpdateWithWhereUniqueWithoutUserInput | ModelCommentUpdateWithWhereUniqueWithoutUserInput[] + updateMany?: ModelCommentUpdateManyWithWhereWithoutUserInput | ModelCommentUpdateManyWithWhereWithoutUserInput[] + deleteMany?: ModelCommentScalarWhereInput | ModelCommentScalarWhereInput[] + } + + export type ModelCommentLikeUpdateManyWithoutUserNestedInput = { + create?: XOR | ModelCommentLikeCreateWithoutUserInput[] | ModelCommentLikeUncheckedCreateWithoutUserInput[] + connectOrCreate?: ModelCommentLikeCreateOrConnectWithoutUserInput | ModelCommentLikeCreateOrConnectWithoutUserInput[] + upsert?: ModelCommentLikeUpsertWithWhereUniqueWithoutUserInput | ModelCommentLikeUpsertWithWhereUniqueWithoutUserInput[] + createMany?: ModelCommentLikeCreateManyUserInputEnvelope + set?: ModelCommentLikeWhereUniqueInput | ModelCommentLikeWhereUniqueInput[] + disconnect?: ModelCommentLikeWhereUniqueInput | ModelCommentLikeWhereUniqueInput[] + delete?: ModelCommentLikeWhereUniqueInput | ModelCommentLikeWhereUniqueInput[] + connect?: ModelCommentLikeWhereUniqueInput | ModelCommentLikeWhereUniqueInput[] + update?: ModelCommentLikeUpdateWithWhereUniqueWithoutUserInput | ModelCommentLikeUpdateWithWhereUniqueWithoutUserInput[] + updateMany?: ModelCommentLikeUpdateManyWithWhereWithoutUserInput | ModelCommentLikeUpdateManyWithWhereWithoutUserInput[] + deleteMany?: ModelCommentLikeScalarWhereInput | ModelCommentLikeScalarWhereInput[] + } + export type PasskeyUpdateManyWithoutUserNestedInput = { create?: XOR | PasskeyCreateWithoutUserInput[] | PasskeyUncheckedCreateWithoutUserInput[] connectOrCreate?: PasskeyCreateOrConnectWithoutUserInput | PasskeyCreateOrConnectWithoutUserInput[] @@ -26452,6 +29655,34 @@ export namespace Prisma { deleteMany?: ModelDraftScalarWhereInput | ModelDraftScalarWhereInput[] } + export type ModelCommentUncheckedUpdateManyWithoutUserNestedInput = { + create?: XOR | ModelCommentCreateWithoutUserInput[] | ModelCommentUncheckedCreateWithoutUserInput[] + connectOrCreate?: ModelCommentCreateOrConnectWithoutUserInput | ModelCommentCreateOrConnectWithoutUserInput[] + upsert?: ModelCommentUpsertWithWhereUniqueWithoutUserInput | ModelCommentUpsertWithWhereUniqueWithoutUserInput[] + createMany?: ModelCommentCreateManyUserInputEnvelope + set?: ModelCommentWhereUniqueInput | ModelCommentWhereUniqueInput[] + disconnect?: ModelCommentWhereUniqueInput | ModelCommentWhereUniqueInput[] + delete?: ModelCommentWhereUniqueInput | ModelCommentWhereUniqueInput[] + connect?: ModelCommentWhereUniqueInput | ModelCommentWhereUniqueInput[] + update?: ModelCommentUpdateWithWhereUniqueWithoutUserInput | ModelCommentUpdateWithWhereUniqueWithoutUserInput[] + updateMany?: ModelCommentUpdateManyWithWhereWithoutUserInput | ModelCommentUpdateManyWithWhereWithoutUserInput[] + deleteMany?: ModelCommentScalarWhereInput | ModelCommentScalarWhereInput[] + } + + export type ModelCommentLikeUncheckedUpdateManyWithoutUserNestedInput = { + create?: XOR | ModelCommentLikeCreateWithoutUserInput[] | ModelCommentLikeUncheckedCreateWithoutUserInput[] + connectOrCreate?: ModelCommentLikeCreateOrConnectWithoutUserInput | ModelCommentLikeCreateOrConnectWithoutUserInput[] + upsert?: ModelCommentLikeUpsertWithWhereUniqueWithoutUserInput | ModelCommentLikeUpsertWithWhereUniqueWithoutUserInput[] + createMany?: ModelCommentLikeCreateManyUserInputEnvelope + set?: ModelCommentLikeWhereUniqueInput | ModelCommentLikeWhereUniqueInput[] + disconnect?: ModelCommentLikeWhereUniqueInput | ModelCommentLikeWhereUniqueInput[] + delete?: ModelCommentLikeWhereUniqueInput | ModelCommentLikeWhereUniqueInput[] + connect?: ModelCommentLikeWhereUniqueInput | ModelCommentLikeWhereUniqueInput[] + update?: ModelCommentLikeUpdateWithWhereUniqueWithoutUserInput | ModelCommentLikeUpdateWithWhereUniqueWithoutUserInput[] + updateMany?: ModelCommentLikeUpdateManyWithWhereWithoutUserInput | ModelCommentLikeUpdateManyWithWhereWithoutUserInput[] + deleteMany?: ModelCommentLikeScalarWhereInput | ModelCommentLikeScalarWhereInput[] + } + export type PasskeyUncheckedUpdateManyWithoutUserNestedInput = { create?: XOR | PasskeyCreateWithoutUserInput[] | PasskeyUncheckedCreateWithoutUserInput[] connectOrCreate?: PasskeyCreateOrConnectWithoutUserInput | PasskeyCreateOrConnectWithoutUserInput[] @@ -26606,6 +29837,13 @@ export namespace Prisma { connect?: ModelDraftWhereUniqueInput | ModelDraftWhereUniqueInput[] } + export type ModelCommentCreateNestedManyWithoutModelInput = { + create?: XOR | ModelCommentCreateWithoutModelInput[] | ModelCommentUncheckedCreateWithoutModelInput[] + connectOrCreate?: ModelCommentCreateOrConnectWithoutModelInput | ModelCommentCreateOrConnectWithoutModelInput[] + createMany?: ModelCommentCreateManyModelInputEnvelope + connect?: ModelCommentWhereUniqueInput | ModelCommentWhereUniqueInput[] + } + export type ModelUncheckedCreateNestedManyWithoutParentModelInput = { create?: XOR | ModelCreateWithoutParentModelInput[] | ModelUncheckedCreateWithoutParentModelInput[] connectOrCreate?: ModelCreateOrConnectWithoutParentModelInput | ModelCreateOrConnectWithoutParentModelInput[] @@ -26662,6 +29900,13 @@ export namespace Prisma { connect?: ModelDraftWhereUniqueInput | ModelDraftWhereUniqueInput[] } + export type ModelCommentUncheckedCreateNestedManyWithoutModelInput = { + create?: XOR | ModelCommentCreateWithoutModelInput[] | ModelCommentUncheckedCreateWithoutModelInput[] + connectOrCreate?: ModelCommentCreateOrConnectWithoutModelInput | ModelCommentCreateOrConnectWithoutModelInput[] + createMany?: ModelCommentCreateManyModelInputEnvelope + connect?: ModelCommentWhereUniqueInput | ModelCommentWhereUniqueInput[] + } + export type EnumModelVisibilityFieldUpdateOperationsInput = { set?: $Enums.ModelVisibility } @@ -26808,6 +30053,20 @@ export namespace Prisma { deleteMany?: ModelDraftScalarWhereInput | ModelDraftScalarWhereInput[] } + export type ModelCommentUpdateManyWithoutModelNestedInput = { + create?: XOR | ModelCommentCreateWithoutModelInput[] | ModelCommentUncheckedCreateWithoutModelInput[] + connectOrCreate?: ModelCommentCreateOrConnectWithoutModelInput | ModelCommentCreateOrConnectWithoutModelInput[] + upsert?: ModelCommentUpsertWithWhereUniqueWithoutModelInput | ModelCommentUpsertWithWhereUniqueWithoutModelInput[] + createMany?: ModelCommentCreateManyModelInputEnvelope + set?: ModelCommentWhereUniqueInput | ModelCommentWhereUniqueInput[] + disconnect?: ModelCommentWhereUniqueInput | ModelCommentWhereUniqueInput[] + delete?: ModelCommentWhereUniqueInput | ModelCommentWhereUniqueInput[] + connect?: ModelCommentWhereUniqueInput | ModelCommentWhereUniqueInput[] + update?: ModelCommentUpdateWithWhereUniqueWithoutModelInput | ModelCommentUpdateWithWhereUniqueWithoutModelInput[] + updateMany?: ModelCommentUpdateManyWithWhereWithoutModelInput | ModelCommentUpdateManyWithWhereWithoutModelInput[] + deleteMany?: ModelCommentScalarWhereInput | ModelCommentScalarWhereInput[] + } + export type ModelUncheckedUpdateManyWithoutParentModelNestedInput = { create?: XOR | ModelCreateWithoutParentModelInput[] | ModelUncheckedCreateWithoutParentModelInput[] connectOrCreate?: ModelCreateOrConnectWithoutParentModelInput | ModelCreateOrConnectWithoutParentModelInput[] @@ -26920,6 +30179,20 @@ export namespace Prisma { deleteMany?: ModelDraftScalarWhereInput | ModelDraftScalarWhereInput[] } + export type ModelCommentUncheckedUpdateManyWithoutModelNestedInput = { + create?: XOR | ModelCommentCreateWithoutModelInput[] | ModelCommentUncheckedCreateWithoutModelInput[] + connectOrCreate?: ModelCommentCreateOrConnectWithoutModelInput | ModelCommentCreateOrConnectWithoutModelInput[] + upsert?: ModelCommentUpsertWithWhereUniqueWithoutModelInput | ModelCommentUpsertWithWhereUniqueWithoutModelInput[] + createMany?: ModelCommentCreateManyModelInputEnvelope + set?: ModelCommentWhereUniqueInput | ModelCommentWhereUniqueInput[] + disconnect?: ModelCommentWhereUniqueInput | ModelCommentWhereUniqueInput[] + delete?: ModelCommentWhereUniqueInput | ModelCommentWhereUniqueInput[] + connect?: ModelCommentWhereUniqueInput | ModelCommentWhereUniqueInput[] + update?: ModelCommentUpdateWithWhereUniqueWithoutModelInput | ModelCommentUpdateWithWhereUniqueWithoutModelInput[] + updateMany?: ModelCommentUpdateManyWithWhereWithoutModelInput | ModelCommentUpdateManyWithWhereWithoutModelInput[] + deleteMany?: ModelCommentScalarWhereInput | ModelCommentScalarWhereInput[] + } + export type ModelCreateNestedOneWithoutVersionsInput = { create?: XOR connectOrCreate?: ModelCreateOrConnectWithoutVersionsInput @@ -27352,6 +30625,164 @@ export namespace Prisma { update?: XOR, ModelUncheckedUpdateWithoutDraftsInput> } + export type ModelCreateNestedOneWithoutCommentsInput = { + create?: XOR + connectOrCreate?: ModelCreateOrConnectWithoutCommentsInput + connect?: ModelWhereUniqueInput + } + + export type UserCreateNestedOneWithoutCommentsInput = { + create?: XOR + connectOrCreate?: UserCreateOrConnectWithoutCommentsInput + connect?: UserWhereUniqueInput + } + + export type ModelCommentCreateNestedOneWithoutRepliesInput = { + create?: XOR + connectOrCreate?: ModelCommentCreateOrConnectWithoutRepliesInput + connect?: ModelCommentWhereUniqueInput + } + + export type ModelCommentCreateNestedManyWithoutParentInput = { + create?: XOR | ModelCommentCreateWithoutParentInput[] | ModelCommentUncheckedCreateWithoutParentInput[] + connectOrCreate?: ModelCommentCreateOrConnectWithoutParentInput | ModelCommentCreateOrConnectWithoutParentInput[] + createMany?: ModelCommentCreateManyParentInputEnvelope + connect?: ModelCommentWhereUniqueInput | ModelCommentWhereUniqueInput[] + } + + export type ModelCommentLikeCreateNestedManyWithoutModelCommentInput = { + create?: XOR | ModelCommentLikeCreateWithoutModelCommentInput[] | ModelCommentLikeUncheckedCreateWithoutModelCommentInput[] + connectOrCreate?: ModelCommentLikeCreateOrConnectWithoutModelCommentInput | ModelCommentLikeCreateOrConnectWithoutModelCommentInput[] + createMany?: ModelCommentLikeCreateManyModelCommentInputEnvelope + connect?: ModelCommentLikeWhereUniqueInput | ModelCommentLikeWhereUniqueInput[] + } + + export type ModelCommentUncheckedCreateNestedManyWithoutParentInput = { + create?: XOR | ModelCommentCreateWithoutParentInput[] | ModelCommentUncheckedCreateWithoutParentInput[] + connectOrCreate?: ModelCommentCreateOrConnectWithoutParentInput | ModelCommentCreateOrConnectWithoutParentInput[] + createMany?: ModelCommentCreateManyParentInputEnvelope + connect?: ModelCommentWhereUniqueInput | ModelCommentWhereUniqueInput[] + } + + export type ModelCommentLikeUncheckedCreateNestedManyWithoutModelCommentInput = { + create?: XOR | ModelCommentLikeCreateWithoutModelCommentInput[] | ModelCommentLikeUncheckedCreateWithoutModelCommentInput[] + connectOrCreate?: ModelCommentLikeCreateOrConnectWithoutModelCommentInput | ModelCommentLikeCreateOrConnectWithoutModelCommentInput[] + createMany?: ModelCommentLikeCreateManyModelCommentInputEnvelope + connect?: ModelCommentLikeWhereUniqueInput | ModelCommentLikeWhereUniqueInput[] + } + + export type ModelUpdateOneRequiredWithoutCommentsNestedInput = { + create?: XOR + connectOrCreate?: ModelCreateOrConnectWithoutCommentsInput + upsert?: ModelUpsertWithoutCommentsInput + connect?: ModelWhereUniqueInput + update?: XOR, ModelUncheckedUpdateWithoutCommentsInput> + } + + export type UserUpdateOneWithoutCommentsNestedInput = { + create?: XOR + connectOrCreate?: UserCreateOrConnectWithoutCommentsInput + upsert?: UserUpsertWithoutCommentsInput + disconnect?: UserWhereInput | boolean + delete?: UserWhereInput | boolean + connect?: UserWhereUniqueInput + update?: XOR, UserUncheckedUpdateWithoutCommentsInput> + } + + export type ModelCommentUpdateOneWithoutRepliesNestedInput = { + create?: XOR + connectOrCreate?: ModelCommentCreateOrConnectWithoutRepliesInput + upsert?: ModelCommentUpsertWithoutRepliesInput + disconnect?: ModelCommentWhereInput | boolean + delete?: ModelCommentWhereInput | boolean + connect?: ModelCommentWhereUniqueInput + update?: XOR, ModelCommentUncheckedUpdateWithoutRepliesInput> + } + + export type ModelCommentUpdateManyWithoutParentNestedInput = { + create?: XOR | ModelCommentCreateWithoutParentInput[] | ModelCommentUncheckedCreateWithoutParentInput[] + connectOrCreate?: ModelCommentCreateOrConnectWithoutParentInput | ModelCommentCreateOrConnectWithoutParentInput[] + upsert?: ModelCommentUpsertWithWhereUniqueWithoutParentInput | ModelCommentUpsertWithWhereUniqueWithoutParentInput[] + createMany?: ModelCommentCreateManyParentInputEnvelope + set?: ModelCommentWhereUniqueInput | ModelCommentWhereUniqueInput[] + disconnect?: ModelCommentWhereUniqueInput | ModelCommentWhereUniqueInput[] + delete?: ModelCommentWhereUniqueInput | ModelCommentWhereUniqueInput[] + connect?: ModelCommentWhereUniqueInput | ModelCommentWhereUniqueInput[] + update?: ModelCommentUpdateWithWhereUniqueWithoutParentInput | ModelCommentUpdateWithWhereUniqueWithoutParentInput[] + updateMany?: ModelCommentUpdateManyWithWhereWithoutParentInput | ModelCommentUpdateManyWithWhereWithoutParentInput[] + deleteMany?: ModelCommentScalarWhereInput | ModelCommentScalarWhereInput[] + } + + export type ModelCommentLikeUpdateManyWithoutModelCommentNestedInput = { + create?: XOR | ModelCommentLikeCreateWithoutModelCommentInput[] | ModelCommentLikeUncheckedCreateWithoutModelCommentInput[] + connectOrCreate?: ModelCommentLikeCreateOrConnectWithoutModelCommentInput | ModelCommentLikeCreateOrConnectWithoutModelCommentInput[] + upsert?: ModelCommentLikeUpsertWithWhereUniqueWithoutModelCommentInput | ModelCommentLikeUpsertWithWhereUniqueWithoutModelCommentInput[] + createMany?: ModelCommentLikeCreateManyModelCommentInputEnvelope + set?: ModelCommentLikeWhereUniqueInput | ModelCommentLikeWhereUniqueInput[] + disconnect?: ModelCommentLikeWhereUniqueInput | ModelCommentLikeWhereUniqueInput[] + delete?: ModelCommentLikeWhereUniqueInput | ModelCommentLikeWhereUniqueInput[] + connect?: ModelCommentLikeWhereUniqueInput | ModelCommentLikeWhereUniqueInput[] + update?: ModelCommentLikeUpdateWithWhereUniqueWithoutModelCommentInput | ModelCommentLikeUpdateWithWhereUniqueWithoutModelCommentInput[] + updateMany?: ModelCommentLikeUpdateManyWithWhereWithoutModelCommentInput | ModelCommentLikeUpdateManyWithWhereWithoutModelCommentInput[] + deleteMany?: ModelCommentLikeScalarWhereInput | ModelCommentLikeScalarWhereInput[] + } + + export type ModelCommentUncheckedUpdateManyWithoutParentNestedInput = { + create?: XOR | ModelCommentCreateWithoutParentInput[] | ModelCommentUncheckedCreateWithoutParentInput[] + connectOrCreate?: ModelCommentCreateOrConnectWithoutParentInput | ModelCommentCreateOrConnectWithoutParentInput[] + upsert?: ModelCommentUpsertWithWhereUniqueWithoutParentInput | ModelCommentUpsertWithWhereUniqueWithoutParentInput[] + createMany?: ModelCommentCreateManyParentInputEnvelope + set?: ModelCommentWhereUniqueInput | ModelCommentWhereUniqueInput[] + disconnect?: ModelCommentWhereUniqueInput | ModelCommentWhereUniqueInput[] + delete?: ModelCommentWhereUniqueInput | ModelCommentWhereUniqueInput[] + connect?: ModelCommentWhereUniqueInput | ModelCommentWhereUniqueInput[] + update?: ModelCommentUpdateWithWhereUniqueWithoutParentInput | ModelCommentUpdateWithWhereUniqueWithoutParentInput[] + updateMany?: ModelCommentUpdateManyWithWhereWithoutParentInput | ModelCommentUpdateManyWithWhereWithoutParentInput[] + deleteMany?: ModelCommentScalarWhereInput | ModelCommentScalarWhereInput[] + } + + export type ModelCommentLikeUncheckedUpdateManyWithoutModelCommentNestedInput = { + create?: XOR | ModelCommentLikeCreateWithoutModelCommentInput[] | ModelCommentLikeUncheckedCreateWithoutModelCommentInput[] + connectOrCreate?: ModelCommentLikeCreateOrConnectWithoutModelCommentInput | ModelCommentLikeCreateOrConnectWithoutModelCommentInput[] + upsert?: ModelCommentLikeUpsertWithWhereUniqueWithoutModelCommentInput | ModelCommentLikeUpsertWithWhereUniqueWithoutModelCommentInput[] + createMany?: ModelCommentLikeCreateManyModelCommentInputEnvelope + set?: ModelCommentLikeWhereUniqueInput | ModelCommentLikeWhereUniqueInput[] + disconnect?: ModelCommentLikeWhereUniqueInput | ModelCommentLikeWhereUniqueInput[] + delete?: ModelCommentLikeWhereUniqueInput | ModelCommentLikeWhereUniqueInput[] + connect?: ModelCommentLikeWhereUniqueInput | ModelCommentLikeWhereUniqueInput[] + update?: ModelCommentLikeUpdateWithWhereUniqueWithoutModelCommentInput | ModelCommentLikeUpdateWithWhereUniqueWithoutModelCommentInput[] + updateMany?: ModelCommentLikeUpdateManyWithWhereWithoutModelCommentInput | ModelCommentLikeUpdateManyWithWhereWithoutModelCommentInput[] + deleteMany?: ModelCommentLikeScalarWhereInput | ModelCommentLikeScalarWhereInput[] + } + + export type ModelCommentCreateNestedOneWithoutLikesInput = { + create?: XOR + connectOrCreate?: ModelCommentCreateOrConnectWithoutLikesInput + connect?: ModelCommentWhereUniqueInput + } + + export type UserCreateNestedOneWithoutCommentLikesInput = { + create?: XOR + connectOrCreate?: UserCreateOrConnectWithoutCommentLikesInput + connect?: UserWhereUniqueInput + } + + export type ModelCommentUpdateOneRequiredWithoutLikesNestedInput = { + create?: XOR + connectOrCreate?: ModelCommentCreateOrConnectWithoutLikesInput + upsert?: ModelCommentUpsertWithoutLikesInput + connect?: ModelCommentWhereUniqueInput + update?: XOR, ModelCommentUncheckedUpdateWithoutLikesInput> + } + + export type UserUpdateOneRequiredWithoutCommentLikesNestedInput = { + create?: XOR + connectOrCreate?: UserCreateOrConnectWithoutCommentLikesInput + upsert?: UserUpsertWithoutCommentLikesInput + connect?: UserWhereUniqueInput + update?: XOR, UserUncheckedUpdateWithoutCommentLikesInput> + } + export type UserCreateNestedOneWithoutEventsInput = { create?: XOR connectOrCreate?: UserCreateOrConnectWithoutEventsInput @@ -28007,6 +31438,68 @@ export namespace Prisma { skipDuplicates?: boolean } + export type ModelCommentCreateWithoutUserInput = { + id?: string + legacyId?: number | null + versionNumber?: number | null + content?: string | null + likesCount?: number + createdAt?: Date | string + updatedAt?: Date | string + editedAt?: Date | string | null + deletedAt?: Date | string | null + model: ModelCreateNestedOneWithoutCommentsInput + parent?: ModelCommentCreateNestedOneWithoutRepliesInput + replies?: ModelCommentCreateNestedManyWithoutParentInput + likes?: ModelCommentLikeCreateNestedManyWithoutModelCommentInput + } + + export type ModelCommentUncheckedCreateWithoutUserInput = { + id?: string + legacyId?: number | null + parentId?: string | null + modelId: string + versionNumber?: number | null + content?: string | null + likesCount?: number + createdAt?: Date | string + updatedAt?: Date | string + editedAt?: Date | string | null + deletedAt?: Date | string | null + replies?: ModelCommentUncheckedCreateNestedManyWithoutParentInput + likes?: ModelCommentLikeUncheckedCreateNestedManyWithoutModelCommentInput + } + + export type ModelCommentCreateOrConnectWithoutUserInput = { + where: ModelCommentWhereUniqueInput + create: XOR + } + + export type ModelCommentCreateManyUserInputEnvelope = { + data: ModelCommentCreateManyUserInput | ModelCommentCreateManyUserInput[] + skipDuplicates?: boolean + } + + export type ModelCommentLikeCreateWithoutUserInput = { + createdAt?: Date | string + modelComment: ModelCommentCreateNestedOneWithoutLikesInput + } + + export type ModelCommentLikeUncheckedCreateWithoutUserInput = { + modelCommentId: string + createdAt?: Date | string + } + + export type ModelCommentLikeCreateOrConnectWithoutUserInput = { + where: ModelCommentLikeWhereUniqueInput + create: XOR + } + + export type ModelCommentLikeCreateManyUserInputEnvelope = { + data: ModelCommentLikeCreateManyUserInput | ModelCommentLikeCreateManyUserInput[] + skipDuplicates?: boolean + } + export type PasskeyCreateWithoutUserInput = { id?: string name?: string | null @@ -28309,6 +31802,65 @@ export namespace Prisma { updatedAt?: DateTimeFilter<"ModelDraft"> | Date | string } + export type ModelCommentUpsertWithWhereUniqueWithoutUserInput = { + where: ModelCommentWhereUniqueInput + update: XOR + create: XOR + } + + export type ModelCommentUpdateWithWhereUniqueWithoutUserInput = { + where: ModelCommentWhereUniqueInput + data: XOR + } + + export type ModelCommentUpdateManyWithWhereWithoutUserInput = { + where: ModelCommentScalarWhereInput + data: XOR + } + + export type ModelCommentScalarWhereInput = { + AND?: ModelCommentScalarWhereInput | ModelCommentScalarWhereInput[] + OR?: ModelCommentScalarWhereInput[] + NOT?: ModelCommentScalarWhereInput | ModelCommentScalarWhereInput[] + id?: StringFilter<"ModelComment"> | string + legacyId?: IntNullableFilter<"ModelComment"> | number | null + parentId?: StringNullableFilter<"ModelComment"> | string | null + userId?: StringNullableFilter<"ModelComment"> | string | null + modelId?: StringFilter<"ModelComment"> | string + versionNumber?: IntNullableFilter<"ModelComment"> | number | null + content?: StringNullableFilter<"ModelComment"> | string | null + likesCount?: IntFilter<"ModelComment"> | number + createdAt?: DateTimeFilter<"ModelComment"> | Date | string + updatedAt?: DateTimeFilter<"ModelComment"> | Date | string + editedAt?: DateTimeNullableFilter<"ModelComment"> | Date | string | null + deletedAt?: DateTimeNullableFilter<"ModelComment"> | Date | string | null + } + + export type ModelCommentLikeUpsertWithWhereUniqueWithoutUserInput = { + where: ModelCommentLikeWhereUniqueInput + update: XOR + create: XOR + } + + export type ModelCommentLikeUpdateWithWhereUniqueWithoutUserInput = { + where: ModelCommentLikeWhereUniqueInput + data: XOR + } + + export type ModelCommentLikeUpdateManyWithWhereWithoutUserInput = { + where: ModelCommentLikeScalarWhereInput + data: XOR + } + + export type ModelCommentLikeScalarWhereInput = { + AND?: ModelCommentLikeScalarWhereInput | ModelCommentLikeScalarWhereInput[] + OR?: ModelCommentLikeScalarWhereInput[] + NOT?: ModelCommentLikeScalarWhereInput | ModelCommentLikeScalarWhereInput[] + modelCommentId?: StringFilter<"ModelCommentLike"> | string + userId?: StringFilter<"ModelCommentLike"> | string + createdAt?: DateTimeFilter<"ModelCommentLike"> | Date | string + } + export type PasskeyUpsertWithWhereUniqueWithoutUserInput = { where: PasskeyWhereUniqueInput update: XOR @@ -28373,6 +31925,8 @@ export namespace Prisma { modelLikes?: ModelLikeCreateNestedManyWithoutUserInput modelInteractions?: ModelInteractionCreateNestedManyWithoutUserInput modelDrafts?: ModelDraftCreateNestedManyWithoutUserInput + comments?: ModelCommentCreateNestedManyWithoutUserInput + commentLikes?: ModelCommentLikeCreateNestedManyWithoutUserInput passkeys?: PasskeyCreateNestedManyWithoutUserInput } @@ -28407,6 +31961,8 @@ export namespace Prisma { modelLikes?: ModelLikeUncheckedCreateNestedManyWithoutUserInput modelInteractions?: ModelInteractionUncheckedCreateNestedManyWithoutUserInput modelDrafts?: ModelDraftUncheckedCreateNestedManyWithoutUserInput + comments?: ModelCommentUncheckedCreateNestedManyWithoutUserInput + commentLikes?: ModelCommentLikeUncheckedCreateNestedManyWithoutUserInput passkeys?: PasskeyUncheckedCreateNestedManyWithoutUserInput } @@ -28457,6 +32013,8 @@ export namespace Prisma { modelLikes?: ModelLikeUpdateManyWithoutUserNestedInput modelInteractions?: ModelInteractionUpdateManyWithoutUserNestedInput modelDrafts?: ModelDraftUpdateManyWithoutUserNestedInput + comments?: ModelCommentUpdateManyWithoutUserNestedInput + commentLikes?: ModelCommentLikeUpdateManyWithoutUserNestedInput passkeys?: PasskeyUpdateManyWithoutUserNestedInput } @@ -28491,6 +32049,8 @@ export namespace Prisma { modelLikes?: ModelLikeUncheckedUpdateManyWithoutUserNestedInput modelInteractions?: ModelInteractionUncheckedUpdateManyWithoutUserNestedInput modelDrafts?: ModelDraftUncheckedUpdateManyWithoutUserNestedInput + comments?: ModelCommentUncheckedUpdateManyWithoutUserNestedInput + commentLikes?: ModelCommentLikeUncheckedUpdateManyWithoutUserNestedInput passkeys?: PasskeyUncheckedUpdateManyWithoutUserNestedInput } @@ -28525,6 +32085,8 @@ export namespace Prisma { modelLikes?: ModelLikeCreateNestedManyWithoutUserInput modelInteractions?: ModelInteractionCreateNestedManyWithoutUserInput modelDrafts?: ModelDraftCreateNestedManyWithoutUserInput + comments?: ModelCommentCreateNestedManyWithoutUserInput + commentLikes?: ModelCommentLikeCreateNestedManyWithoutUserInput passkeys?: PasskeyCreateNestedManyWithoutUserInput } @@ -28559,6 +32121,8 @@ export namespace Prisma { modelLikes?: ModelLikeUncheckedCreateNestedManyWithoutUserInput modelInteractions?: ModelInteractionUncheckedCreateNestedManyWithoutUserInput modelDrafts?: ModelDraftUncheckedCreateNestedManyWithoutUserInput + comments?: ModelCommentUncheckedCreateNestedManyWithoutUserInput + commentLikes?: ModelCommentLikeUncheckedCreateNestedManyWithoutUserInput passkeys?: PasskeyUncheckedCreateNestedManyWithoutUserInput } @@ -28609,6 +32173,8 @@ export namespace Prisma { modelLikes?: ModelLikeUpdateManyWithoutUserNestedInput modelInteractions?: ModelInteractionUpdateManyWithoutUserNestedInput modelDrafts?: ModelDraftUpdateManyWithoutUserNestedInput + comments?: ModelCommentUpdateManyWithoutUserNestedInput + commentLikes?: ModelCommentLikeUpdateManyWithoutUserNestedInput passkeys?: PasskeyUpdateManyWithoutUserNestedInput } @@ -28643,6 +32209,8 @@ export namespace Prisma { modelLikes?: ModelLikeUncheckedUpdateManyWithoutUserNestedInput modelInteractions?: ModelInteractionUncheckedUpdateManyWithoutUserNestedInput modelDrafts?: ModelDraftUncheckedUpdateManyWithoutUserNestedInput + comments?: ModelCommentUncheckedUpdateManyWithoutUserNestedInput + commentLikes?: ModelCommentLikeUncheckedUpdateManyWithoutUserNestedInput passkeys?: PasskeyUncheckedUpdateManyWithoutUserNestedInput } @@ -28677,6 +32245,8 @@ export namespace Prisma { modelLikes?: ModelLikeCreateNestedManyWithoutUserInput modelInteractions?: ModelInteractionCreateNestedManyWithoutUserInput modelDrafts?: ModelDraftCreateNestedManyWithoutUserInput + comments?: ModelCommentCreateNestedManyWithoutUserInput + commentLikes?: ModelCommentLikeCreateNestedManyWithoutUserInput passkeys?: PasskeyCreateNestedManyWithoutUserInput } @@ -28711,6 +32281,8 @@ export namespace Prisma { modelLikes?: ModelLikeUncheckedCreateNestedManyWithoutUserInput modelInteractions?: ModelInteractionUncheckedCreateNestedManyWithoutUserInput modelDrafts?: ModelDraftUncheckedCreateNestedManyWithoutUserInput + comments?: ModelCommentUncheckedCreateNestedManyWithoutUserInput + commentLikes?: ModelCommentLikeUncheckedCreateNestedManyWithoutUserInput passkeys?: PasskeyUncheckedCreateNestedManyWithoutUserInput } @@ -28761,6 +32333,8 @@ export namespace Prisma { modelLikes?: ModelLikeUpdateManyWithoutUserNestedInput modelInteractions?: ModelInteractionUpdateManyWithoutUserNestedInput modelDrafts?: ModelDraftUpdateManyWithoutUserNestedInput + comments?: ModelCommentUpdateManyWithoutUserNestedInput + commentLikes?: ModelCommentLikeUpdateManyWithoutUserNestedInput passkeys?: PasskeyUpdateManyWithoutUserNestedInput } @@ -28795,6 +32369,8 @@ export namespace Prisma { modelLikes?: ModelLikeUncheckedUpdateManyWithoutUserNestedInput modelInteractions?: ModelInteractionUncheckedUpdateManyWithoutUserNestedInput modelDrafts?: ModelDraftUncheckedUpdateManyWithoutUserNestedInput + comments?: ModelCommentUncheckedUpdateManyWithoutUserNestedInput + commentLikes?: ModelCommentLikeUncheckedUpdateManyWithoutUserNestedInput passkeys?: PasskeyUncheckedUpdateManyWithoutUserNestedInput } @@ -28830,6 +32406,8 @@ export namespace Prisma { modelLikes?: ModelLikeCreateNestedManyWithoutUserInput modelInteractions?: ModelInteractionCreateNestedManyWithoutUserInput modelDrafts?: ModelDraftCreateNestedManyWithoutUserInput + comments?: ModelCommentCreateNestedManyWithoutUserInput + commentLikes?: ModelCommentLikeCreateNestedManyWithoutUserInput } export type UserUncheckedCreateWithoutPasskeysInput = { @@ -28864,6 +32442,8 @@ export namespace Prisma { modelLikes?: ModelLikeUncheckedCreateNestedManyWithoutUserInput modelInteractions?: ModelInteractionUncheckedCreateNestedManyWithoutUserInput modelDrafts?: ModelDraftUncheckedCreateNestedManyWithoutUserInput + comments?: ModelCommentUncheckedCreateNestedManyWithoutUserInput + commentLikes?: ModelCommentLikeUncheckedCreateNestedManyWithoutUserInput } export type UserCreateOrConnectWithoutPasskeysInput = { @@ -28914,6 +32494,8 @@ export namespace Prisma { modelLikes?: ModelLikeUpdateManyWithoutUserNestedInput modelInteractions?: ModelInteractionUpdateManyWithoutUserNestedInput modelDrafts?: ModelDraftUpdateManyWithoutUserNestedInput + comments?: ModelCommentUpdateManyWithoutUserNestedInput + commentLikes?: ModelCommentLikeUpdateManyWithoutUserNestedInput } export type UserUncheckedUpdateWithoutPasskeysInput = { @@ -28948,6 +32530,8 @@ export namespace Prisma { modelLikes?: ModelLikeUncheckedUpdateManyWithoutUserNestedInput modelInteractions?: ModelInteractionUncheckedUpdateManyWithoutUserNestedInput modelDrafts?: ModelDraftUncheckedUpdateManyWithoutUserNestedInput + comments?: ModelCommentUncheckedUpdateManyWithoutUserNestedInput + commentLikes?: ModelCommentLikeUncheckedUpdateManyWithoutUserNestedInput } export type ModelVersionCreateWithoutLatestOfModelInput = { @@ -29011,6 +32595,7 @@ export namespace Prisma { likes?: ModelLikeCreateNestedManyWithoutModelInput interactions?: ModelInteractionCreateNestedManyWithoutModelInput drafts?: ModelDraftCreateNestedManyWithoutModelInput + comments?: ModelCommentCreateNestedManyWithoutModelInput } export type ModelUncheckedCreateWithoutChildModelsInput = { @@ -29036,6 +32621,7 @@ export namespace Prisma { likes?: ModelLikeUncheckedCreateNestedManyWithoutModelInput interactions?: ModelInteractionUncheckedCreateNestedManyWithoutModelInput drafts?: ModelDraftUncheckedCreateNestedManyWithoutModelInput + comments?: ModelCommentUncheckedCreateNestedManyWithoutModelInput } export type ModelCreateOrConnectWithoutChildModelsInput = { @@ -29065,6 +32651,7 @@ export namespace Prisma { likes?: ModelLikeCreateNestedManyWithoutModelInput interactions?: ModelInteractionCreateNestedManyWithoutModelInput drafts?: ModelDraftCreateNestedManyWithoutModelInput + comments?: ModelCommentCreateNestedManyWithoutModelInput } export type ModelUncheckedCreateWithoutParentModelInput = { @@ -29090,6 +32677,7 @@ export namespace Prisma { likes?: ModelLikeUncheckedCreateNestedManyWithoutModelInput interactions?: ModelInteractionUncheckedCreateNestedManyWithoutModelInput drafts?: ModelDraftUncheckedCreateNestedManyWithoutModelInput + comments?: ModelCommentUncheckedCreateNestedManyWithoutModelInput } export type ModelCreateOrConnectWithoutParentModelInput = { @@ -29343,6 +32931,48 @@ export namespace Prisma { skipDuplicates?: boolean } + export type ModelCommentCreateWithoutModelInput = { + id?: string + legacyId?: number | null + versionNumber?: number | null + content?: string | null + likesCount?: number + createdAt?: Date | string + updatedAt?: Date | string + editedAt?: Date | string | null + deletedAt?: Date | string | null + user?: UserCreateNestedOneWithoutCommentsInput + parent?: ModelCommentCreateNestedOneWithoutRepliesInput + replies?: ModelCommentCreateNestedManyWithoutParentInput + likes?: ModelCommentLikeCreateNestedManyWithoutModelCommentInput + } + + export type ModelCommentUncheckedCreateWithoutModelInput = { + id?: string + legacyId?: number | null + parentId?: string | null + userId?: string | null + versionNumber?: number | null + content?: string | null + likesCount?: number + createdAt?: Date | string + updatedAt?: Date | string + editedAt?: Date | string | null + deletedAt?: Date | string | null + replies?: ModelCommentUncheckedCreateNestedManyWithoutParentInput + likes?: ModelCommentLikeUncheckedCreateNestedManyWithoutModelCommentInput + } + + export type ModelCommentCreateOrConnectWithoutModelInput = { + where: ModelCommentWhereUniqueInput + create: XOR + } + + export type ModelCommentCreateManyModelInputEnvelope = { + data: ModelCommentCreateManyModelInput | ModelCommentCreateManyModelInput[] + skipDuplicates?: boolean + } + export type ModelVersionUpsertWithoutLatestOfModelInput = { update: XOR create: XOR @@ -29421,6 +33051,7 @@ export namespace Prisma { likes?: ModelLikeUpdateManyWithoutModelNestedInput interactions?: ModelInteractionUpdateManyWithoutModelNestedInput drafts?: ModelDraftUpdateManyWithoutModelNestedInput + comments?: ModelCommentUpdateManyWithoutModelNestedInput } export type ModelUncheckedUpdateWithoutChildModelsInput = { @@ -29446,6 +33077,7 @@ export namespace Prisma { likes?: ModelLikeUncheckedUpdateManyWithoutModelNestedInput interactions?: ModelInteractionUncheckedUpdateManyWithoutModelNestedInput drafts?: ModelDraftUncheckedUpdateManyWithoutModelNestedInput + comments?: ModelCommentUncheckedUpdateManyWithoutModelNestedInput } export type ModelUpsertWithWhereUniqueWithoutParentModelInput = { @@ -29671,6 +33303,22 @@ export namespace Prisma { data: XOR } + export type ModelCommentUpsertWithWhereUniqueWithoutModelInput = { + where: ModelCommentWhereUniqueInput + update: XOR + create: XOR + } + + export type ModelCommentUpdateWithWhereUniqueWithoutModelInput = { + where: ModelCommentWhereUniqueInput + data: XOR + } + + export type ModelCommentUpdateManyWithWhereWithoutModelInput = { + where: ModelCommentScalarWhereInput + data: XOR + } + export type ModelCreateWithoutVersionsInput = { legacyId?: number | null visibility?: $Enums.ModelVisibility @@ -29693,6 +33341,7 @@ export namespace Prisma { likes?: ModelLikeCreateNestedManyWithoutModelInput interactions?: ModelInteractionCreateNestedManyWithoutModelInput drafts?: ModelDraftCreateNestedManyWithoutModelInput + comments?: ModelCommentCreateNestedManyWithoutModelInput } export type ModelUncheckedCreateWithoutVersionsInput = { @@ -29718,6 +33367,7 @@ export namespace Prisma { likes?: ModelLikeUncheckedCreateNestedManyWithoutModelInput interactions?: ModelInteractionUncheckedCreateNestedManyWithoutModelInput drafts?: ModelDraftUncheckedCreateNestedManyWithoutModelInput + comments?: ModelCommentUncheckedCreateNestedManyWithoutModelInput } export type ModelCreateOrConnectWithoutVersionsInput = { @@ -29747,6 +33397,7 @@ export namespace Prisma { likes?: ModelLikeCreateNestedManyWithoutModelInput interactions?: ModelInteractionCreateNestedManyWithoutModelInput drafts?: ModelDraftCreateNestedManyWithoutModelInput + comments?: ModelCommentCreateNestedManyWithoutModelInput } export type ModelUncheckedCreateWithoutLatestVersionInput = { @@ -29771,6 +33422,7 @@ export namespace Prisma { likes?: ModelLikeUncheckedCreateNestedManyWithoutModelInput interactions?: ModelInteractionUncheckedCreateNestedManyWithoutModelInput drafts?: ModelDraftUncheckedCreateNestedManyWithoutModelInput + comments?: ModelCommentUncheckedCreateNestedManyWithoutModelInput } export type ModelCreateOrConnectWithoutLatestVersionInput = { @@ -29800,6 +33452,7 @@ export namespace Prisma { likes?: ModelLikeCreateNestedManyWithoutModelInput interactions?: ModelInteractionCreateNestedManyWithoutModelInput drafts?: ModelDraftCreateNestedManyWithoutModelInput + comments?: ModelCommentCreateNestedManyWithoutModelInput } export type ModelUncheckedCreateWithoutParentVersionInput = { @@ -29824,6 +33477,7 @@ export namespace Prisma { likes?: ModelLikeUncheckedCreateNestedManyWithoutModelInput interactions?: ModelInteractionUncheckedCreateNestedManyWithoutModelInput drafts?: ModelDraftUncheckedCreateNestedManyWithoutModelInput + comments?: ModelCommentUncheckedCreateNestedManyWithoutModelInput } export type ModelCreateOrConnectWithoutParentVersionInput = { @@ -29914,6 +33568,7 @@ export namespace Prisma { likes?: ModelLikeUpdateManyWithoutModelNestedInput interactions?: ModelInteractionUpdateManyWithoutModelNestedInput drafts?: ModelDraftUpdateManyWithoutModelNestedInput + comments?: ModelCommentUpdateManyWithoutModelNestedInput } export type ModelUncheckedUpdateWithoutVersionsInput = { @@ -29939,6 +33594,7 @@ export namespace Prisma { likes?: ModelLikeUncheckedUpdateManyWithoutModelNestedInput interactions?: ModelInteractionUncheckedUpdateManyWithoutModelNestedInput drafts?: ModelDraftUncheckedUpdateManyWithoutModelNestedInput + comments?: ModelCommentUncheckedUpdateManyWithoutModelNestedInput } export type ModelUpsertWithoutLatestVersionInput = { @@ -29974,6 +33630,7 @@ export namespace Prisma { likes?: ModelLikeUpdateManyWithoutModelNestedInput interactions?: ModelInteractionUpdateManyWithoutModelNestedInput drafts?: ModelDraftUpdateManyWithoutModelNestedInput + comments?: ModelCommentUpdateManyWithoutModelNestedInput } export type ModelUncheckedUpdateWithoutLatestVersionInput = { @@ -29998,6 +33655,7 @@ export namespace Prisma { likes?: ModelLikeUncheckedUpdateManyWithoutModelNestedInput interactions?: ModelInteractionUncheckedUpdateManyWithoutModelNestedInput drafts?: ModelDraftUncheckedUpdateManyWithoutModelNestedInput + comments?: ModelCommentUncheckedUpdateManyWithoutModelNestedInput } export type ModelUpsertWithWhereUniqueWithoutParentVersionInput = { @@ -30100,97 +33758,614 @@ export namespace Prisma { export type TagCreateWithoutModelVersionsInput = { id?: string legacyId?: number | null - name: string - displayName?: string | null - createdAt?: Date | string + name: string + displayName?: string | null + createdAt?: Date | string + } + + export type TagUncheckedCreateWithoutModelVersionsInput = { + id?: string + legacyId?: number | null + name: string + displayName?: string | null + createdAt?: Date | string + } + + export type TagCreateOrConnectWithoutModelVersionsInput = { + where: TagWhereUniqueInput + create: XOR + } + + export type ModelVersionUpsertWithoutTagsInput = { + update: XOR + create: XOR + where?: ModelVersionWhereInput + } + + export type ModelVersionUpdateToOneWithWhereWithoutTagsInput = { + where?: ModelVersionWhereInput + data: XOR + } + + export type ModelVersionUpdateWithoutTagsInput = { + versionNumber?: IntFieldUpdateOperationsInput | number + title?: StringFieldUpdateOperationsInput | string + description?: NullableStringFieldUpdateOperationsInput | string | null + changeSummary?: NullableStringFieldUpdateOperationsInput | string | null + previewImageFileKey?: NullableStringFieldUpdateOperationsInput | string | null + netlogoFileKey?: StringFieldUpdateOperationsInput | string + netlogoVersion?: NullableStringFieldUpdateOperationsInput | string | null + infoTab?: NullableStringFieldUpdateOperationsInput | string | null + createdAt?: DateTimeFieldUpdateOperationsInput | Date | string + finalizedAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + model?: ModelUpdateOneRequiredWithoutVersionsNestedInput + latestOfModel?: ModelUpdateOneWithoutLatestVersionNestedInput + parentOfModels?: ModelUpdateManyWithoutParentVersionNestedInput + taggedAdditionalFiles?: ModelAdditionalFileUpdateManyWithoutTaggedVersionNestedInput + } + + export type ModelVersionUncheckedUpdateWithoutTagsInput = { + modelId?: StringFieldUpdateOperationsInput | string + versionNumber?: IntFieldUpdateOperationsInput | number + title?: StringFieldUpdateOperationsInput | string + description?: NullableStringFieldUpdateOperationsInput | string | null + changeSummary?: NullableStringFieldUpdateOperationsInput | string | null + previewImageFileKey?: NullableStringFieldUpdateOperationsInput | string | null + netlogoFileKey?: StringFieldUpdateOperationsInput | string + netlogoVersion?: NullableStringFieldUpdateOperationsInput | string | null + infoTab?: NullableStringFieldUpdateOperationsInput | string | null + createdAt?: DateTimeFieldUpdateOperationsInput | Date | string + finalizedAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + latestOfModel?: ModelUncheckedUpdateOneWithoutLatestVersionNestedInput + parentOfModels?: ModelUncheckedUpdateManyWithoutParentVersionNestedInput + taggedAdditionalFiles?: ModelAdditionalFileUncheckedUpdateManyWithoutTaggedVersionNestedInput + } + + export type TagUpsertWithoutModelVersionsInput = { + update: XOR + create: XOR + where?: TagWhereInput + } + + export type TagUpdateToOneWithWhereWithoutModelVersionsInput = { + where?: TagWhereInput + data: XOR + } + + export type TagUpdateWithoutModelVersionsInput = { + id?: StringFieldUpdateOperationsInput | string + legacyId?: NullableIntFieldUpdateOperationsInput | number | null + name?: StringFieldUpdateOperationsInput | string + displayName?: NullableStringFieldUpdateOperationsInput | string | null + createdAt?: DateTimeFieldUpdateOperationsInput | Date | string + } + + export type TagUncheckedUpdateWithoutModelVersionsInput = { + id?: StringFieldUpdateOperationsInput | string + legacyId?: NullableIntFieldUpdateOperationsInput | number | null + name?: StringFieldUpdateOperationsInput | string + displayName?: NullableStringFieldUpdateOperationsInput | string | null + createdAt?: DateTimeFieldUpdateOperationsInput | Date | string + } + + export type ModelCreateWithoutAdditionalFilesInput = { + legacyId?: number | null + visibility?: $Enums.ModelVisibility + isEndorsed?: boolean + isLibraryModel?: boolean + viewCount?: number + runCount?: number + downloadCount?: number + shareCount?: number + createdAt?: Date | string + updatedAt?: Date | string + deletedAt?: Date | string | null + latestVersion?: ModelVersionCreateNestedOneWithoutLatestOfModelInput + parentModel?: ModelCreateNestedOneWithoutChildModelsInput + childModels?: ModelCreateNestedManyWithoutParentModelInput + parentVersion?: ModelVersionCreateNestedOneWithoutParentOfModelsInput + versions?: ModelVersionCreateNestedManyWithoutModelInput + authors?: ModelAuthorCreateNestedManyWithoutModelInput + permissions?: ModelPermissionCreateNestedManyWithoutModelInput + likes?: ModelLikeCreateNestedManyWithoutModelInput + interactions?: ModelInteractionCreateNestedManyWithoutModelInput + drafts?: ModelDraftCreateNestedManyWithoutModelInput + comments?: ModelCommentCreateNestedManyWithoutModelInput + } + + export type ModelUncheckedCreateWithoutAdditionalFilesInput = { + id?: string + legacyId?: number | null + latestVersionNumber?: number | null + parentModelId?: string | null + parentVersionNumber?: number | null + visibility?: $Enums.ModelVisibility + isEndorsed?: boolean + isLibraryModel?: boolean + viewCount?: number + runCount?: number + downloadCount?: number + shareCount?: number + createdAt?: Date | string + updatedAt?: Date | string + deletedAt?: Date | string | null + childModels?: ModelUncheckedCreateNestedManyWithoutParentModelInput + versions?: ModelVersionUncheckedCreateNestedManyWithoutModelInput + authors?: ModelAuthorUncheckedCreateNestedManyWithoutModelInput + permissions?: ModelPermissionUncheckedCreateNestedManyWithoutModelInput + likes?: ModelLikeUncheckedCreateNestedManyWithoutModelInput + interactions?: ModelInteractionUncheckedCreateNestedManyWithoutModelInput + drafts?: ModelDraftUncheckedCreateNestedManyWithoutModelInput + comments?: ModelCommentUncheckedCreateNestedManyWithoutModelInput + } + + export type ModelCreateOrConnectWithoutAdditionalFilesInput = { + where: ModelWhereUniqueInput + create: XOR + } + + export type ModelVersionCreateWithoutTaggedAdditionalFilesInput = { + versionNumber: number + title: string + description?: string | null + changeSummary?: string | null + previewImageFileKey?: string | null + netlogoFileKey: string + netlogoVersion?: string | null + infoTab?: string | null + createdAt?: Date | string + finalizedAt?: Date | string | null + model: ModelCreateNestedOneWithoutVersionsInput + latestOfModel?: ModelCreateNestedOneWithoutLatestVersionInput + parentOfModels?: ModelCreateNestedManyWithoutParentVersionInput + tags?: ModelVersionTagCreateNestedManyWithoutModelVersionInput + } + + export type ModelVersionUncheckedCreateWithoutTaggedAdditionalFilesInput = { + modelId: string + versionNumber: number + title: string + description?: string | null + changeSummary?: string | null + previewImageFileKey?: string | null + netlogoFileKey: string + netlogoVersion?: string | null + infoTab?: string | null + createdAt?: Date | string + finalizedAt?: Date | string | null + latestOfModel?: ModelUncheckedCreateNestedOneWithoutLatestVersionInput + parentOfModels?: ModelUncheckedCreateNestedManyWithoutParentVersionInput + tags?: ModelVersionTagUncheckedCreateNestedManyWithoutModelVersionInput + } + + export type ModelVersionCreateOrConnectWithoutTaggedAdditionalFilesInput = { + where: ModelVersionWhereUniqueInput + create: XOR + } + + export type ModelUpsertWithoutAdditionalFilesInput = { + update: XOR + create: XOR + where?: ModelWhereInput + } + + export type ModelUpdateToOneWithWhereWithoutAdditionalFilesInput = { + where?: ModelWhereInput + data: XOR + } + + export type ModelUpdateWithoutAdditionalFilesInput = { + legacyId?: NullableIntFieldUpdateOperationsInput | number | null + visibility?: EnumModelVisibilityFieldUpdateOperationsInput | $Enums.ModelVisibility + isEndorsed?: BoolFieldUpdateOperationsInput | boolean + isLibraryModel?: BoolFieldUpdateOperationsInput | boolean + viewCount?: IntFieldUpdateOperationsInput | number + runCount?: IntFieldUpdateOperationsInput | number + downloadCount?: IntFieldUpdateOperationsInput | number + shareCount?: IntFieldUpdateOperationsInput | number + createdAt?: DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string + deletedAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + latestVersion?: ModelVersionUpdateOneWithoutLatestOfModelNestedInput + parentModel?: ModelUpdateOneWithoutChildModelsNestedInput + childModels?: ModelUpdateManyWithoutParentModelNestedInput + parentVersion?: ModelVersionUpdateOneWithoutParentOfModelsNestedInput + versions?: ModelVersionUpdateManyWithoutModelNestedInput + authors?: ModelAuthorUpdateManyWithoutModelNestedInput + permissions?: ModelPermissionUpdateManyWithoutModelNestedInput + likes?: ModelLikeUpdateManyWithoutModelNestedInput + interactions?: ModelInteractionUpdateManyWithoutModelNestedInput + drafts?: ModelDraftUpdateManyWithoutModelNestedInput + comments?: ModelCommentUpdateManyWithoutModelNestedInput + } + + export type ModelUncheckedUpdateWithoutAdditionalFilesInput = { + id?: StringFieldUpdateOperationsInput | string + legacyId?: NullableIntFieldUpdateOperationsInput | number | null + latestVersionNumber?: NullableIntFieldUpdateOperationsInput | number | null + parentModelId?: NullableStringFieldUpdateOperationsInput | string | null + parentVersionNumber?: NullableIntFieldUpdateOperationsInput | number | null + visibility?: EnumModelVisibilityFieldUpdateOperationsInput | $Enums.ModelVisibility + isEndorsed?: BoolFieldUpdateOperationsInput | boolean + isLibraryModel?: BoolFieldUpdateOperationsInput | boolean + viewCount?: IntFieldUpdateOperationsInput | number + runCount?: IntFieldUpdateOperationsInput | number + downloadCount?: IntFieldUpdateOperationsInput | number + shareCount?: IntFieldUpdateOperationsInput | number + createdAt?: DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string + deletedAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + childModels?: ModelUncheckedUpdateManyWithoutParentModelNestedInput + versions?: ModelVersionUncheckedUpdateManyWithoutModelNestedInput + authors?: ModelAuthorUncheckedUpdateManyWithoutModelNestedInput + permissions?: ModelPermissionUncheckedUpdateManyWithoutModelNestedInput + likes?: ModelLikeUncheckedUpdateManyWithoutModelNestedInput + interactions?: ModelInteractionUncheckedUpdateManyWithoutModelNestedInput + drafts?: ModelDraftUncheckedUpdateManyWithoutModelNestedInput + comments?: ModelCommentUncheckedUpdateManyWithoutModelNestedInput + } + + export type ModelVersionUpsertWithoutTaggedAdditionalFilesInput = { + update: XOR + create: XOR + where?: ModelVersionWhereInput + } + + export type ModelVersionUpdateToOneWithWhereWithoutTaggedAdditionalFilesInput = { + where?: ModelVersionWhereInput + data: XOR + } + + export type ModelVersionUpdateWithoutTaggedAdditionalFilesInput = { + versionNumber?: IntFieldUpdateOperationsInput | number + title?: StringFieldUpdateOperationsInput | string + description?: NullableStringFieldUpdateOperationsInput | string | null + changeSummary?: NullableStringFieldUpdateOperationsInput | string | null + previewImageFileKey?: NullableStringFieldUpdateOperationsInput | string | null + netlogoFileKey?: StringFieldUpdateOperationsInput | string + netlogoVersion?: NullableStringFieldUpdateOperationsInput | string | null + infoTab?: NullableStringFieldUpdateOperationsInput | string | null + createdAt?: DateTimeFieldUpdateOperationsInput | Date | string + finalizedAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + model?: ModelUpdateOneRequiredWithoutVersionsNestedInput + latestOfModel?: ModelUpdateOneWithoutLatestVersionNestedInput + parentOfModels?: ModelUpdateManyWithoutParentVersionNestedInput + tags?: ModelVersionTagUpdateManyWithoutModelVersionNestedInput + } + + export type ModelVersionUncheckedUpdateWithoutTaggedAdditionalFilesInput = { + modelId?: StringFieldUpdateOperationsInput | string + versionNumber?: IntFieldUpdateOperationsInput | number + title?: StringFieldUpdateOperationsInput | string + description?: NullableStringFieldUpdateOperationsInput | string | null + changeSummary?: NullableStringFieldUpdateOperationsInput | string | null + previewImageFileKey?: NullableStringFieldUpdateOperationsInput | string | null + netlogoFileKey?: StringFieldUpdateOperationsInput | string + netlogoVersion?: NullableStringFieldUpdateOperationsInput | string | null + infoTab?: NullableStringFieldUpdateOperationsInput | string | null + createdAt?: DateTimeFieldUpdateOperationsInput | Date | string + finalizedAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + latestOfModel?: ModelUncheckedUpdateOneWithoutLatestVersionNestedInput + parentOfModels?: ModelUncheckedUpdateManyWithoutParentVersionNestedInput + tags?: ModelVersionTagUncheckedUpdateManyWithoutModelVersionNestedInput + } + + export type ModelVersionTagCreateWithoutTagInput = { + createdAt?: Date | string + modelVersion: ModelVersionCreateNestedOneWithoutTagsInput + } + + export type ModelVersionTagUncheckedCreateWithoutTagInput = { + modelId: string + versionNumber: number + createdAt?: Date | string + } + + export type ModelVersionTagCreateOrConnectWithoutTagInput = { + where: ModelVersionTagWhereUniqueInput + create: XOR + } + + export type ModelVersionTagCreateManyTagInputEnvelope = { + data: ModelVersionTagCreateManyTagInput | ModelVersionTagCreateManyTagInput[] + skipDuplicates?: boolean + } + + export type ModelVersionTagUpsertWithWhereUniqueWithoutTagInput = { + where: ModelVersionTagWhereUniqueInput + update: XOR + create: XOR + } + + export type ModelVersionTagUpdateWithWhereUniqueWithoutTagInput = { + where: ModelVersionTagWhereUniqueInput + data: XOR + } + + export type ModelVersionTagUpdateManyWithWhereWithoutTagInput = { + where: ModelVersionTagScalarWhereInput + data: XOR + } + + export type ModelCreateWithoutAuthorsInput = { + legacyId?: number | null + visibility?: $Enums.ModelVisibility + isEndorsed?: boolean + isLibraryModel?: boolean + viewCount?: number + runCount?: number + downloadCount?: number + shareCount?: number + createdAt?: Date | string + updatedAt?: Date | string + deletedAt?: Date | string | null + latestVersion?: ModelVersionCreateNestedOneWithoutLatestOfModelInput + parentModel?: ModelCreateNestedOneWithoutChildModelsInput + childModels?: ModelCreateNestedManyWithoutParentModelInput + parentVersion?: ModelVersionCreateNestedOneWithoutParentOfModelsInput + versions?: ModelVersionCreateNestedManyWithoutModelInput + permissions?: ModelPermissionCreateNestedManyWithoutModelInput + additionalFiles?: ModelAdditionalFileCreateNestedManyWithoutModelInput + likes?: ModelLikeCreateNestedManyWithoutModelInput + interactions?: ModelInteractionCreateNestedManyWithoutModelInput + drafts?: ModelDraftCreateNestedManyWithoutModelInput + comments?: ModelCommentCreateNestedManyWithoutModelInput + } + + export type ModelUncheckedCreateWithoutAuthorsInput = { + id?: string + legacyId?: number | null + latestVersionNumber?: number | null + parentModelId?: string | null + parentVersionNumber?: number | null + visibility?: $Enums.ModelVisibility + isEndorsed?: boolean + isLibraryModel?: boolean + viewCount?: number + runCount?: number + downloadCount?: number + shareCount?: number + createdAt?: Date | string + updatedAt?: Date | string + deletedAt?: Date | string | null + childModels?: ModelUncheckedCreateNestedManyWithoutParentModelInput + versions?: ModelVersionUncheckedCreateNestedManyWithoutModelInput + permissions?: ModelPermissionUncheckedCreateNestedManyWithoutModelInput + additionalFiles?: ModelAdditionalFileUncheckedCreateNestedManyWithoutModelInput + likes?: ModelLikeUncheckedCreateNestedManyWithoutModelInput + interactions?: ModelInteractionUncheckedCreateNestedManyWithoutModelInput + drafts?: ModelDraftUncheckedCreateNestedManyWithoutModelInput + comments?: ModelCommentUncheckedCreateNestedManyWithoutModelInput + } + + export type ModelCreateOrConnectWithoutAuthorsInput = { + where: ModelWhereUniqueInput + create: XOR + } + + export type UserCreateWithoutAuthoredModelsInput = { + id?: string + name?: string | null + email?: string | null + emailVerified?: boolean + image?: string | null + createdAt?: Date | string + updatedAt?: Date | string + systemRole?: $Enums.SystemRole + userKind?: $Enums.UserKind + isProfilePublic?: boolean + deletedAt?: Date | string | null + bio?: string | null + country?: string | null + socialLinks?: NullableJsonNullValueInput | InputJsonValue + dob?: Date | string | null + affiliation?: string | null + role?: string | null + banned?: boolean | null + banReason?: string | null + banExpires?: Date | string | null + onboardedAt?: Date | string | null + legacyId?: number | null + accounts?: AccountCreateNestedManyWithoutUserInput + sessions?: SessionCreateNestedManyWithoutUserInput + verifications?: VerificationCreateNestedManyWithoutUserInput + grantedPermissions?: ModelPermissionCreateNestedManyWithoutGranteeUserInput + events?: EventCreateNestedManyWithoutActorInput + modelLikes?: ModelLikeCreateNestedManyWithoutUserInput + modelInteractions?: ModelInteractionCreateNestedManyWithoutUserInput + modelDrafts?: ModelDraftCreateNestedManyWithoutUserInput + comments?: ModelCommentCreateNestedManyWithoutUserInput + commentLikes?: ModelCommentLikeCreateNestedManyWithoutUserInput + passkeys?: PasskeyCreateNestedManyWithoutUserInput } - export type TagUncheckedCreateWithoutModelVersionsInput = { + export type UserUncheckedCreateWithoutAuthoredModelsInput = { id?: string - legacyId?: number | null - name: string - displayName?: string | null + name?: string | null + email?: string | null + emailVerified?: boolean + image?: string | null createdAt?: Date | string + updatedAt?: Date | string + systemRole?: $Enums.SystemRole + userKind?: $Enums.UserKind + isProfilePublic?: boolean + deletedAt?: Date | string | null + bio?: string | null + country?: string | null + socialLinks?: NullableJsonNullValueInput | InputJsonValue + dob?: Date | string | null + affiliation?: string | null + role?: string | null + banned?: boolean | null + banReason?: string | null + banExpires?: Date | string | null + onboardedAt?: Date | string | null + legacyId?: number | null + accounts?: AccountUncheckedCreateNestedManyWithoutUserInput + sessions?: SessionUncheckedCreateNestedManyWithoutUserInput + verifications?: VerificationUncheckedCreateNestedManyWithoutUserInput + grantedPermissions?: ModelPermissionUncheckedCreateNestedManyWithoutGranteeUserInput + events?: EventUncheckedCreateNestedManyWithoutActorInput + modelLikes?: ModelLikeUncheckedCreateNestedManyWithoutUserInput + modelInteractions?: ModelInteractionUncheckedCreateNestedManyWithoutUserInput + modelDrafts?: ModelDraftUncheckedCreateNestedManyWithoutUserInput + comments?: ModelCommentUncheckedCreateNestedManyWithoutUserInput + commentLikes?: ModelCommentLikeUncheckedCreateNestedManyWithoutUserInput + passkeys?: PasskeyUncheckedCreateNestedManyWithoutUserInput } - export type TagCreateOrConnectWithoutModelVersionsInput = { - where: TagWhereUniqueInput - create: XOR + export type UserCreateOrConnectWithoutAuthoredModelsInput = { + where: UserWhereUniqueInput + create: XOR } - export type ModelVersionUpsertWithoutTagsInput = { - update: XOR - create: XOR - where?: ModelVersionWhereInput + export type ModelUpsertWithoutAuthorsInput = { + update: XOR + create: XOR + where?: ModelWhereInput } - export type ModelVersionUpdateToOneWithWhereWithoutTagsInput = { - where?: ModelVersionWhereInput - data: XOR + export type ModelUpdateToOneWithWhereWithoutAuthorsInput = { + where?: ModelWhereInput + data: XOR } - export type ModelVersionUpdateWithoutTagsInput = { - versionNumber?: IntFieldUpdateOperationsInput | number - title?: StringFieldUpdateOperationsInput | string - description?: NullableStringFieldUpdateOperationsInput | string | null - changeSummary?: NullableStringFieldUpdateOperationsInput | string | null - previewImageFileKey?: NullableStringFieldUpdateOperationsInput | string | null - netlogoFileKey?: StringFieldUpdateOperationsInput | string - netlogoVersion?: NullableStringFieldUpdateOperationsInput | string | null - infoTab?: NullableStringFieldUpdateOperationsInput | string | null + export type ModelUpdateWithoutAuthorsInput = { + legacyId?: NullableIntFieldUpdateOperationsInput | number | null + visibility?: EnumModelVisibilityFieldUpdateOperationsInput | $Enums.ModelVisibility + isEndorsed?: BoolFieldUpdateOperationsInput | boolean + isLibraryModel?: BoolFieldUpdateOperationsInput | boolean + viewCount?: IntFieldUpdateOperationsInput | number + runCount?: IntFieldUpdateOperationsInput | number + downloadCount?: IntFieldUpdateOperationsInput | number + shareCount?: IntFieldUpdateOperationsInput | number createdAt?: DateTimeFieldUpdateOperationsInput | Date | string - finalizedAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null - model?: ModelUpdateOneRequiredWithoutVersionsNestedInput - latestOfModel?: ModelUpdateOneWithoutLatestVersionNestedInput - parentOfModels?: ModelUpdateManyWithoutParentVersionNestedInput - taggedAdditionalFiles?: ModelAdditionalFileUpdateManyWithoutTaggedVersionNestedInput + updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string + deletedAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + latestVersion?: ModelVersionUpdateOneWithoutLatestOfModelNestedInput + parentModel?: ModelUpdateOneWithoutChildModelsNestedInput + childModels?: ModelUpdateManyWithoutParentModelNestedInput + parentVersion?: ModelVersionUpdateOneWithoutParentOfModelsNestedInput + versions?: ModelVersionUpdateManyWithoutModelNestedInput + permissions?: ModelPermissionUpdateManyWithoutModelNestedInput + additionalFiles?: ModelAdditionalFileUpdateManyWithoutModelNestedInput + likes?: ModelLikeUpdateManyWithoutModelNestedInput + interactions?: ModelInteractionUpdateManyWithoutModelNestedInput + drafts?: ModelDraftUpdateManyWithoutModelNestedInput + comments?: ModelCommentUpdateManyWithoutModelNestedInput } - export type ModelVersionUncheckedUpdateWithoutTagsInput = { - modelId?: StringFieldUpdateOperationsInput | string - versionNumber?: IntFieldUpdateOperationsInput | number - title?: StringFieldUpdateOperationsInput | string - description?: NullableStringFieldUpdateOperationsInput | string | null - changeSummary?: NullableStringFieldUpdateOperationsInput | string | null - previewImageFileKey?: NullableStringFieldUpdateOperationsInput | string | null - netlogoFileKey?: StringFieldUpdateOperationsInput | string - netlogoVersion?: NullableStringFieldUpdateOperationsInput | string | null - infoTab?: NullableStringFieldUpdateOperationsInput | string | null + export type ModelUncheckedUpdateWithoutAuthorsInput = { + id?: StringFieldUpdateOperationsInput | string + legacyId?: NullableIntFieldUpdateOperationsInput | number | null + latestVersionNumber?: NullableIntFieldUpdateOperationsInput | number | null + parentModelId?: NullableStringFieldUpdateOperationsInput | string | null + parentVersionNumber?: NullableIntFieldUpdateOperationsInput | number | null + visibility?: EnumModelVisibilityFieldUpdateOperationsInput | $Enums.ModelVisibility + isEndorsed?: BoolFieldUpdateOperationsInput | boolean + isLibraryModel?: BoolFieldUpdateOperationsInput | boolean + viewCount?: IntFieldUpdateOperationsInput | number + runCount?: IntFieldUpdateOperationsInput | number + downloadCount?: IntFieldUpdateOperationsInput | number + shareCount?: IntFieldUpdateOperationsInput | number createdAt?: DateTimeFieldUpdateOperationsInput | Date | string - finalizedAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null - latestOfModel?: ModelUncheckedUpdateOneWithoutLatestVersionNestedInput - parentOfModels?: ModelUncheckedUpdateManyWithoutParentVersionNestedInput - taggedAdditionalFiles?: ModelAdditionalFileUncheckedUpdateManyWithoutTaggedVersionNestedInput + updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string + deletedAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + childModels?: ModelUncheckedUpdateManyWithoutParentModelNestedInput + versions?: ModelVersionUncheckedUpdateManyWithoutModelNestedInput + permissions?: ModelPermissionUncheckedUpdateManyWithoutModelNestedInput + additionalFiles?: ModelAdditionalFileUncheckedUpdateManyWithoutModelNestedInput + likes?: ModelLikeUncheckedUpdateManyWithoutModelNestedInput + interactions?: ModelInteractionUncheckedUpdateManyWithoutModelNestedInput + drafts?: ModelDraftUncheckedUpdateManyWithoutModelNestedInput + comments?: ModelCommentUncheckedUpdateManyWithoutModelNestedInput } - export type TagUpsertWithoutModelVersionsInput = { - update: XOR - create: XOR - where?: TagWhereInput + export type UserUpsertWithoutAuthoredModelsInput = { + update: XOR + create: XOR + where?: UserWhereInput } - export type TagUpdateToOneWithWhereWithoutModelVersionsInput = { - where?: TagWhereInput - data: XOR + export type UserUpdateToOneWithWhereWithoutAuthoredModelsInput = { + where?: UserWhereInput + data: XOR } - export type TagUpdateWithoutModelVersionsInput = { + export type UserUpdateWithoutAuthoredModelsInput = { id?: StringFieldUpdateOperationsInput | string - legacyId?: NullableIntFieldUpdateOperationsInput | number | null - name?: StringFieldUpdateOperationsInput | string - displayName?: NullableStringFieldUpdateOperationsInput | string | null + name?: NullableStringFieldUpdateOperationsInput | string | null + email?: NullableStringFieldUpdateOperationsInput | string | null + emailVerified?: BoolFieldUpdateOperationsInput | boolean + image?: NullableStringFieldUpdateOperationsInput | string | null createdAt?: DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string + systemRole?: EnumSystemRoleFieldUpdateOperationsInput | $Enums.SystemRole + userKind?: EnumUserKindFieldUpdateOperationsInput | $Enums.UserKind + isProfilePublic?: BoolFieldUpdateOperationsInput | boolean + deletedAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + bio?: NullableStringFieldUpdateOperationsInput | string | null + country?: NullableStringFieldUpdateOperationsInput | string | null + socialLinks?: NullableJsonNullValueInput | InputJsonValue + dob?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + affiliation?: NullableStringFieldUpdateOperationsInput | string | null + role?: NullableStringFieldUpdateOperationsInput | string | null + banned?: NullableBoolFieldUpdateOperationsInput | boolean | null + banReason?: NullableStringFieldUpdateOperationsInput | string | null + banExpires?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + onboardedAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + legacyId?: NullableIntFieldUpdateOperationsInput | number | null + accounts?: AccountUpdateManyWithoutUserNestedInput + sessions?: SessionUpdateManyWithoutUserNestedInput + verifications?: VerificationUpdateManyWithoutUserNestedInput + grantedPermissions?: ModelPermissionUpdateManyWithoutGranteeUserNestedInput + events?: EventUpdateManyWithoutActorNestedInput + modelLikes?: ModelLikeUpdateManyWithoutUserNestedInput + modelInteractions?: ModelInteractionUpdateManyWithoutUserNestedInput + modelDrafts?: ModelDraftUpdateManyWithoutUserNestedInput + comments?: ModelCommentUpdateManyWithoutUserNestedInput + commentLikes?: ModelCommentLikeUpdateManyWithoutUserNestedInput + passkeys?: PasskeyUpdateManyWithoutUserNestedInput } - export type TagUncheckedUpdateWithoutModelVersionsInput = { + export type UserUncheckedUpdateWithoutAuthoredModelsInput = { id?: StringFieldUpdateOperationsInput | string - legacyId?: NullableIntFieldUpdateOperationsInput | number | null - name?: StringFieldUpdateOperationsInput | string - displayName?: NullableStringFieldUpdateOperationsInput | string | null + name?: NullableStringFieldUpdateOperationsInput | string | null + email?: NullableStringFieldUpdateOperationsInput | string | null + emailVerified?: BoolFieldUpdateOperationsInput | boolean + image?: NullableStringFieldUpdateOperationsInput | string | null createdAt?: DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string + systemRole?: EnumSystemRoleFieldUpdateOperationsInput | $Enums.SystemRole + userKind?: EnumUserKindFieldUpdateOperationsInput | $Enums.UserKind + isProfilePublic?: BoolFieldUpdateOperationsInput | boolean + deletedAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + bio?: NullableStringFieldUpdateOperationsInput | string | null + country?: NullableStringFieldUpdateOperationsInput | string | null + socialLinks?: NullableJsonNullValueInput | InputJsonValue + dob?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + affiliation?: NullableStringFieldUpdateOperationsInput | string | null + role?: NullableStringFieldUpdateOperationsInput | string | null + banned?: NullableBoolFieldUpdateOperationsInput | boolean | null + banReason?: NullableStringFieldUpdateOperationsInput | string | null + banExpires?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + onboardedAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + legacyId?: NullableIntFieldUpdateOperationsInput | number | null + accounts?: AccountUncheckedUpdateManyWithoutUserNestedInput + sessions?: SessionUncheckedUpdateManyWithoutUserNestedInput + verifications?: VerificationUncheckedUpdateManyWithoutUserNestedInput + grantedPermissions?: ModelPermissionUncheckedUpdateManyWithoutGranteeUserNestedInput + events?: EventUncheckedUpdateManyWithoutActorNestedInput + modelLikes?: ModelLikeUncheckedUpdateManyWithoutUserNestedInput + modelInteractions?: ModelInteractionUncheckedUpdateManyWithoutUserNestedInput + modelDrafts?: ModelDraftUncheckedUpdateManyWithoutUserNestedInput + comments?: ModelCommentUncheckedUpdateManyWithoutUserNestedInput + commentLikes?: ModelCommentLikeUncheckedUpdateManyWithoutUserNestedInput + passkeys?: PasskeyUncheckedUpdateManyWithoutUserNestedInput } - export type ModelCreateWithoutAdditionalFilesInput = { + export type ModelCreateWithoutPermissionsInput = { legacyId?: number | null visibility?: $Enums.ModelVisibility isEndorsed?: boolean @@ -30208,13 +34383,14 @@ export namespace Prisma { parentVersion?: ModelVersionCreateNestedOneWithoutParentOfModelsInput versions?: ModelVersionCreateNestedManyWithoutModelInput authors?: ModelAuthorCreateNestedManyWithoutModelInput - permissions?: ModelPermissionCreateNestedManyWithoutModelInput + additionalFiles?: ModelAdditionalFileCreateNestedManyWithoutModelInput likes?: ModelLikeCreateNestedManyWithoutModelInput interactions?: ModelInteractionCreateNestedManyWithoutModelInput drafts?: ModelDraftCreateNestedManyWithoutModelInput + comments?: ModelCommentCreateNestedManyWithoutModelInput } - export type ModelUncheckedCreateWithoutAdditionalFilesInput = { + export type ModelUncheckedCreateWithoutPermissionsInput = { id?: string legacyId?: number | null latestVersionNumber?: number | null @@ -30233,68 +34409,107 @@ export namespace Prisma { childModels?: ModelUncheckedCreateNestedManyWithoutParentModelInput versions?: ModelVersionUncheckedCreateNestedManyWithoutModelInput authors?: ModelAuthorUncheckedCreateNestedManyWithoutModelInput - permissions?: ModelPermissionUncheckedCreateNestedManyWithoutModelInput + additionalFiles?: ModelAdditionalFileUncheckedCreateNestedManyWithoutModelInput likes?: ModelLikeUncheckedCreateNestedManyWithoutModelInput interactions?: ModelInteractionUncheckedCreateNestedManyWithoutModelInput drafts?: ModelDraftUncheckedCreateNestedManyWithoutModelInput + comments?: ModelCommentUncheckedCreateNestedManyWithoutModelInput } - export type ModelCreateOrConnectWithoutAdditionalFilesInput = { + export type ModelCreateOrConnectWithoutPermissionsInput = { where: ModelWhereUniqueInput - create: XOR + create: XOR } - export type ModelVersionCreateWithoutTaggedAdditionalFilesInput = { - versionNumber: number - title: string - description?: string | null - changeSummary?: string | null - previewImageFileKey?: string | null - netlogoFileKey: string - netlogoVersion?: string | null - infoTab?: string | null + export type UserCreateWithoutGrantedPermissionsInput = { + id?: string + name?: string | null + email?: string | null + emailVerified?: boolean + image?: string | null createdAt?: Date | string - finalizedAt?: Date | string | null - model: ModelCreateNestedOneWithoutVersionsInput - latestOfModel?: ModelCreateNestedOneWithoutLatestVersionInput - parentOfModels?: ModelCreateNestedManyWithoutParentVersionInput - tags?: ModelVersionTagCreateNestedManyWithoutModelVersionInput + updatedAt?: Date | string + systemRole?: $Enums.SystemRole + userKind?: $Enums.UserKind + isProfilePublic?: boolean + deletedAt?: Date | string | null + bio?: string | null + country?: string | null + socialLinks?: NullableJsonNullValueInput | InputJsonValue + dob?: Date | string | null + affiliation?: string | null + role?: string | null + banned?: boolean | null + banReason?: string | null + banExpires?: Date | string | null + onboardedAt?: Date | string | null + legacyId?: number | null + accounts?: AccountCreateNestedManyWithoutUserInput + sessions?: SessionCreateNestedManyWithoutUserInput + verifications?: VerificationCreateNestedManyWithoutUserInput + authoredModels?: ModelAuthorCreateNestedManyWithoutUserInput + events?: EventCreateNestedManyWithoutActorInput + modelLikes?: ModelLikeCreateNestedManyWithoutUserInput + modelInteractions?: ModelInteractionCreateNestedManyWithoutUserInput + modelDrafts?: ModelDraftCreateNestedManyWithoutUserInput + comments?: ModelCommentCreateNestedManyWithoutUserInput + commentLikes?: ModelCommentLikeCreateNestedManyWithoutUserInput + passkeys?: PasskeyCreateNestedManyWithoutUserInput } - export type ModelVersionUncheckedCreateWithoutTaggedAdditionalFilesInput = { - modelId: string - versionNumber: number - title: string - description?: string | null - changeSummary?: string | null - previewImageFileKey?: string | null - netlogoFileKey: string - netlogoVersion?: string | null - infoTab?: string | null + export type UserUncheckedCreateWithoutGrantedPermissionsInput = { + id?: string + name?: string | null + email?: string | null + emailVerified?: boolean + image?: string | null createdAt?: Date | string - finalizedAt?: Date | string | null - latestOfModel?: ModelUncheckedCreateNestedOneWithoutLatestVersionInput - parentOfModels?: ModelUncheckedCreateNestedManyWithoutParentVersionInput - tags?: ModelVersionTagUncheckedCreateNestedManyWithoutModelVersionInput + updatedAt?: Date | string + systemRole?: $Enums.SystemRole + userKind?: $Enums.UserKind + isProfilePublic?: boolean + deletedAt?: Date | string | null + bio?: string | null + country?: string | null + socialLinks?: NullableJsonNullValueInput | InputJsonValue + dob?: Date | string | null + affiliation?: string | null + role?: string | null + banned?: boolean | null + banReason?: string | null + banExpires?: Date | string | null + onboardedAt?: Date | string | null + legacyId?: number | null + accounts?: AccountUncheckedCreateNestedManyWithoutUserInput + sessions?: SessionUncheckedCreateNestedManyWithoutUserInput + verifications?: VerificationUncheckedCreateNestedManyWithoutUserInput + authoredModels?: ModelAuthorUncheckedCreateNestedManyWithoutUserInput + events?: EventUncheckedCreateNestedManyWithoutActorInput + modelLikes?: ModelLikeUncheckedCreateNestedManyWithoutUserInput + modelInteractions?: ModelInteractionUncheckedCreateNestedManyWithoutUserInput + modelDrafts?: ModelDraftUncheckedCreateNestedManyWithoutUserInput + comments?: ModelCommentUncheckedCreateNestedManyWithoutUserInput + commentLikes?: ModelCommentLikeUncheckedCreateNestedManyWithoutUserInput + passkeys?: PasskeyUncheckedCreateNestedManyWithoutUserInput } - export type ModelVersionCreateOrConnectWithoutTaggedAdditionalFilesInput = { - where: ModelVersionWhereUniqueInput - create: XOR + export type UserCreateOrConnectWithoutGrantedPermissionsInput = { + where: UserWhereUniqueInput + create: XOR } - export type ModelUpsertWithoutAdditionalFilesInput = { - update: XOR - create: XOR + export type ModelUpsertWithoutPermissionsInput = { + update: XOR + create: XOR where?: ModelWhereInput } - export type ModelUpdateToOneWithWhereWithoutAdditionalFilesInput = { + export type ModelUpdateToOneWithWhereWithoutPermissionsInput = { where?: ModelWhereInput - data: XOR + data: XOR } - export type ModelUpdateWithoutAdditionalFilesInput = { + export type ModelUpdateWithoutPermissionsInput = { legacyId?: NullableIntFieldUpdateOperationsInput | number | null visibility?: EnumModelVisibilityFieldUpdateOperationsInput | $Enums.ModelVisibility isEndorsed?: BoolFieldUpdateOperationsInput | boolean @@ -30312,13 +34527,14 @@ export namespace Prisma { parentVersion?: ModelVersionUpdateOneWithoutParentOfModelsNestedInput versions?: ModelVersionUpdateManyWithoutModelNestedInput authors?: ModelAuthorUpdateManyWithoutModelNestedInput - permissions?: ModelPermissionUpdateManyWithoutModelNestedInput + additionalFiles?: ModelAdditionalFileUpdateManyWithoutModelNestedInput likes?: ModelLikeUpdateManyWithoutModelNestedInput interactions?: ModelInteractionUpdateManyWithoutModelNestedInput drafts?: ModelDraftUpdateManyWithoutModelNestedInput + comments?: ModelCommentUpdateManyWithoutModelNestedInput } - export type ModelUncheckedUpdateWithoutAdditionalFilesInput = { + export type ModelUncheckedUpdateWithoutPermissionsInput = { id?: StringFieldUpdateOperationsInput | string legacyId?: NullableIntFieldUpdateOperationsInput | number | null latestVersionNumber?: NullableIntFieldUpdateOperationsInput | number | null @@ -30337,95 +34553,97 @@ export namespace Prisma { childModels?: ModelUncheckedUpdateManyWithoutParentModelNestedInput versions?: ModelVersionUncheckedUpdateManyWithoutModelNestedInput authors?: ModelAuthorUncheckedUpdateManyWithoutModelNestedInput - permissions?: ModelPermissionUncheckedUpdateManyWithoutModelNestedInput + additionalFiles?: ModelAdditionalFileUncheckedUpdateManyWithoutModelNestedInput likes?: ModelLikeUncheckedUpdateManyWithoutModelNestedInput interactions?: ModelInteractionUncheckedUpdateManyWithoutModelNestedInput drafts?: ModelDraftUncheckedUpdateManyWithoutModelNestedInput + comments?: ModelCommentUncheckedUpdateManyWithoutModelNestedInput } - export type ModelVersionUpsertWithoutTaggedAdditionalFilesInput = { - update: XOR - create: XOR - where?: ModelVersionWhereInput + export type UserUpsertWithoutGrantedPermissionsInput = { + update: XOR + create: XOR + where?: UserWhereInput } - export type ModelVersionUpdateToOneWithWhereWithoutTaggedAdditionalFilesInput = { - where?: ModelVersionWhereInput - data: XOR + export type UserUpdateToOneWithWhereWithoutGrantedPermissionsInput = { + where?: UserWhereInput + data: XOR } - export type ModelVersionUpdateWithoutTaggedAdditionalFilesInput = { - versionNumber?: IntFieldUpdateOperationsInput | number - title?: StringFieldUpdateOperationsInput | string - description?: NullableStringFieldUpdateOperationsInput | string | null - changeSummary?: NullableStringFieldUpdateOperationsInput | string | null - previewImageFileKey?: NullableStringFieldUpdateOperationsInput | string | null - netlogoFileKey?: StringFieldUpdateOperationsInput | string - netlogoVersion?: NullableStringFieldUpdateOperationsInput | string | null - infoTab?: NullableStringFieldUpdateOperationsInput | string | null + export type UserUpdateWithoutGrantedPermissionsInput = { + id?: StringFieldUpdateOperationsInput | string + name?: NullableStringFieldUpdateOperationsInput | string | null + email?: NullableStringFieldUpdateOperationsInput | string | null + emailVerified?: BoolFieldUpdateOperationsInput | boolean + image?: NullableStringFieldUpdateOperationsInput | string | null createdAt?: DateTimeFieldUpdateOperationsInput | Date | string - finalizedAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null - model?: ModelUpdateOneRequiredWithoutVersionsNestedInput - latestOfModel?: ModelUpdateOneWithoutLatestVersionNestedInput - parentOfModels?: ModelUpdateManyWithoutParentVersionNestedInput - tags?: ModelVersionTagUpdateManyWithoutModelVersionNestedInput + updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string + systemRole?: EnumSystemRoleFieldUpdateOperationsInput | $Enums.SystemRole + userKind?: EnumUserKindFieldUpdateOperationsInput | $Enums.UserKind + isProfilePublic?: BoolFieldUpdateOperationsInput | boolean + deletedAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + bio?: NullableStringFieldUpdateOperationsInput | string | null + country?: NullableStringFieldUpdateOperationsInput | string | null + socialLinks?: NullableJsonNullValueInput | InputJsonValue + dob?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + affiliation?: NullableStringFieldUpdateOperationsInput | string | null + role?: NullableStringFieldUpdateOperationsInput | string | null + banned?: NullableBoolFieldUpdateOperationsInput | boolean | null + banReason?: NullableStringFieldUpdateOperationsInput | string | null + banExpires?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + onboardedAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + legacyId?: NullableIntFieldUpdateOperationsInput | number | null + accounts?: AccountUpdateManyWithoutUserNestedInput + sessions?: SessionUpdateManyWithoutUserNestedInput + verifications?: VerificationUpdateManyWithoutUserNestedInput + authoredModels?: ModelAuthorUpdateManyWithoutUserNestedInput + events?: EventUpdateManyWithoutActorNestedInput + modelLikes?: ModelLikeUpdateManyWithoutUserNestedInput + modelInteractions?: ModelInteractionUpdateManyWithoutUserNestedInput + modelDrafts?: ModelDraftUpdateManyWithoutUserNestedInput + comments?: ModelCommentUpdateManyWithoutUserNestedInput + commentLikes?: ModelCommentLikeUpdateManyWithoutUserNestedInput + passkeys?: PasskeyUpdateManyWithoutUserNestedInput } - export type ModelVersionUncheckedUpdateWithoutTaggedAdditionalFilesInput = { - modelId?: StringFieldUpdateOperationsInput | string - versionNumber?: IntFieldUpdateOperationsInput | number - title?: StringFieldUpdateOperationsInput | string - description?: NullableStringFieldUpdateOperationsInput | string | null - changeSummary?: NullableStringFieldUpdateOperationsInput | string | null - previewImageFileKey?: NullableStringFieldUpdateOperationsInput | string | null - netlogoFileKey?: StringFieldUpdateOperationsInput | string - netlogoVersion?: NullableStringFieldUpdateOperationsInput | string | null - infoTab?: NullableStringFieldUpdateOperationsInput | string | null + export type UserUncheckedUpdateWithoutGrantedPermissionsInput = { + id?: StringFieldUpdateOperationsInput | string + name?: NullableStringFieldUpdateOperationsInput | string | null + email?: NullableStringFieldUpdateOperationsInput | string | null + emailVerified?: BoolFieldUpdateOperationsInput | boolean + image?: NullableStringFieldUpdateOperationsInput | string | null createdAt?: DateTimeFieldUpdateOperationsInput | Date | string - finalizedAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null - latestOfModel?: ModelUncheckedUpdateOneWithoutLatestVersionNestedInput - parentOfModels?: ModelUncheckedUpdateManyWithoutParentVersionNestedInput - tags?: ModelVersionTagUncheckedUpdateManyWithoutModelVersionNestedInput - } - - export type ModelVersionTagCreateWithoutTagInput = { - createdAt?: Date | string - modelVersion: ModelVersionCreateNestedOneWithoutTagsInput - } - - export type ModelVersionTagUncheckedCreateWithoutTagInput = { - modelId: string - versionNumber: number - createdAt?: Date | string - } - - export type ModelVersionTagCreateOrConnectWithoutTagInput = { - where: ModelVersionTagWhereUniqueInput - create: XOR - } - - export type ModelVersionTagCreateManyTagInputEnvelope = { - data: ModelVersionTagCreateManyTagInput | ModelVersionTagCreateManyTagInput[] - skipDuplicates?: boolean - } - - export type ModelVersionTagUpsertWithWhereUniqueWithoutTagInput = { - where: ModelVersionTagWhereUniqueInput - update: XOR - create: XOR - } - - export type ModelVersionTagUpdateWithWhereUniqueWithoutTagInput = { - where: ModelVersionTagWhereUniqueInput - data: XOR - } - - export type ModelVersionTagUpdateManyWithWhereWithoutTagInput = { - where: ModelVersionTagScalarWhereInput - data: XOR + updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string + systemRole?: EnumSystemRoleFieldUpdateOperationsInput | $Enums.SystemRole + userKind?: EnumUserKindFieldUpdateOperationsInput | $Enums.UserKind + isProfilePublic?: BoolFieldUpdateOperationsInput | boolean + deletedAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + bio?: NullableStringFieldUpdateOperationsInput | string | null + country?: NullableStringFieldUpdateOperationsInput | string | null + socialLinks?: NullableJsonNullValueInput | InputJsonValue + dob?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + affiliation?: NullableStringFieldUpdateOperationsInput | string | null + role?: NullableStringFieldUpdateOperationsInput | string | null + banned?: NullableBoolFieldUpdateOperationsInput | boolean | null + banReason?: NullableStringFieldUpdateOperationsInput | string | null + banExpires?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + onboardedAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + legacyId?: NullableIntFieldUpdateOperationsInput | number | null + accounts?: AccountUncheckedUpdateManyWithoutUserNestedInput + sessions?: SessionUncheckedUpdateManyWithoutUserNestedInput + verifications?: VerificationUncheckedUpdateManyWithoutUserNestedInput + authoredModels?: ModelAuthorUncheckedUpdateManyWithoutUserNestedInput + events?: EventUncheckedUpdateManyWithoutActorNestedInput + modelLikes?: ModelLikeUncheckedUpdateManyWithoutUserNestedInput + modelInteractions?: ModelInteractionUncheckedUpdateManyWithoutUserNestedInput + modelDrafts?: ModelDraftUncheckedUpdateManyWithoutUserNestedInput + comments?: ModelCommentUncheckedUpdateManyWithoutUserNestedInput + commentLikes?: ModelCommentLikeUncheckedUpdateManyWithoutUserNestedInput + passkeys?: PasskeyUncheckedUpdateManyWithoutUserNestedInput } - export type ModelCreateWithoutAuthorsInput = { + export type ModelCreateWithoutLikesInput = { legacyId?: number | null visibility?: $Enums.ModelVisibility isEndorsed?: boolean @@ -30442,14 +34660,15 @@ export namespace Prisma { childModels?: ModelCreateNestedManyWithoutParentModelInput parentVersion?: ModelVersionCreateNestedOneWithoutParentOfModelsInput versions?: ModelVersionCreateNestedManyWithoutModelInput + authors?: ModelAuthorCreateNestedManyWithoutModelInput permissions?: ModelPermissionCreateNestedManyWithoutModelInput additionalFiles?: ModelAdditionalFileCreateNestedManyWithoutModelInput - likes?: ModelLikeCreateNestedManyWithoutModelInput interactions?: ModelInteractionCreateNestedManyWithoutModelInput drafts?: ModelDraftCreateNestedManyWithoutModelInput + comments?: ModelCommentCreateNestedManyWithoutModelInput } - export type ModelUncheckedCreateWithoutAuthorsInput = { + export type ModelUncheckedCreateWithoutLikesInput = { id?: string legacyId?: number | null latestVersionNumber?: number | null @@ -30467,19 +34686,20 @@ export namespace Prisma { deletedAt?: Date | string | null childModels?: ModelUncheckedCreateNestedManyWithoutParentModelInput versions?: ModelVersionUncheckedCreateNestedManyWithoutModelInput + authors?: ModelAuthorUncheckedCreateNestedManyWithoutModelInput permissions?: ModelPermissionUncheckedCreateNestedManyWithoutModelInput additionalFiles?: ModelAdditionalFileUncheckedCreateNestedManyWithoutModelInput - likes?: ModelLikeUncheckedCreateNestedManyWithoutModelInput interactions?: ModelInteractionUncheckedCreateNestedManyWithoutModelInput drafts?: ModelDraftUncheckedCreateNestedManyWithoutModelInput + comments?: ModelCommentUncheckedCreateNestedManyWithoutModelInput } - export type ModelCreateOrConnectWithoutAuthorsInput = { + export type ModelCreateOrConnectWithoutLikesInput = { where: ModelWhereUniqueInput - create: XOR + create: XOR } - export type UserCreateWithoutAuthoredModelsInput = { + export type UserCreateWithoutModelLikesInput = { id?: string name?: string | null email?: string | null @@ -30505,15 +34725,17 @@ export namespace Prisma { accounts?: AccountCreateNestedManyWithoutUserInput sessions?: SessionCreateNestedManyWithoutUserInput verifications?: VerificationCreateNestedManyWithoutUserInput + authoredModels?: ModelAuthorCreateNestedManyWithoutUserInput grantedPermissions?: ModelPermissionCreateNestedManyWithoutGranteeUserInput events?: EventCreateNestedManyWithoutActorInput - modelLikes?: ModelLikeCreateNestedManyWithoutUserInput modelInteractions?: ModelInteractionCreateNestedManyWithoutUserInput modelDrafts?: ModelDraftCreateNestedManyWithoutUserInput + comments?: ModelCommentCreateNestedManyWithoutUserInput + commentLikes?: ModelCommentLikeCreateNestedManyWithoutUserInput passkeys?: PasskeyCreateNestedManyWithoutUserInput } - export type UserUncheckedCreateWithoutAuthoredModelsInput = { + export type UserUncheckedCreateWithoutModelLikesInput = { id?: string name?: string | null email?: string | null @@ -30539,31 +34761,33 @@ export namespace Prisma { accounts?: AccountUncheckedCreateNestedManyWithoutUserInput sessions?: SessionUncheckedCreateNestedManyWithoutUserInput verifications?: VerificationUncheckedCreateNestedManyWithoutUserInput + authoredModels?: ModelAuthorUncheckedCreateNestedManyWithoutUserInput grantedPermissions?: ModelPermissionUncheckedCreateNestedManyWithoutGranteeUserInput events?: EventUncheckedCreateNestedManyWithoutActorInput - modelLikes?: ModelLikeUncheckedCreateNestedManyWithoutUserInput modelInteractions?: ModelInteractionUncheckedCreateNestedManyWithoutUserInput modelDrafts?: ModelDraftUncheckedCreateNestedManyWithoutUserInput + comments?: ModelCommentUncheckedCreateNestedManyWithoutUserInput + commentLikes?: ModelCommentLikeUncheckedCreateNestedManyWithoutUserInput passkeys?: PasskeyUncheckedCreateNestedManyWithoutUserInput } - export type UserCreateOrConnectWithoutAuthoredModelsInput = { + export type UserCreateOrConnectWithoutModelLikesInput = { where: UserWhereUniqueInput - create: XOR + create: XOR } - export type ModelUpsertWithoutAuthorsInput = { - update: XOR - create: XOR + export type ModelUpsertWithoutLikesInput = { + update: XOR + create: XOR where?: ModelWhereInput } - export type ModelUpdateToOneWithWhereWithoutAuthorsInput = { + export type ModelUpdateToOneWithWhereWithoutLikesInput = { where?: ModelWhereInput - data: XOR + data: XOR } - export type ModelUpdateWithoutAuthorsInput = { + export type ModelUpdateWithoutLikesInput = { legacyId?: NullableIntFieldUpdateOperationsInput | number | null visibility?: EnumModelVisibilityFieldUpdateOperationsInput | $Enums.ModelVisibility isEndorsed?: BoolFieldUpdateOperationsInput | boolean @@ -30580,14 +34804,15 @@ export namespace Prisma { childModels?: ModelUpdateManyWithoutParentModelNestedInput parentVersion?: ModelVersionUpdateOneWithoutParentOfModelsNestedInput versions?: ModelVersionUpdateManyWithoutModelNestedInput + authors?: ModelAuthorUpdateManyWithoutModelNestedInput permissions?: ModelPermissionUpdateManyWithoutModelNestedInput additionalFiles?: ModelAdditionalFileUpdateManyWithoutModelNestedInput - likes?: ModelLikeUpdateManyWithoutModelNestedInput interactions?: ModelInteractionUpdateManyWithoutModelNestedInput drafts?: ModelDraftUpdateManyWithoutModelNestedInput + comments?: ModelCommentUpdateManyWithoutModelNestedInput } - export type ModelUncheckedUpdateWithoutAuthorsInput = { + export type ModelUncheckedUpdateWithoutLikesInput = { id?: StringFieldUpdateOperationsInput | string legacyId?: NullableIntFieldUpdateOperationsInput | number | null latestVersionNumber?: NullableIntFieldUpdateOperationsInput | number | null @@ -30605,25 +34830,26 @@ export namespace Prisma { deletedAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null childModels?: ModelUncheckedUpdateManyWithoutParentModelNestedInput versions?: ModelVersionUncheckedUpdateManyWithoutModelNestedInput + authors?: ModelAuthorUncheckedUpdateManyWithoutModelNestedInput permissions?: ModelPermissionUncheckedUpdateManyWithoutModelNestedInput additionalFiles?: ModelAdditionalFileUncheckedUpdateManyWithoutModelNestedInput - likes?: ModelLikeUncheckedUpdateManyWithoutModelNestedInput interactions?: ModelInteractionUncheckedUpdateManyWithoutModelNestedInput drafts?: ModelDraftUncheckedUpdateManyWithoutModelNestedInput + comments?: ModelCommentUncheckedUpdateManyWithoutModelNestedInput } - export type UserUpsertWithoutAuthoredModelsInput = { - update: XOR - create: XOR + export type UserUpsertWithoutModelLikesInput = { + update: XOR + create: XOR where?: UserWhereInput } - export type UserUpdateToOneWithWhereWithoutAuthoredModelsInput = { + export type UserUpdateToOneWithWhereWithoutModelLikesInput = { where?: UserWhereInput - data: XOR + data: XOR } - export type UserUpdateWithoutAuthoredModelsInput = { + export type UserUpdateWithoutModelLikesInput = { id?: StringFieldUpdateOperationsInput | string name?: NullableStringFieldUpdateOperationsInput | string | null email?: NullableStringFieldUpdateOperationsInput | string | null @@ -30649,15 +34875,17 @@ export namespace Prisma { accounts?: AccountUpdateManyWithoutUserNestedInput sessions?: SessionUpdateManyWithoutUserNestedInput verifications?: VerificationUpdateManyWithoutUserNestedInput + authoredModels?: ModelAuthorUpdateManyWithoutUserNestedInput grantedPermissions?: ModelPermissionUpdateManyWithoutGranteeUserNestedInput events?: EventUpdateManyWithoutActorNestedInput - modelLikes?: ModelLikeUpdateManyWithoutUserNestedInput modelInteractions?: ModelInteractionUpdateManyWithoutUserNestedInput modelDrafts?: ModelDraftUpdateManyWithoutUserNestedInput + comments?: ModelCommentUpdateManyWithoutUserNestedInput + commentLikes?: ModelCommentLikeUpdateManyWithoutUserNestedInput passkeys?: PasskeyUpdateManyWithoutUserNestedInput } - export type UserUncheckedUpdateWithoutAuthoredModelsInput = { + export type UserUncheckedUpdateWithoutModelLikesInput = { id?: StringFieldUpdateOperationsInput | string name?: NullableStringFieldUpdateOperationsInput | string | null email?: NullableStringFieldUpdateOperationsInput | string | null @@ -30683,15 +34911,17 @@ export namespace Prisma { accounts?: AccountUncheckedUpdateManyWithoutUserNestedInput sessions?: SessionUncheckedUpdateManyWithoutUserNestedInput verifications?: VerificationUncheckedUpdateManyWithoutUserNestedInput + authoredModels?: ModelAuthorUncheckedUpdateManyWithoutUserNestedInput grantedPermissions?: ModelPermissionUncheckedUpdateManyWithoutGranteeUserNestedInput events?: EventUncheckedUpdateManyWithoutActorNestedInput - modelLikes?: ModelLikeUncheckedUpdateManyWithoutUserNestedInput modelInteractions?: ModelInteractionUncheckedUpdateManyWithoutUserNestedInput modelDrafts?: ModelDraftUncheckedUpdateManyWithoutUserNestedInput + comments?: ModelCommentUncheckedUpdateManyWithoutUserNestedInput + commentLikes?: ModelCommentLikeUncheckedUpdateManyWithoutUserNestedInput passkeys?: PasskeyUncheckedUpdateManyWithoutUserNestedInput } - export type ModelCreateWithoutPermissionsInput = { + export type ModelCreateWithoutInteractionsInput = { legacyId?: number | null visibility?: $Enums.ModelVisibility isEndorsed?: boolean @@ -30709,13 +34939,14 @@ export namespace Prisma { parentVersion?: ModelVersionCreateNestedOneWithoutParentOfModelsInput versions?: ModelVersionCreateNestedManyWithoutModelInput authors?: ModelAuthorCreateNestedManyWithoutModelInput + permissions?: ModelPermissionCreateNestedManyWithoutModelInput additionalFiles?: ModelAdditionalFileCreateNestedManyWithoutModelInput likes?: ModelLikeCreateNestedManyWithoutModelInput - interactions?: ModelInteractionCreateNestedManyWithoutModelInput drafts?: ModelDraftCreateNestedManyWithoutModelInput + comments?: ModelCommentCreateNestedManyWithoutModelInput } - export type ModelUncheckedCreateWithoutPermissionsInput = { + export type ModelUncheckedCreateWithoutInteractionsInput = { id?: string legacyId?: number | null latestVersionNumber?: number | null @@ -30734,18 +34965,19 @@ export namespace Prisma { childModels?: ModelUncheckedCreateNestedManyWithoutParentModelInput versions?: ModelVersionUncheckedCreateNestedManyWithoutModelInput authors?: ModelAuthorUncheckedCreateNestedManyWithoutModelInput + permissions?: ModelPermissionUncheckedCreateNestedManyWithoutModelInput additionalFiles?: ModelAdditionalFileUncheckedCreateNestedManyWithoutModelInput likes?: ModelLikeUncheckedCreateNestedManyWithoutModelInput - interactions?: ModelInteractionUncheckedCreateNestedManyWithoutModelInput drafts?: ModelDraftUncheckedCreateNestedManyWithoutModelInput + comments?: ModelCommentUncheckedCreateNestedManyWithoutModelInput } - export type ModelCreateOrConnectWithoutPermissionsInput = { + export type ModelCreateOrConnectWithoutInteractionsInput = { where: ModelWhereUniqueInput - create: XOR + create: XOR } - export type UserCreateWithoutGrantedPermissionsInput = { + export type UserCreateWithoutModelInteractionsInput = { id?: string name?: string | null email?: string | null @@ -30772,14 +35004,16 @@ export namespace Prisma { sessions?: SessionCreateNestedManyWithoutUserInput verifications?: VerificationCreateNestedManyWithoutUserInput authoredModels?: ModelAuthorCreateNestedManyWithoutUserInput + grantedPermissions?: ModelPermissionCreateNestedManyWithoutGranteeUserInput events?: EventCreateNestedManyWithoutActorInput modelLikes?: ModelLikeCreateNestedManyWithoutUserInput - modelInteractions?: ModelInteractionCreateNestedManyWithoutUserInput modelDrafts?: ModelDraftCreateNestedManyWithoutUserInput + comments?: ModelCommentCreateNestedManyWithoutUserInput + commentLikes?: ModelCommentLikeCreateNestedManyWithoutUserInput passkeys?: PasskeyCreateNestedManyWithoutUserInput } - export type UserUncheckedCreateWithoutGrantedPermissionsInput = { + export type UserUncheckedCreateWithoutModelInteractionsInput = { id?: string name?: string | null email?: string | null @@ -30806,30 +35040,32 @@ export namespace Prisma { sessions?: SessionUncheckedCreateNestedManyWithoutUserInput verifications?: VerificationUncheckedCreateNestedManyWithoutUserInput authoredModels?: ModelAuthorUncheckedCreateNestedManyWithoutUserInput + grantedPermissions?: ModelPermissionUncheckedCreateNestedManyWithoutGranteeUserInput events?: EventUncheckedCreateNestedManyWithoutActorInput modelLikes?: ModelLikeUncheckedCreateNestedManyWithoutUserInput - modelInteractions?: ModelInteractionUncheckedCreateNestedManyWithoutUserInput modelDrafts?: ModelDraftUncheckedCreateNestedManyWithoutUserInput + comments?: ModelCommentUncheckedCreateNestedManyWithoutUserInput + commentLikes?: ModelCommentLikeUncheckedCreateNestedManyWithoutUserInput passkeys?: PasskeyUncheckedCreateNestedManyWithoutUserInput } - export type UserCreateOrConnectWithoutGrantedPermissionsInput = { + export type UserCreateOrConnectWithoutModelInteractionsInput = { where: UserWhereUniqueInput - create: XOR + create: XOR } - export type ModelUpsertWithoutPermissionsInput = { - update: XOR - create: XOR + export type ModelUpsertWithoutInteractionsInput = { + update: XOR + create: XOR where?: ModelWhereInput } - export type ModelUpdateToOneWithWhereWithoutPermissionsInput = { + export type ModelUpdateToOneWithWhereWithoutInteractionsInput = { where?: ModelWhereInput - data: XOR + data: XOR } - export type ModelUpdateWithoutPermissionsInput = { + export type ModelUpdateWithoutInteractionsInput = { legacyId?: NullableIntFieldUpdateOperationsInput | number | null visibility?: EnumModelVisibilityFieldUpdateOperationsInput | $Enums.ModelVisibility isEndorsed?: BoolFieldUpdateOperationsInput | boolean @@ -30847,13 +35083,14 @@ export namespace Prisma { parentVersion?: ModelVersionUpdateOneWithoutParentOfModelsNestedInput versions?: ModelVersionUpdateManyWithoutModelNestedInput authors?: ModelAuthorUpdateManyWithoutModelNestedInput + permissions?: ModelPermissionUpdateManyWithoutModelNestedInput additionalFiles?: ModelAdditionalFileUpdateManyWithoutModelNestedInput likes?: ModelLikeUpdateManyWithoutModelNestedInput - interactions?: ModelInteractionUpdateManyWithoutModelNestedInput drafts?: ModelDraftUpdateManyWithoutModelNestedInput + comments?: ModelCommentUpdateManyWithoutModelNestedInput } - export type ModelUncheckedUpdateWithoutPermissionsInput = { + export type ModelUncheckedUpdateWithoutInteractionsInput = { id?: StringFieldUpdateOperationsInput | string legacyId?: NullableIntFieldUpdateOperationsInput | number | null latestVersionNumber?: NullableIntFieldUpdateOperationsInput | number | null @@ -30872,24 +35109,25 @@ export namespace Prisma { childModels?: ModelUncheckedUpdateManyWithoutParentModelNestedInput versions?: ModelVersionUncheckedUpdateManyWithoutModelNestedInput authors?: ModelAuthorUncheckedUpdateManyWithoutModelNestedInput + permissions?: ModelPermissionUncheckedUpdateManyWithoutModelNestedInput additionalFiles?: ModelAdditionalFileUncheckedUpdateManyWithoutModelNestedInput likes?: ModelLikeUncheckedUpdateManyWithoutModelNestedInput - interactions?: ModelInteractionUncheckedUpdateManyWithoutModelNestedInput drafts?: ModelDraftUncheckedUpdateManyWithoutModelNestedInput + comments?: ModelCommentUncheckedUpdateManyWithoutModelNestedInput } - export type UserUpsertWithoutGrantedPermissionsInput = { - update: XOR - create: XOR + export type UserUpsertWithoutModelInteractionsInput = { + update: XOR + create: XOR where?: UserWhereInput } - export type UserUpdateToOneWithWhereWithoutGrantedPermissionsInput = { + export type UserUpdateToOneWithWhereWithoutModelInteractionsInput = { where?: UserWhereInput - data: XOR + data: XOR } - export type UserUpdateWithoutGrantedPermissionsInput = { + export type UserUpdateWithoutModelInteractionsInput = { id?: StringFieldUpdateOperationsInput | string name?: NullableStringFieldUpdateOperationsInput | string | null email?: NullableStringFieldUpdateOperationsInput | string | null @@ -30916,14 +35154,16 @@ export namespace Prisma { sessions?: SessionUpdateManyWithoutUserNestedInput verifications?: VerificationUpdateManyWithoutUserNestedInput authoredModels?: ModelAuthorUpdateManyWithoutUserNestedInput + grantedPermissions?: ModelPermissionUpdateManyWithoutGranteeUserNestedInput events?: EventUpdateManyWithoutActorNestedInput modelLikes?: ModelLikeUpdateManyWithoutUserNestedInput - modelInteractions?: ModelInteractionUpdateManyWithoutUserNestedInput modelDrafts?: ModelDraftUpdateManyWithoutUserNestedInput + comments?: ModelCommentUpdateManyWithoutUserNestedInput + commentLikes?: ModelCommentLikeUpdateManyWithoutUserNestedInput passkeys?: PasskeyUpdateManyWithoutUserNestedInput } - export type UserUncheckedUpdateWithoutGrantedPermissionsInput = { + export type UserUncheckedUpdateWithoutModelInteractionsInput = { id?: StringFieldUpdateOperationsInput | string name?: NullableStringFieldUpdateOperationsInput | string | null email?: NullableStringFieldUpdateOperationsInput | string | null @@ -30950,68 +35190,16 @@ export namespace Prisma { sessions?: SessionUncheckedUpdateManyWithoutUserNestedInput verifications?: VerificationUncheckedUpdateManyWithoutUserNestedInput authoredModels?: ModelAuthorUncheckedUpdateManyWithoutUserNestedInput + grantedPermissions?: ModelPermissionUncheckedUpdateManyWithoutGranteeUserNestedInput events?: EventUncheckedUpdateManyWithoutActorNestedInput modelLikes?: ModelLikeUncheckedUpdateManyWithoutUserNestedInput - modelInteractions?: ModelInteractionUncheckedUpdateManyWithoutUserNestedInput modelDrafts?: ModelDraftUncheckedUpdateManyWithoutUserNestedInput + comments?: ModelCommentUncheckedUpdateManyWithoutUserNestedInput + commentLikes?: ModelCommentLikeUncheckedUpdateManyWithoutUserNestedInput passkeys?: PasskeyUncheckedUpdateManyWithoutUserNestedInput } - export type ModelCreateWithoutLikesInput = { - legacyId?: number | null - visibility?: $Enums.ModelVisibility - isEndorsed?: boolean - isLibraryModel?: boolean - viewCount?: number - runCount?: number - downloadCount?: number - shareCount?: number - createdAt?: Date | string - updatedAt?: Date | string - deletedAt?: Date | string | null - latestVersion?: ModelVersionCreateNestedOneWithoutLatestOfModelInput - parentModel?: ModelCreateNestedOneWithoutChildModelsInput - childModels?: ModelCreateNestedManyWithoutParentModelInput - parentVersion?: ModelVersionCreateNestedOneWithoutParentOfModelsInput - versions?: ModelVersionCreateNestedManyWithoutModelInput - authors?: ModelAuthorCreateNestedManyWithoutModelInput - permissions?: ModelPermissionCreateNestedManyWithoutModelInput - additionalFiles?: ModelAdditionalFileCreateNestedManyWithoutModelInput - interactions?: ModelInteractionCreateNestedManyWithoutModelInput - drafts?: ModelDraftCreateNestedManyWithoutModelInput - } - - export type ModelUncheckedCreateWithoutLikesInput = { - id?: string - legacyId?: number | null - latestVersionNumber?: number | null - parentModelId?: string | null - parentVersionNumber?: number | null - visibility?: $Enums.ModelVisibility - isEndorsed?: boolean - isLibraryModel?: boolean - viewCount?: number - runCount?: number - downloadCount?: number - shareCount?: number - createdAt?: Date | string - updatedAt?: Date | string - deletedAt?: Date | string | null - childModels?: ModelUncheckedCreateNestedManyWithoutParentModelInput - versions?: ModelVersionUncheckedCreateNestedManyWithoutModelInput - authors?: ModelAuthorUncheckedCreateNestedManyWithoutModelInput - permissions?: ModelPermissionUncheckedCreateNestedManyWithoutModelInput - additionalFiles?: ModelAdditionalFileUncheckedCreateNestedManyWithoutModelInput - interactions?: ModelInteractionUncheckedCreateNestedManyWithoutModelInput - drafts?: ModelDraftUncheckedCreateNestedManyWithoutModelInput - } - - export type ModelCreateOrConnectWithoutLikesInput = { - where: ModelWhereUniqueInput - create: XOR - } - - export type UserCreateWithoutModelLikesInput = { + export type UserCreateWithoutModelDraftsInput = { id?: string name?: string | null email?: string | null @@ -31040,12 +35228,14 @@ export namespace Prisma { authoredModels?: ModelAuthorCreateNestedManyWithoutUserInput grantedPermissions?: ModelPermissionCreateNestedManyWithoutGranteeUserInput events?: EventCreateNestedManyWithoutActorInput + modelLikes?: ModelLikeCreateNestedManyWithoutUserInput modelInteractions?: ModelInteractionCreateNestedManyWithoutUserInput - modelDrafts?: ModelDraftCreateNestedManyWithoutUserInput + comments?: ModelCommentCreateNestedManyWithoutUserInput + commentLikes?: ModelCommentLikeCreateNestedManyWithoutUserInput passkeys?: PasskeyCreateNestedManyWithoutUserInput } - export type UserUncheckedCreateWithoutModelLikesInput = { + export type UserUncheckedCreateWithoutModelDraftsInput = { id?: string name?: string | null email?: string | null @@ -31074,88 +35264,86 @@ export namespace Prisma { authoredModels?: ModelAuthorUncheckedCreateNestedManyWithoutUserInput grantedPermissions?: ModelPermissionUncheckedCreateNestedManyWithoutGranteeUserInput events?: EventUncheckedCreateNestedManyWithoutActorInput + modelLikes?: ModelLikeUncheckedCreateNestedManyWithoutUserInput modelInteractions?: ModelInteractionUncheckedCreateNestedManyWithoutUserInput - modelDrafts?: ModelDraftUncheckedCreateNestedManyWithoutUserInput + comments?: ModelCommentUncheckedCreateNestedManyWithoutUserInput + commentLikes?: ModelCommentLikeUncheckedCreateNestedManyWithoutUserInput passkeys?: PasskeyUncheckedCreateNestedManyWithoutUserInput } - export type UserCreateOrConnectWithoutModelLikesInput = { + export type UserCreateOrConnectWithoutModelDraftsInput = { where: UserWhereUniqueInput - create: XOR - } - - export type ModelUpsertWithoutLikesInput = { - update: XOR - create: XOR - where?: ModelWhereInput + create: XOR } - export type ModelUpdateToOneWithWhereWithoutLikesInput = { - where?: ModelWhereInput - data: XOR + export type ModelCreateWithoutDraftsInput = { + legacyId?: number | null + visibility?: $Enums.ModelVisibility + isEndorsed?: boolean + isLibraryModel?: boolean + viewCount?: number + runCount?: number + downloadCount?: number + shareCount?: number + createdAt?: Date | string + updatedAt?: Date | string + deletedAt?: Date | string | null + latestVersion?: ModelVersionCreateNestedOneWithoutLatestOfModelInput + parentModel?: ModelCreateNestedOneWithoutChildModelsInput + childModels?: ModelCreateNestedManyWithoutParentModelInput + parentVersion?: ModelVersionCreateNestedOneWithoutParentOfModelsInput + versions?: ModelVersionCreateNestedManyWithoutModelInput + authors?: ModelAuthorCreateNestedManyWithoutModelInput + permissions?: ModelPermissionCreateNestedManyWithoutModelInput + additionalFiles?: ModelAdditionalFileCreateNestedManyWithoutModelInput + likes?: ModelLikeCreateNestedManyWithoutModelInput + interactions?: ModelInteractionCreateNestedManyWithoutModelInput + comments?: ModelCommentCreateNestedManyWithoutModelInput } - export type ModelUpdateWithoutLikesInput = { - legacyId?: NullableIntFieldUpdateOperationsInput | number | null - visibility?: EnumModelVisibilityFieldUpdateOperationsInput | $Enums.ModelVisibility - isEndorsed?: BoolFieldUpdateOperationsInput | boolean - isLibraryModel?: BoolFieldUpdateOperationsInput | boolean - viewCount?: IntFieldUpdateOperationsInput | number - runCount?: IntFieldUpdateOperationsInput | number - downloadCount?: IntFieldUpdateOperationsInput | number - shareCount?: IntFieldUpdateOperationsInput | number - createdAt?: DateTimeFieldUpdateOperationsInput | Date | string - updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string - deletedAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null - latestVersion?: ModelVersionUpdateOneWithoutLatestOfModelNestedInput - parentModel?: ModelUpdateOneWithoutChildModelsNestedInput - childModels?: ModelUpdateManyWithoutParentModelNestedInput - parentVersion?: ModelVersionUpdateOneWithoutParentOfModelsNestedInput - versions?: ModelVersionUpdateManyWithoutModelNestedInput - authors?: ModelAuthorUpdateManyWithoutModelNestedInput - permissions?: ModelPermissionUpdateManyWithoutModelNestedInput - additionalFiles?: ModelAdditionalFileUpdateManyWithoutModelNestedInput - interactions?: ModelInteractionUpdateManyWithoutModelNestedInput - drafts?: ModelDraftUpdateManyWithoutModelNestedInput + export type ModelUncheckedCreateWithoutDraftsInput = { + id?: string + legacyId?: number | null + latestVersionNumber?: number | null + parentModelId?: string | null + parentVersionNumber?: number | null + visibility?: $Enums.ModelVisibility + isEndorsed?: boolean + isLibraryModel?: boolean + viewCount?: number + runCount?: number + downloadCount?: number + shareCount?: number + createdAt?: Date | string + updatedAt?: Date | string + deletedAt?: Date | string | null + childModels?: ModelUncheckedCreateNestedManyWithoutParentModelInput + versions?: ModelVersionUncheckedCreateNestedManyWithoutModelInput + authors?: ModelAuthorUncheckedCreateNestedManyWithoutModelInput + permissions?: ModelPermissionUncheckedCreateNestedManyWithoutModelInput + additionalFiles?: ModelAdditionalFileUncheckedCreateNestedManyWithoutModelInput + likes?: ModelLikeUncheckedCreateNestedManyWithoutModelInput + interactions?: ModelInteractionUncheckedCreateNestedManyWithoutModelInput + comments?: ModelCommentUncheckedCreateNestedManyWithoutModelInput } - export type ModelUncheckedUpdateWithoutLikesInput = { - id?: StringFieldUpdateOperationsInput | string - legacyId?: NullableIntFieldUpdateOperationsInput | number | null - latestVersionNumber?: NullableIntFieldUpdateOperationsInput | number | null - parentModelId?: NullableStringFieldUpdateOperationsInput | string | null - parentVersionNumber?: NullableIntFieldUpdateOperationsInput | number | null - visibility?: EnumModelVisibilityFieldUpdateOperationsInput | $Enums.ModelVisibility - isEndorsed?: BoolFieldUpdateOperationsInput | boolean - isLibraryModel?: BoolFieldUpdateOperationsInput | boolean - viewCount?: IntFieldUpdateOperationsInput | number - runCount?: IntFieldUpdateOperationsInput | number - downloadCount?: IntFieldUpdateOperationsInput | number - shareCount?: IntFieldUpdateOperationsInput | number - createdAt?: DateTimeFieldUpdateOperationsInput | Date | string - updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string - deletedAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null - childModels?: ModelUncheckedUpdateManyWithoutParentModelNestedInput - versions?: ModelVersionUncheckedUpdateManyWithoutModelNestedInput - authors?: ModelAuthorUncheckedUpdateManyWithoutModelNestedInput - permissions?: ModelPermissionUncheckedUpdateManyWithoutModelNestedInput - additionalFiles?: ModelAdditionalFileUncheckedUpdateManyWithoutModelNestedInput - interactions?: ModelInteractionUncheckedUpdateManyWithoutModelNestedInput - drafts?: ModelDraftUncheckedUpdateManyWithoutModelNestedInput + export type ModelCreateOrConnectWithoutDraftsInput = { + where: ModelWhereUniqueInput + create: XOR } - export type UserUpsertWithoutModelLikesInput = { - update: XOR - create: XOR + export type UserUpsertWithoutModelDraftsInput = { + update: XOR + create: XOR where?: UserWhereInput } - export type UserUpdateToOneWithWhereWithoutModelLikesInput = { + export type UserUpdateToOneWithWhereWithoutModelDraftsInput = { where?: UserWhereInput - data: XOR + data: XOR } - export type UserUpdateWithoutModelLikesInput = { + export type UserUpdateWithoutModelDraftsInput = { id?: StringFieldUpdateOperationsInput | string name?: NullableStringFieldUpdateOperationsInput | string | null email?: NullableStringFieldUpdateOperationsInput | string | null @@ -31184,12 +35372,14 @@ export namespace Prisma { authoredModels?: ModelAuthorUpdateManyWithoutUserNestedInput grantedPermissions?: ModelPermissionUpdateManyWithoutGranteeUserNestedInput events?: EventUpdateManyWithoutActorNestedInput + modelLikes?: ModelLikeUpdateManyWithoutUserNestedInput modelInteractions?: ModelInteractionUpdateManyWithoutUserNestedInput - modelDrafts?: ModelDraftUpdateManyWithoutUserNestedInput + comments?: ModelCommentUpdateManyWithoutUserNestedInput + commentLikes?: ModelCommentLikeUpdateManyWithoutUserNestedInput passkeys?: PasskeyUpdateManyWithoutUserNestedInput } - export type UserUncheckedUpdateWithoutModelLikesInput = { + export type UserUncheckedUpdateWithoutModelDraftsInput = { id?: StringFieldUpdateOperationsInput | string name?: NullableStringFieldUpdateOperationsInput | string | null email?: NullableStringFieldUpdateOperationsInput | string | null @@ -31218,12 +35408,76 @@ export namespace Prisma { authoredModels?: ModelAuthorUncheckedUpdateManyWithoutUserNestedInput grantedPermissions?: ModelPermissionUncheckedUpdateManyWithoutGranteeUserNestedInput events?: EventUncheckedUpdateManyWithoutActorNestedInput + modelLikes?: ModelLikeUncheckedUpdateManyWithoutUserNestedInput modelInteractions?: ModelInteractionUncheckedUpdateManyWithoutUserNestedInput - modelDrafts?: ModelDraftUncheckedUpdateManyWithoutUserNestedInput + comments?: ModelCommentUncheckedUpdateManyWithoutUserNestedInput + commentLikes?: ModelCommentLikeUncheckedUpdateManyWithoutUserNestedInput passkeys?: PasskeyUncheckedUpdateManyWithoutUserNestedInput } - export type ModelCreateWithoutInteractionsInput = { + export type ModelUpsertWithoutDraftsInput = { + update: XOR + create: XOR + where?: ModelWhereInput + } + + export type ModelUpdateToOneWithWhereWithoutDraftsInput = { + where?: ModelWhereInput + data: XOR + } + + export type ModelUpdateWithoutDraftsInput = { + legacyId?: NullableIntFieldUpdateOperationsInput | number | null + visibility?: EnumModelVisibilityFieldUpdateOperationsInput | $Enums.ModelVisibility + isEndorsed?: BoolFieldUpdateOperationsInput | boolean + isLibraryModel?: BoolFieldUpdateOperationsInput | boolean + viewCount?: IntFieldUpdateOperationsInput | number + runCount?: IntFieldUpdateOperationsInput | number + downloadCount?: IntFieldUpdateOperationsInput | number + shareCount?: IntFieldUpdateOperationsInput | number + createdAt?: DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string + deletedAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + latestVersion?: ModelVersionUpdateOneWithoutLatestOfModelNestedInput + parentModel?: ModelUpdateOneWithoutChildModelsNestedInput + childModels?: ModelUpdateManyWithoutParentModelNestedInput + parentVersion?: ModelVersionUpdateOneWithoutParentOfModelsNestedInput + versions?: ModelVersionUpdateManyWithoutModelNestedInput + authors?: ModelAuthorUpdateManyWithoutModelNestedInput + permissions?: ModelPermissionUpdateManyWithoutModelNestedInput + additionalFiles?: ModelAdditionalFileUpdateManyWithoutModelNestedInput + likes?: ModelLikeUpdateManyWithoutModelNestedInput + interactions?: ModelInteractionUpdateManyWithoutModelNestedInput + comments?: ModelCommentUpdateManyWithoutModelNestedInput + } + + export type ModelUncheckedUpdateWithoutDraftsInput = { + id?: StringFieldUpdateOperationsInput | string + legacyId?: NullableIntFieldUpdateOperationsInput | number | null + latestVersionNumber?: NullableIntFieldUpdateOperationsInput | number | null + parentModelId?: NullableStringFieldUpdateOperationsInput | string | null + parentVersionNumber?: NullableIntFieldUpdateOperationsInput | number | null + visibility?: EnumModelVisibilityFieldUpdateOperationsInput | $Enums.ModelVisibility + isEndorsed?: BoolFieldUpdateOperationsInput | boolean + isLibraryModel?: BoolFieldUpdateOperationsInput | boolean + viewCount?: IntFieldUpdateOperationsInput | number + runCount?: IntFieldUpdateOperationsInput | number + downloadCount?: IntFieldUpdateOperationsInput | number + shareCount?: IntFieldUpdateOperationsInput | number + createdAt?: DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string + deletedAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + childModels?: ModelUncheckedUpdateManyWithoutParentModelNestedInput + versions?: ModelVersionUncheckedUpdateManyWithoutModelNestedInput + authors?: ModelAuthorUncheckedUpdateManyWithoutModelNestedInput + permissions?: ModelPermissionUncheckedUpdateManyWithoutModelNestedInput + additionalFiles?: ModelAdditionalFileUncheckedUpdateManyWithoutModelNestedInput + likes?: ModelLikeUncheckedUpdateManyWithoutModelNestedInput + interactions?: ModelInteractionUncheckedUpdateManyWithoutModelNestedInput + comments?: ModelCommentUncheckedUpdateManyWithoutModelNestedInput + } + + export type ModelCreateWithoutCommentsInput = { legacyId?: number | null visibility?: $Enums.ModelVisibility isEndorsed?: boolean @@ -31244,10 +35498,11 @@ export namespace Prisma { permissions?: ModelPermissionCreateNestedManyWithoutModelInput additionalFiles?: ModelAdditionalFileCreateNestedManyWithoutModelInput likes?: ModelLikeCreateNestedManyWithoutModelInput + interactions?: ModelInteractionCreateNestedManyWithoutModelInput drafts?: ModelDraftCreateNestedManyWithoutModelInput } - export type ModelUncheckedCreateWithoutInteractionsInput = { + export type ModelUncheckedCreateWithoutCommentsInput = { id?: string legacyId?: number | null latestVersionNumber?: number | null @@ -31269,15 +35524,16 @@ export namespace Prisma { permissions?: ModelPermissionUncheckedCreateNestedManyWithoutModelInput additionalFiles?: ModelAdditionalFileUncheckedCreateNestedManyWithoutModelInput likes?: ModelLikeUncheckedCreateNestedManyWithoutModelInput + interactions?: ModelInteractionUncheckedCreateNestedManyWithoutModelInput drafts?: ModelDraftUncheckedCreateNestedManyWithoutModelInput } - export type ModelCreateOrConnectWithoutInteractionsInput = { + export type ModelCreateOrConnectWithoutCommentsInput = { where: ModelWhereUniqueInput - create: XOR + create: XOR } - export type UserCreateWithoutModelInteractionsInput = { + export type UserCreateWithoutCommentsInput = { id?: string name?: string | null email?: string | null @@ -31307,11 +35563,13 @@ export namespace Prisma { grantedPermissions?: ModelPermissionCreateNestedManyWithoutGranteeUserInput events?: EventCreateNestedManyWithoutActorInput modelLikes?: ModelLikeCreateNestedManyWithoutUserInput + modelInteractions?: ModelInteractionCreateNestedManyWithoutUserInput modelDrafts?: ModelDraftCreateNestedManyWithoutUserInput + commentLikes?: ModelCommentLikeCreateNestedManyWithoutUserInput passkeys?: PasskeyCreateNestedManyWithoutUserInput } - export type UserUncheckedCreateWithoutModelInteractionsInput = { + export type UserUncheckedCreateWithoutCommentsInput = { id?: string name?: string | null email?: string | null @@ -31341,27 +35599,128 @@ export namespace Prisma { grantedPermissions?: ModelPermissionUncheckedCreateNestedManyWithoutGranteeUserInput events?: EventUncheckedCreateNestedManyWithoutActorInput modelLikes?: ModelLikeUncheckedCreateNestedManyWithoutUserInput + modelInteractions?: ModelInteractionUncheckedCreateNestedManyWithoutUserInput modelDrafts?: ModelDraftUncheckedCreateNestedManyWithoutUserInput + commentLikes?: ModelCommentLikeUncheckedCreateNestedManyWithoutUserInput passkeys?: PasskeyUncheckedCreateNestedManyWithoutUserInput } - export type UserCreateOrConnectWithoutModelInteractionsInput = { + export type UserCreateOrConnectWithoutCommentsInput = { where: UserWhereUniqueInput - create: XOR + create: XOR } - export type ModelUpsertWithoutInteractionsInput = { - update: XOR - create: XOR + export type ModelCommentCreateWithoutRepliesInput = { + id?: string + legacyId?: number | null + versionNumber?: number | null + content?: string | null + likesCount?: number + createdAt?: Date | string + updatedAt?: Date | string + editedAt?: Date | string | null + deletedAt?: Date | string | null + model: ModelCreateNestedOneWithoutCommentsInput + user?: UserCreateNestedOneWithoutCommentsInput + parent?: ModelCommentCreateNestedOneWithoutRepliesInput + likes?: ModelCommentLikeCreateNestedManyWithoutModelCommentInput + } + + export type ModelCommentUncheckedCreateWithoutRepliesInput = { + id?: string + legacyId?: number | null + parentId?: string | null + userId?: string | null + modelId: string + versionNumber?: number | null + content?: string | null + likesCount?: number + createdAt?: Date | string + updatedAt?: Date | string + editedAt?: Date | string | null + deletedAt?: Date | string | null + likes?: ModelCommentLikeUncheckedCreateNestedManyWithoutModelCommentInput + } + + export type ModelCommentCreateOrConnectWithoutRepliesInput = { + where: ModelCommentWhereUniqueInput + create: XOR + } + + export type ModelCommentCreateWithoutParentInput = { + id?: string + legacyId?: number | null + versionNumber?: number | null + content?: string | null + likesCount?: number + createdAt?: Date | string + updatedAt?: Date | string + editedAt?: Date | string | null + deletedAt?: Date | string | null + model: ModelCreateNestedOneWithoutCommentsInput + user?: UserCreateNestedOneWithoutCommentsInput + replies?: ModelCommentCreateNestedManyWithoutParentInput + likes?: ModelCommentLikeCreateNestedManyWithoutModelCommentInput + } + + export type ModelCommentUncheckedCreateWithoutParentInput = { + id?: string + legacyId?: number | null + userId?: string | null + modelId: string + versionNumber?: number | null + content?: string | null + likesCount?: number + createdAt?: Date | string + updatedAt?: Date | string + editedAt?: Date | string | null + deletedAt?: Date | string | null + replies?: ModelCommentUncheckedCreateNestedManyWithoutParentInput + likes?: ModelCommentLikeUncheckedCreateNestedManyWithoutModelCommentInput + } + + export type ModelCommentCreateOrConnectWithoutParentInput = { + where: ModelCommentWhereUniqueInput + create: XOR + } + + export type ModelCommentCreateManyParentInputEnvelope = { + data: ModelCommentCreateManyParentInput | ModelCommentCreateManyParentInput[] + skipDuplicates?: boolean + } + + export type ModelCommentLikeCreateWithoutModelCommentInput = { + createdAt?: Date | string + user: UserCreateNestedOneWithoutCommentLikesInput + } + + export type ModelCommentLikeUncheckedCreateWithoutModelCommentInput = { + userId: string + createdAt?: Date | string + } + + export type ModelCommentLikeCreateOrConnectWithoutModelCommentInput = { + where: ModelCommentLikeWhereUniqueInput + create: XOR + } + + export type ModelCommentLikeCreateManyModelCommentInputEnvelope = { + data: ModelCommentLikeCreateManyModelCommentInput | ModelCommentLikeCreateManyModelCommentInput[] + skipDuplicates?: boolean + } + + export type ModelUpsertWithoutCommentsInput = { + update: XOR + create: XOR where?: ModelWhereInput } - export type ModelUpdateToOneWithWhereWithoutInteractionsInput = { + export type ModelUpdateToOneWithWhereWithoutCommentsInput = { where?: ModelWhereInput - data: XOR + data: XOR } - export type ModelUpdateWithoutInteractionsInput = { + export type ModelUpdateWithoutCommentsInput = { legacyId?: NullableIntFieldUpdateOperationsInput | number | null visibility?: EnumModelVisibilityFieldUpdateOperationsInput | $Enums.ModelVisibility isEndorsed?: BoolFieldUpdateOperationsInput | boolean @@ -31382,10 +35741,11 @@ export namespace Prisma { permissions?: ModelPermissionUpdateManyWithoutModelNestedInput additionalFiles?: ModelAdditionalFileUpdateManyWithoutModelNestedInput likes?: ModelLikeUpdateManyWithoutModelNestedInput + interactions?: ModelInteractionUpdateManyWithoutModelNestedInput drafts?: ModelDraftUpdateManyWithoutModelNestedInput } - export type ModelUncheckedUpdateWithoutInteractionsInput = { + export type ModelUncheckedUpdateWithoutCommentsInput = { id?: StringFieldUpdateOperationsInput | string legacyId?: NullableIntFieldUpdateOperationsInput | number | null latestVersionNumber?: NullableIntFieldUpdateOperationsInput | number | null @@ -31407,21 +35767,22 @@ export namespace Prisma { permissions?: ModelPermissionUncheckedUpdateManyWithoutModelNestedInput additionalFiles?: ModelAdditionalFileUncheckedUpdateManyWithoutModelNestedInput likes?: ModelLikeUncheckedUpdateManyWithoutModelNestedInput + interactions?: ModelInteractionUncheckedUpdateManyWithoutModelNestedInput drafts?: ModelDraftUncheckedUpdateManyWithoutModelNestedInput } - export type UserUpsertWithoutModelInteractionsInput = { - update: XOR - create: XOR + export type UserUpsertWithoutCommentsInput = { + update: XOR + create: XOR where?: UserWhereInput } - export type UserUpdateToOneWithWhereWithoutModelInteractionsInput = { + export type UserUpdateToOneWithWhereWithoutCommentsInput = { where?: UserWhereInput - data: XOR + data: XOR } - export type UserUpdateWithoutModelInteractionsInput = { + export type UserUpdateWithoutCommentsInput = { id?: StringFieldUpdateOperationsInput | string name?: NullableStringFieldUpdateOperationsInput | string | null email?: NullableStringFieldUpdateOperationsInput | string | null @@ -31451,11 +35812,13 @@ export namespace Prisma { grantedPermissions?: ModelPermissionUpdateManyWithoutGranteeUserNestedInput events?: EventUpdateManyWithoutActorNestedInput modelLikes?: ModelLikeUpdateManyWithoutUserNestedInput + modelInteractions?: ModelInteractionUpdateManyWithoutUserNestedInput modelDrafts?: ModelDraftUpdateManyWithoutUserNestedInput + commentLikes?: ModelCommentLikeUpdateManyWithoutUserNestedInput passkeys?: PasskeyUpdateManyWithoutUserNestedInput } - export type UserUncheckedUpdateWithoutModelInteractionsInput = { + export type UserUncheckedUpdateWithoutCommentsInput = { id?: StringFieldUpdateOperationsInput | string name?: NullableStringFieldUpdateOperationsInput | string | null email?: NullableStringFieldUpdateOperationsInput | string | null @@ -31485,11 +35848,125 @@ export namespace Prisma { grantedPermissions?: ModelPermissionUncheckedUpdateManyWithoutGranteeUserNestedInput events?: EventUncheckedUpdateManyWithoutActorNestedInput modelLikes?: ModelLikeUncheckedUpdateManyWithoutUserNestedInput + modelInteractions?: ModelInteractionUncheckedUpdateManyWithoutUserNestedInput modelDrafts?: ModelDraftUncheckedUpdateManyWithoutUserNestedInput + commentLikes?: ModelCommentLikeUncheckedUpdateManyWithoutUserNestedInput passkeys?: PasskeyUncheckedUpdateManyWithoutUserNestedInput } - export type UserCreateWithoutModelDraftsInput = { + export type ModelCommentUpsertWithoutRepliesInput = { + update: XOR + create: XOR + where?: ModelCommentWhereInput + } + + export type ModelCommentUpdateToOneWithWhereWithoutRepliesInput = { + where?: ModelCommentWhereInput + data: XOR + } + + export type ModelCommentUpdateWithoutRepliesInput = { + id?: StringFieldUpdateOperationsInput | string + legacyId?: NullableIntFieldUpdateOperationsInput | number | null + versionNumber?: NullableIntFieldUpdateOperationsInput | number | null + content?: NullableStringFieldUpdateOperationsInput | string | null + likesCount?: IntFieldUpdateOperationsInput | number + createdAt?: DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string + editedAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + deletedAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + model?: ModelUpdateOneRequiredWithoutCommentsNestedInput + user?: UserUpdateOneWithoutCommentsNestedInput + parent?: ModelCommentUpdateOneWithoutRepliesNestedInput + likes?: ModelCommentLikeUpdateManyWithoutModelCommentNestedInput + } + + export type ModelCommentUncheckedUpdateWithoutRepliesInput = { + id?: StringFieldUpdateOperationsInput | string + legacyId?: NullableIntFieldUpdateOperationsInput | number | null + parentId?: NullableStringFieldUpdateOperationsInput | string | null + userId?: NullableStringFieldUpdateOperationsInput | string | null + modelId?: StringFieldUpdateOperationsInput | string + versionNumber?: NullableIntFieldUpdateOperationsInput | number | null + content?: NullableStringFieldUpdateOperationsInput | string | null + likesCount?: IntFieldUpdateOperationsInput | number + createdAt?: DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string + editedAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + deletedAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + likes?: ModelCommentLikeUncheckedUpdateManyWithoutModelCommentNestedInput + } + + export type ModelCommentUpsertWithWhereUniqueWithoutParentInput = { + where: ModelCommentWhereUniqueInput + update: XOR + create: XOR + } + + export type ModelCommentUpdateWithWhereUniqueWithoutParentInput = { + where: ModelCommentWhereUniqueInput + data: XOR + } + + export type ModelCommentUpdateManyWithWhereWithoutParentInput = { + where: ModelCommentScalarWhereInput + data: XOR + } + + export type ModelCommentLikeUpsertWithWhereUniqueWithoutModelCommentInput = { + where: ModelCommentLikeWhereUniqueInput + update: XOR + create: XOR + } + + export type ModelCommentLikeUpdateWithWhereUniqueWithoutModelCommentInput = { + where: ModelCommentLikeWhereUniqueInput + data: XOR + } + + export type ModelCommentLikeUpdateManyWithWhereWithoutModelCommentInput = { + where: ModelCommentLikeScalarWhereInput + data: XOR + } + + export type ModelCommentCreateWithoutLikesInput = { + id?: string + legacyId?: number | null + versionNumber?: number | null + content?: string | null + likesCount?: number + createdAt?: Date | string + updatedAt?: Date | string + editedAt?: Date | string | null + deletedAt?: Date | string | null + model: ModelCreateNestedOneWithoutCommentsInput + user?: UserCreateNestedOneWithoutCommentsInput + parent?: ModelCommentCreateNestedOneWithoutRepliesInput + replies?: ModelCommentCreateNestedManyWithoutParentInput + } + + export type ModelCommentUncheckedCreateWithoutLikesInput = { + id?: string + legacyId?: number | null + parentId?: string | null + userId?: string | null + modelId: string + versionNumber?: number | null + content?: string | null + likesCount?: number + createdAt?: Date | string + updatedAt?: Date | string + editedAt?: Date | string | null + deletedAt?: Date | string | null + replies?: ModelCommentUncheckedCreateNestedManyWithoutParentInput + } + + export type ModelCommentCreateOrConnectWithoutLikesInput = { + where: ModelCommentWhereUniqueInput + create: XOR + } + + export type UserCreateWithoutCommentLikesInput = { id?: string name?: string | null email?: string | null @@ -31520,10 +35997,12 @@ export namespace Prisma { events?: EventCreateNestedManyWithoutActorInput modelLikes?: ModelLikeCreateNestedManyWithoutUserInput modelInteractions?: ModelInteractionCreateNestedManyWithoutUserInput + modelDrafts?: ModelDraftCreateNestedManyWithoutUserInput + comments?: ModelCommentCreateNestedManyWithoutUserInput passkeys?: PasskeyCreateNestedManyWithoutUserInput } - export type UserUncheckedCreateWithoutModelDraftsInput = { + export type UserUncheckedCreateWithoutCommentLikesInput = { id?: string name?: string | null email?: string | null @@ -31554,80 +36033,71 @@ export namespace Prisma { events?: EventUncheckedCreateNestedManyWithoutActorInput modelLikes?: ModelLikeUncheckedCreateNestedManyWithoutUserInput modelInteractions?: ModelInteractionUncheckedCreateNestedManyWithoutUserInput + modelDrafts?: ModelDraftUncheckedCreateNestedManyWithoutUserInput + comments?: ModelCommentUncheckedCreateNestedManyWithoutUserInput passkeys?: PasskeyUncheckedCreateNestedManyWithoutUserInput } - export type UserCreateOrConnectWithoutModelDraftsInput = { + export type UserCreateOrConnectWithoutCommentLikesInput = { where: UserWhereUniqueInput - create: XOR + create: XOR } - export type ModelCreateWithoutDraftsInput = { - legacyId?: number | null - visibility?: $Enums.ModelVisibility - isEndorsed?: boolean - isLibraryModel?: boolean - viewCount?: number - runCount?: number - downloadCount?: number - shareCount?: number - createdAt?: Date | string - updatedAt?: Date | string - deletedAt?: Date | string | null - latestVersion?: ModelVersionCreateNestedOneWithoutLatestOfModelInput - parentModel?: ModelCreateNestedOneWithoutChildModelsInput - childModels?: ModelCreateNestedManyWithoutParentModelInput - parentVersion?: ModelVersionCreateNestedOneWithoutParentOfModelsInput - versions?: ModelVersionCreateNestedManyWithoutModelInput - authors?: ModelAuthorCreateNestedManyWithoutModelInput - permissions?: ModelPermissionCreateNestedManyWithoutModelInput - additionalFiles?: ModelAdditionalFileCreateNestedManyWithoutModelInput - likes?: ModelLikeCreateNestedManyWithoutModelInput - interactions?: ModelInteractionCreateNestedManyWithoutModelInput + export type ModelCommentUpsertWithoutLikesInput = { + update: XOR + create: XOR + where?: ModelCommentWhereInput } - export type ModelUncheckedCreateWithoutDraftsInput = { - id?: string - legacyId?: number | null - latestVersionNumber?: number | null - parentModelId?: string | null - parentVersionNumber?: number | null - visibility?: $Enums.ModelVisibility - isEndorsed?: boolean - isLibraryModel?: boolean - viewCount?: number - runCount?: number - downloadCount?: number - shareCount?: number - createdAt?: Date | string - updatedAt?: Date | string - deletedAt?: Date | string | null - childModels?: ModelUncheckedCreateNestedManyWithoutParentModelInput - versions?: ModelVersionUncheckedCreateNestedManyWithoutModelInput - authors?: ModelAuthorUncheckedCreateNestedManyWithoutModelInput - permissions?: ModelPermissionUncheckedCreateNestedManyWithoutModelInput - additionalFiles?: ModelAdditionalFileUncheckedCreateNestedManyWithoutModelInput - likes?: ModelLikeUncheckedCreateNestedManyWithoutModelInput - interactions?: ModelInteractionUncheckedCreateNestedManyWithoutModelInput + export type ModelCommentUpdateToOneWithWhereWithoutLikesInput = { + where?: ModelCommentWhereInput + data: XOR } - export type ModelCreateOrConnectWithoutDraftsInput = { - where: ModelWhereUniqueInput - create: XOR + export type ModelCommentUpdateWithoutLikesInput = { + id?: StringFieldUpdateOperationsInput | string + legacyId?: NullableIntFieldUpdateOperationsInput | number | null + versionNumber?: NullableIntFieldUpdateOperationsInput | number | null + content?: NullableStringFieldUpdateOperationsInput | string | null + likesCount?: IntFieldUpdateOperationsInput | number + createdAt?: DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string + editedAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + deletedAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + model?: ModelUpdateOneRequiredWithoutCommentsNestedInput + user?: UserUpdateOneWithoutCommentsNestedInput + parent?: ModelCommentUpdateOneWithoutRepliesNestedInput + replies?: ModelCommentUpdateManyWithoutParentNestedInput } - export type UserUpsertWithoutModelDraftsInput = { - update: XOR - create: XOR + export type ModelCommentUncheckedUpdateWithoutLikesInput = { + id?: StringFieldUpdateOperationsInput | string + legacyId?: NullableIntFieldUpdateOperationsInput | number | null + parentId?: NullableStringFieldUpdateOperationsInput | string | null + userId?: NullableStringFieldUpdateOperationsInput | string | null + modelId?: StringFieldUpdateOperationsInput | string + versionNumber?: NullableIntFieldUpdateOperationsInput | number | null + content?: NullableStringFieldUpdateOperationsInput | string | null + likesCount?: IntFieldUpdateOperationsInput | number + createdAt?: DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string + editedAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + deletedAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + replies?: ModelCommentUncheckedUpdateManyWithoutParentNestedInput + } + + export type UserUpsertWithoutCommentLikesInput = { + update: XOR + create: XOR where?: UserWhereInput } - export type UserUpdateToOneWithWhereWithoutModelDraftsInput = { + export type UserUpdateToOneWithWhereWithoutCommentLikesInput = { where?: UserWhereInput - data: XOR + data: XOR } - export type UserUpdateWithoutModelDraftsInput = { + export type UserUpdateWithoutCommentLikesInput = { id?: StringFieldUpdateOperationsInput | string name?: NullableStringFieldUpdateOperationsInput | string | null email?: NullableStringFieldUpdateOperationsInput | string | null @@ -31658,10 +36128,12 @@ export namespace Prisma { events?: EventUpdateManyWithoutActorNestedInput modelLikes?: ModelLikeUpdateManyWithoutUserNestedInput modelInteractions?: ModelInteractionUpdateManyWithoutUserNestedInput + modelDrafts?: ModelDraftUpdateManyWithoutUserNestedInput + comments?: ModelCommentUpdateManyWithoutUserNestedInput passkeys?: PasskeyUpdateManyWithoutUserNestedInput } - export type UserUncheckedUpdateWithoutModelDraftsInput = { + export type UserUncheckedUpdateWithoutCommentLikesInput = { id?: StringFieldUpdateOperationsInput | string name?: NullableStringFieldUpdateOperationsInput | string | null email?: NullableStringFieldUpdateOperationsInput | string | null @@ -31692,69 +36164,11 @@ export namespace Prisma { events?: EventUncheckedUpdateManyWithoutActorNestedInput modelLikes?: ModelLikeUncheckedUpdateManyWithoutUserNestedInput modelInteractions?: ModelInteractionUncheckedUpdateManyWithoutUserNestedInput + modelDrafts?: ModelDraftUncheckedUpdateManyWithoutUserNestedInput + comments?: ModelCommentUncheckedUpdateManyWithoutUserNestedInput passkeys?: PasskeyUncheckedUpdateManyWithoutUserNestedInput } - export type ModelUpsertWithoutDraftsInput = { - update: XOR - create: XOR - where?: ModelWhereInput - } - - export type ModelUpdateToOneWithWhereWithoutDraftsInput = { - where?: ModelWhereInput - data: XOR - } - - export type ModelUpdateWithoutDraftsInput = { - legacyId?: NullableIntFieldUpdateOperationsInput | number | null - visibility?: EnumModelVisibilityFieldUpdateOperationsInput | $Enums.ModelVisibility - isEndorsed?: BoolFieldUpdateOperationsInput | boolean - isLibraryModel?: BoolFieldUpdateOperationsInput | boolean - viewCount?: IntFieldUpdateOperationsInput | number - runCount?: IntFieldUpdateOperationsInput | number - downloadCount?: IntFieldUpdateOperationsInput | number - shareCount?: IntFieldUpdateOperationsInput | number - createdAt?: DateTimeFieldUpdateOperationsInput | Date | string - updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string - deletedAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null - latestVersion?: ModelVersionUpdateOneWithoutLatestOfModelNestedInput - parentModel?: ModelUpdateOneWithoutChildModelsNestedInput - childModels?: ModelUpdateManyWithoutParentModelNestedInput - parentVersion?: ModelVersionUpdateOneWithoutParentOfModelsNestedInput - versions?: ModelVersionUpdateManyWithoutModelNestedInput - authors?: ModelAuthorUpdateManyWithoutModelNestedInput - permissions?: ModelPermissionUpdateManyWithoutModelNestedInput - additionalFiles?: ModelAdditionalFileUpdateManyWithoutModelNestedInput - likes?: ModelLikeUpdateManyWithoutModelNestedInput - interactions?: ModelInteractionUpdateManyWithoutModelNestedInput - } - - export type ModelUncheckedUpdateWithoutDraftsInput = { - id?: StringFieldUpdateOperationsInput | string - legacyId?: NullableIntFieldUpdateOperationsInput | number | null - latestVersionNumber?: NullableIntFieldUpdateOperationsInput | number | null - parentModelId?: NullableStringFieldUpdateOperationsInput | string | null - parentVersionNumber?: NullableIntFieldUpdateOperationsInput | number | null - visibility?: EnumModelVisibilityFieldUpdateOperationsInput | $Enums.ModelVisibility - isEndorsed?: BoolFieldUpdateOperationsInput | boolean - isLibraryModel?: BoolFieldUpdateOperationsInput | boolean - viewCount?: IntFieldUpdateOperationsInput | number - runCount?: IntFieldUpdateOperationsInput | number - downloadCount?: IntFieldUpdateOperationsInput | number - shareCount?: IntFieldUpdateOperationsInput | number - createdAt?: DateTimeFieldUpdateOperationsInput | Date | string - updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string - deletedAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null - childModels?: ModelUncheckedUpdateManyWithoutParentModelNestedInput - versions?: ModelVersionUncheckedUpdateManyWithoutModelNestedInput - authors?: ModelAuthorUncheckedUpdateManyWithoutModelNestedInput - permissions?: ModelPermissionUncheckedUpdateManyWithoutModelNestedInput - additionalFiles?: ModelAdditionalFileUncheckedUpdateManyWithoutModelNestedInput - likes?: ModelLikeUncheckedUpdateManyWithoutModelNestedInput - interactions?: ModelInteractionUncheckedUpdateManyWithoutModelNestedInput - } - export type UserCreateWithoutEventsInput = { id?: string name?: string | null @@ -31786,6 +36200,8 @@ export namespace Prisma { modelLikes?: ModelLikeCreateNestedManyWithoutUserInput modelInteractions?: ModelInteractionCreateNestedManyWithoutUserInput modelDrafts?: ModelDraftCreateNestedManyWithoutUserInput + comments?: ModelCommentCreateNestedManyWithoutUserInput + commentLikes?: ModelCommentLikeCreateNestedManyWithoutUserInput passkeys?: PasskeyCreateNestedManyWithoutUserInput } @@ -31820,6 +36236,8 @@ export namespace Prisma { modelLikes?: ModelLikeUncheckedCreateNestedManyWithoutUserInput modelInteractions?: ModelInteractionUncheckedCreateNestedManyWithoutUserInput modelDrafts?: ModelDraftUncheckedCreateNestedManyWithoutUserInput + comments?: ModelCommentUncheckedCreateNestedManyWithoutUserInput + commentLikes?: ModelCommentLikeUncheckedCreateNestedManyWithoutUserInput passkeys?: PasskeyUncheckedCreateNestedManyWithoutUserInput } @@ -31870,6 +36288,8 @@ export namespace Prisma { modelLikes?: ModelLikeUpdateManyWithoutUserNestedInput modelInteractions?: ModelInteractionUpdateManyWithoutUserNestedInput modelDrafts?: ModelDraftUpdateManyWithoutUserNestedInput + comments?: ModelCommentUpdateManyWithoutUserNestedInput + commentLikes?: ModelCommentLikeUpdateManyWithoutUserNestedInput passkeys?: PasskeyUpdateManyWithoutUserNestedInput } @@ -31904,6 +36324,8 @@ export namespace Prisma { modelLikes?: ModelLikeUncheckedUpdateManyWithoutUserNestedInput modelInteractions?: ModelInteractionUncheckedUpdateManyWithoutUserNestedInput modelDrafts?: ModelDraftUncheckedUpdateManyWithoutUserNestedInput + comments?: ModelCommentUncheckedUpdateManyWithoutUserNestedInput + commentLikes?: ModelCommentLikeUncheckedUpdateManyWithoutUserNestedInput passkeys?: PasskeyUncheckedUpdateManyWithoutUserNestedInput } @@ -31993,6 +36415,25 @@ export namespace Prisma { updatedAt?: Date | string } + export type ModelCommentCreateManyUserInput = { + id?: string + legacyId?: number | null + parentId?: string | null + modelId: string + versionNumber?: number | null + content?: string | null + likesCount?: number + createdAt?: Date | string + updatedAt?: Date | string + editedAt?: Date | string | null + deletedAt?: Date | string | null + } + + export type ModelCommentLikeCreateManyUserInput = { + modelCommentId: string + createdAt?: Date | string + } + export type PasskeyCreateManyUserInput = { id?: string name?: string | null @@ -32264,6 +36705,67 @@ export namespace Prisma { updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string } + export type ModelCommentUpdateWithoutUserInput = { + id?: StringFieldUpdateOperationsInput | string + legacyId?: NullableIntFieldUpdateOperationsInput | number | null + versionNumber?: NullableIntFieldUpdateOperationsInput | number | null + content?: NullableStringFieldUpdateOperationsInput | string | null + likesCount?: IntFieldUpdateOperationsInput | number + createdAt?: DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string + editedAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + deletedAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + model?: ModelUpdateOneRequiredWithoutCommentsNestedInput + parent?: ModelCommentUpdateOneWithoutRepliesNestedInput + replies?: ModelCommentUpdateManyWithoutParentNestedInput + likes?: ModelCommentLikeUpdateManyWithoutModelCommentNestedInput + } + + export type ModelCommentUncheckedUpdateWithoutUserInput = { + id?: StringFieldUpdateOperationsInput | string + legacyId?: NullableIntFieldUpdateOperationsInput | number | null + parentId?: NullableStringFieldUpdateOperationsInput | string | null + modelId?: StringFieldUpdateOperationsInput | string + versionNumber?: NullableIntFieldUpdateOperationsInput | number | null + content?: NullableStringFieldUpdateOperationsInput | string | null + likesCount?: IntFieldUpdateOperationsInput | number + createdAt?: DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string + editedAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + deletedAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + replies?: ModelCommentUncheckedUpdateManyWithoutParentNestedInput + likes?: ModelCommentLikeUncheckedUpdateManyWithoutModelCommentNestedInput + } + + export type ModelCommentUncheckedUpdateManyWithoutUserInput = { + id?: StringFieldUpdateOperationsInput | string + legacyId?: NullableIntFieldUpdateOperationsInput | number | null + parentId?: NullableStringFieldUpdateOperationsInput | string | null + modelId?: StringFieldUpdateOperationsInput | string + versionNumber?: NullableIntFieldUpdateOperationsInput | number | null + content?: NullableStringFieldUpdateOperationsInput | string | null + likesCount?: IntFieldUpdateOperationsInput | number + createdAt?: DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string + editedAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + deletedAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + } + + export type ModelCommentLikeUpdateWithoutUserInput = { + createdAt?: DateTimeFieldUpdateOperationsInput | Date | string + modelComment?: ModelCommentUpdateOneRequiredWithoutLikesNestedInput + } + + export type ModelCommentLikeUncheckedUpdateWithoutUserInput = { + modelCommentId?: StringFieldUpdateOperationsInput | string + createdAt?: DateTimeFieldUpdateOperationsInput | Date | string + } + + export type ModelCommentLikeUncheckedUpdateManyWithoutUserInput = { + modelCommentId?: StringFieldUpdateOperationsInput | string + createdAt?: DateTimeFieldUpdateOperationsInput | Date | string + } + export type PasskeyUpdateWithoutUserInput = { id?: StringFieldUpdateOperationsInput | string name?: NullableStringFieldUpdateOperationsInput | string | null @@ -32382,6 +36884,20 @@ export namespace Prisma { updatedAt?: Date | string } + export type ModelCommentCreateManyModelInput = { + id?: string + legacyId?: number | null + parentId?: string | null + userId?: string | null + versionNumber?: number | null + content?: string | null + likesCount?: number + createdAt?: Date | string + updatedAt?: Date | string + editedAt?: Date | string | null + deletedAt?: Date | string | null + } + export type ModelUpdateWithoutParentModelInput = { legacyId?: NullableIntFieldUpdateOperationsInput | number | null visibility?: EnumModelVisibilityFieldUpdateOperationsInput | $Enums.ModelVisibility @@ -32404,6 +36920,7 @@ export namespace Prisma { likes?: ModelLikeUpdateManyWithoutModelNestedInput interactions?: ModelInteractionUpdateManyWithoutModelNestedInput drafts?: ModelDraftUpdateManyWithoutModelNestedInput + comments?: ModelCommentUpdateManyWithoutModelNestedInput } export type ModelUncheckedUpdateWithoutParentModelInput = { @@ -32429,6 +36946,7 @@ export namespace Prisma { likes?: ModelLikeUncheckedUpdateManyWithoutModelNestedInput interactions?: ModelInteractionUncheckedUpdateManyWithoutModelNestedInput drafts?: ModelDraftUncheckedUpdateManyWithoutModelNestedInput + comments?: ModelCommentUncheckedUpdateManyWithoutModelNestedInput } export type ModelUncheckedUpdateManyWithoutParentModelInput = { @@ -32642,6 +37160,52 @@ export namespace Prisma { updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string } + export type ModelCommentUpdateWithoutModelInput = { + id?: StringFieldUpdateOperationsInput | string + legacyId?: NullableIntFieldUpdateOperationsInput | number | null + versionNumber?: NullableIntFieldUpdateOperationsInput | number | null + content?: NullableStringFieldUpdateOperationsInput | string | null + likesCount?: IntFieldUpdateOperationsInput | number + createdAt?: DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string + editedAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + deletedAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + user?: UserUpdateOneWithoutCommentsNestedInput + parent?: ModelCommentUpdateOneWithoutRepliesNestedInput + replies?: ModelCommentUpdateManyWithoutParentNestedInput + likes?: ModelCommentLikeUpdateManyWithoutModelCommentNestedInput + } + + export type ModelCommentUncheckedUpdateWithoutModelInput = { + id?: StringFieldUpdateOperationsInput | string + legacyId?: NullableIntFieldUpdateOperationsInput | number | null + parentId?: NullableStringFieldUpdateOperationsInput | string | null + userId?: NullableStringFieldUpdateOperationsInput | string | null + versionNumber?: NullableIntFieldUpdateOperationsInput | number | null + content?: NullableStringFieldUpdateOperationsInput | string | null + likesCount?: IntFieldUpdateOperationsInput | number + createdAt?: DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string + editedAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + deletedAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + replies?: ModelCommentUncheckedUpdateManyWithoutParentNestedInput + likes?: ModelCommentLikeUncheckedUpdateManyWithoutModelCommentNestedInput + } + + export type ModelCommentUncheckedUpdateManyWithoutModelInput = { + id?: StringFieldUpdateOperationsInput | string + legacyId?: NullableIntFieldUpdateOperationsInput | number | null + parentId?: NullableStringFieldUpdateOperationsInput | string | null + userId?: NullableStringFieldUpdateOperationsInput | string | null + versionNumber?: NullableIntFieldUpdateOperationsInput | number | null + content?: NullableStringFieldUpdateOperationsInput | string | null + likesCount?: IntFieldUpdateOperationsInput | number + createdAt?: DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string + editedAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + deletedAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + } + export type ModelCreateManyParentVersionInput = { id?: string legacyId?: number | null @@ -32692,6 +37256,7 @@ export namespace Prisma { likes?: ModelLikeUpdateManyWithoutModelNestedInput interactions?: ModelInteractionUpdateManyWithoutModelNestedInput drafts?: ModelDraftUpdateManyWithoutModelNestedInput + comments?: ModelCommentUpdateManyWithoutModelNestedInput } export type ModelUncheckedUpdateWithoutParentVersionInput = { @@ -32716,6 +37281,7 @@ export namespace Prisma { likes?: ModelLikeUncheckedUpdateManyWithoutModelNestedInput interactions?: ModelInteractionUncheckedUpdateManyWithoutModelNestedInput drafts?: ModelDraftUncheckedUpdateManyWithoutModelNestedInput + comments?: ModelCommentUncheckedUpdateManyWithoutModelNestedInput } export type ModelUncheckedUpdateManyWithoutParentVersionInput = { @@ -32794,6 +37360,86 @@ export namespace Prisma { createdAt?: DateTimeFieldUpdateOperationsInput | Date | string } + export type ModelCommentCreateManyParentInput = { + id?: string + legacyId?: number | null + userId?: string | null + modelId: string + versionNumber?: number | null + content?: string | null + likesCount?: number + createdAt?: Date | string + updatedAt?: Date | string + editedAt?: Date | string | null + deletedAt?: Date | string | null + } + + export type ModelCommentLikeCreateManyModelCommentInput = { + userId: string + createdAt?: Date | string + } + + export type ModelCommentUpdateWithoutParentInput = { + id?: StringFieldUpdateOperationsInput | string + legacyId?: NullableIntFieldUpdateOperationsInput | number | null + versionNumber?: NullableIntFieldUpdateOperationsInput | number | null + content?: NullableStringFieldUpdateOperationsInput | string | null + likesCount?: IntFieldUpdateOperationsInput | number + createdAt?: DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string + editedAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + deletedAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + model?: ModelUpdateOneRequiredWithoutCommentsNestedInput + user?: UserUpdateOneWithoutCommentsNestedInput + replies?: ModelCommentUpdateManyWithoutParentNestedInput + likes?: ModelCommentLikeUpdateManyWithoutModelCommentNestedInput + } + + export type ModelCommentUncheckedUpdateWithoutParentInput = { + id?: StringFieldUpdateOperationsInput | string + legacyId?: NullableIntFieldUpdateOperationsInput | number | null + userId?: NullableStringFieldUpdateOperationsInput | string | null + modelId?: StringFieldUpdateOperationsInput | string + versionNumber?: NullableIntFieldUpdateOperationsInput | number | null + content?: NullableStringFieldUpdateOperationsInput | string | null + likesCount?: IntFieldUpdateOperationsInput | number + createdAt?: DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string + editedAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + deletedAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + replies?: ModelCommentUncheckedUpdateManyWithoutParentNestedInput + likes?: ModelCommentLikeUncheckedUpdateManyWithoutModelCommentNestedInput + } + + export type ModelCommentUncheckedUpdateManyWithoutParentInput = { + id?: StringFieldUpdateOperationsInput | string + legacyId?: NullableIntFieldUpdateOperationsInput | number | null + userId?: NullableStringFieldUpdateOperationsInput | string | null + modelId?: StringFieldUpdateOperationsInput | string + versionNumber?: NullableIntFieldUpdateOperationsInput | number | null + content?: NullableStringFieldUpdateOperationsInput | string | null + likesCount?: IntFieldUpdateOperationsInput | number + createdAt?: DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string + editedAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + deletedAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + } + + export type ModelCommentLikeUpdateWithoutModelCommentInput = { + createdAt?: DateTimeFieldUpdateOperationsInput | Date | string + user?: UserUpdateOneRequiredWithoutCommentLikesNestedInput + } + + export type ModelCommentLikeUncheckedUpdateWithoutModelCommentInput = { + userId?: StringFieldUpdateOperationsInput | string + createdAt?: DateTimeFieldUpdateOperationsInput | Date | string + } + + export type ModelCommentLikeUncheckedUpdateManyWithoutModelCommentInput = { + userId?: StringFieldUpdateOperationsInput | string + createdAt?: DateTimeFieldUpdateOperationsInput | Date | string + } + /** diff --git a/apps/modeling-commons-backend/generated/prisma/index.js b/apps/modeling-commons-backend/generated/prisma/index.js index f31a743b..a174799e 100644 --- a/apps/modeling-commons-backend/generated/prisma/index.js +++ b/apps/modeling-commons-backend/generated/prisma/index.js @@ -272,6 +272,27 @@ exports.Prisma.ModelDraftScalarFieldEnum = { updatedAt: 'updatedAt' }; +exports.Prisma.ModelCommentScalarFieldEnum = { + id: 'id', + legacyId: 'legacyId', + parentId: 'parentId', + userId: 'userId', + modelId: 'modelId', + versionNumber: 'versionNumber', + content: 'content', + likesCount: 'likesCount', + createdAt: 'createdAt', + updatedAt: 'updatedAt', + editedAt: 'editedAt', + deletedAt: 'deletedAt' +}; + +exports.Prisma.ModelCommentLikeScalarFieldEnum = { + modelCommentId: 'modelCommentId', + userId: 'userId', + createdAt: 'createdAt' +}; + exports.Prisma.EventScalarFieldEnum = { id: 'id', type: 'type', @@ -370,6 +391,8 @@ exports.Prisma.ModelName = { ModelLike: 'ModelLike', ModelInteraction: 'ModelInteraction', ModelDraft: 'ModelDraft', + ModelComment: 'ModelComment', + ModelCommentLike: 'ModelCommentLike', Event: 'Event' }; /** @@ -380,14 +403,14 @@ const config = { "clientVersion": "7.8.0", "engineVersion": "3c6e192761c0362d496ed980de936e2f3cebcd3a", "activeProvider": "postgresql", - "inlineSchema": "generator client {\n provider = \"prisma-client-js\"\n output = \"../generated/prisma\"\n}\n\ndatasource db {\n provider = \"postgresql\"\n}\n\n// Enums\n\nenum ModelVisibility {\n public\n private\n unlisted\n}\n\nenum SystemRole {\n admin\n moderator\n user\n}\n\nenum UserKind {\n student\n teacher\n researcher\n other\n}\n\nenum AuthorRole {\n owner\n contributor\n}\n\nenum PermissionLevel {\n read\n write\n admin\n}\n\nenum ModelInteractionKind {\n view\n run\n download\n share\n}\n\nenum ModelFileKind {\n model\n additional\n}\n\n// Better Auth core tables\n\nmodel User {\n id String @id @default(uuid())\n name String?\n email String? @unique\n emailVerified Boolean @default(false)\n image String?\n createdAt DateTime @default(now()) @db.Timestamptz(3)\n updatedAt DateTime @updatedAt @db.Timestamptz(3)\n\n // Extended fields\n systemRole SystemRole @default(user)\n userKind UserKind @default(other)\n isProfilePublic Boolean @default(false)\n deletedAt DateTime?\n\n // User profile fields\n bio String?\n country String?\n socialLinks Json? // e.g. [{ platform: 'twitter', url: '...' }]\n dob DateTime? @db.Date\n affiliation String?\n\n // Better Auth relations\n accounts Account[]\n sessions Session[]\n verifications Verification[]\n\n // Domain relations\n authoredModels ModelAuthor[]\n grantedPermissions ModelPermission[]\n events Event[]\n modelLikes ModelLike[]\n modelInteractions ModelInteraction[]\n modelDrafts ModelDraft[]\n\n // Better Auth Admin plugin\n role String?\n banned Boolean?\n banReason String?\n banExpires DateTime? @db.Timestamptz(3)\n\n // Application behavior\n onboardedAt DateTime? @db.Timestamptz(3)\n legacyId Int? @unique\n\n // Passkey relations\n passkeys Passkey[]\n}\n\nmodel Account {\n id String @id @default(uuid())\n userId String\n accountId String\n providerId String\n accessToken String?\n refreshToken String?\n accessTokenExpiresAt DateTime? @db.Timestamptz(3)\n refreshTokenExpiresAt DateTime? @db.Timestamptz(3)\n scope String?\n idToken String?\n password String?\n createdAt DateTime @default(now()) @db.Timestamptz(3)\n updatedAt DateTime @updatedAt @db.Timestamptz(3)\n\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n\n @@index([userId])\n}\n\nmodel Session {\n id String @id @default(uuid())\n userId String\n expiresAt DateTime\n token String @unique\n ipAddress String?\n userAgent String?\n createdAt DateTime @default(now()) @db.Timestamptz(3)\n updatedAt DateTime @updatedAt @db.Timestamptz(3)\n\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n\n // Better Auth Admin plugin fields\n impersonatedBy String?\n\n @@index([userId])\n}\n\nmodel Verification {\n id String @id @default(uuid())\n identifier String\n value String\n expiresAt DateTime\n createdAt DateTime? @default(now()) @db.Timestamptz(3)\n updatedAt DateTime? @updatedAt @db.Timestamptz(3)\n\n user User? @relation(fields: [userId], references: [id], onDelete: Cascade)\n userId String?\n\n @@index([userId])\n}\n\nmodel Passkey {\n id String @id @default(uuid())\n name String?\n publicKey String\n userId String\n credentialID String\n counter Int\n deviceType String\n backedUp Boolean\n transports String?\n createdAt DateTime? @default(now()) @db.Timestamptz(3)\n aaguid String?\n\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n}\n\n// Domain models\n\nmodel Model {\n id String @id @default(uuid())\n legacyId Int? @unique\n latestVersionNumber Int?\n parentModelId String?\n parentVersionNumber Int?\n visibility ModelVisibility @default(public)\n isEndorsed Boolean @default(false)\n isLibraryModel Boolean @default(false)\n viewCount Int @default(0)\n runCount Int @default(0)\n downloadCount Int @default(0)\n shareCount Int @default(0)\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n deletedAt DateTime?\n\n latestVersion ModelVersion? @relation(\"LatestVersion\", fields: [id, latestVersionNumber], references: [modelId, versionNumber])\n parentModel Model? @relation(\"ModelParent\", fields: [parentModelId], references: [id])\n childModels Model[] @relation(\"ModelParent\")\n parentVersion ModelVersion? @relation(\"ParentVersion\", fields: [parentModelId, parentVersionNumber], references: [modelId, versionNumber])\n\n versions ModelVersion[] @relation(\"ModelVersions\")\n authors ModelAuthor[]\n permissions ModelPermission[]\n additionalFiles ModelAdditionalFile[]\n likes ModelLike[]\n interactions ModelInteraction[]\n drafts ModelDraft[]\n\n @@unique([id, latestVersionNumber])\n @@index([parentModelId])\n @@index([parentModelId, parentVersionNumber])\n @@index([viewCount])\n @@index([runCount])\n @@index([downloadCount])\n}\n\nmodel ModelVersion {\n modelId String\n versionNumber Int\n title String\n description String?\n changeSummary String?\n previewImageFileKey String?\n netlogoFileKey String\n netlogoVersion String?\n infoTab String?\n createdAt DateTime @default(now())\n finalizedAt DateTime?\n\n model Model @relation(\"ModelVersions\", fields: [modelId], references: [id], onDelete: Cascade)\n\n // Reverse relations\n latestOfModel Model? @relation(\"LatestVersion\")\n parentOfModels Model[] @relation(\"ParentVersion\")\n\n tags ModelVersionTag[]\n taggedAdditionalFiles ModelAdditionalFile[]\n\n @@id([modelId, versionNumber])\n @@index([modelId])\n}\n\nmodel ModelVersionTag {\n modelId String\n versionNumber Int\n tagId String\n createdAt DateTime @default(now())\n\n modelVersion ModelVersion @relation(fields: [modelId, versionNumber], references: [modelId, versionNumber], onDelete: Cascade)\n tag Tag @relation(fields: [tagId], references: [id], onDelete: Cascade)\n\n @@id([modelId, versionNumber, tagId])\n @@index([tagId])\n}\n\nmodel ModelAdditionalFile {\n id String @id @default(uuid())\n modelId String\n taggedVersionNumber Int\n fileKey String\n kind ModelFileKind @default(additional)\n createdAt DateTime @default(now())\n\n model Model @relation(fields: [modelId], references: [id], onDelete: Cascade)\n taggedVersion ModelVersion @relation(fields: [modelId, taggedVersionNumber], references: [modelId, versionNumber])\n\n @@index([modelId])\n @@index([modelId, taggedVersionNumber])\n}\n\nmodel Tag {\n id String @id @default(uuid())\n legacyId Int? @unique\n name String @unique\n displayName String?\n createdAt DateTime @default(now())\n\n modelVersions ModelVersionTag[]\n}\n\nmodel ModelAuthor {\n modelId String\n userId String\n role AuthorRole\n createdAt DateTime @default(now())\n\n model Model @relation(fields: [modelId], references: [id], onDelete: Cascade)\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n\n @@id([modelId, userId])\n @@index([userId])\n}\n\nmodel ModelPermission {\n id String @id @default(uuid())\n modelId String\n granteeUserId String?\n permissionLevel PermissionLevel\n createdAt DateTime @default(now())\n\n model Model @relation(fields: [modelId], references: [id], onDelete: Cascade)\n granteeUser User? @relation(fields: [granteeUserId], references: [id], onDelete: Cascade)\n\n @@unique([modelId, granteeUserId])\n @@index([modelId])\n @@index([granteeUserId])\n}\n\nmodel ModelLike {\n modelId String\n userId String\n createdAt DateTime @default(now()) @db.Timestamptz(3)\n\n model Model @relation(fields: [modelId], references: [id], onDelete: Cascade)\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n\n @@id([modelId, userId])\n @@index([userId])\n @@index([modelId, createdAt])\n}\n\nmodel ModelInteraction {\n id String @id @default(uuid())\n modelId String\n versionNumber Int?\n kind ModelInteractionKind\n userId String?\n sessionId String?\n ipHash String?\n userAgent String?\n referer String?\n geo Json?\n cookie String?\n\n createdAt DateTime @default(now()) @db.Timestamptz(3)\n\n model Model @relation(fields: [modelId], references: [id], onDelete: Cascade)\n user User? @relation(fields: [userId], references: [id], onDelete: SetNull)\n\n @@index([modelId, kind, createdAt])\n @@index([modelId, kind, userId])\n @@index([userId, createdAt])\n @@index([createdAt])\n}\n\nmodel ModelDraft {\n id String @id @default(cuid())\n userId String\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n\n modelId String?\n model Model? @relation(fields: [modelId], references: [id], onDelete: Cascade)\n\n schemaVersion Int\n data Json\n\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n @@index([userId])\n @@index([modelId])\n}\n\nmodel Event {\n id String @id @default(uuid())\n type String\n actorId String\n resourceType String\n resourceId String\n payload Json\n createdAt DateTime @default(now())\n processedAt DateTime?\n\n actor User @relation(fields: [actorId], references: [id])\n\n @@index([actorId])\n @@index([resourceType, resourceId])\n @@index([type])\n @@index([processedAt])\n}\n" + "inlineSchema": "generator client {\n provider = \"prisma-client-js\"\n output = \"../generated/prisma\"\n}\n\ndatasource db {\n provider = \"postgresql\"\n}\n\n// Enums\n\nenum ModelVisibility {\n public\n private\n unlisted\n}\n\nenum SystemRole {\n admin\n moderator\n user\n}\n\nenum UserKind {\n student\n teacher\n researcher\n other\n}\n\nenum AuthorRole {\n owner\n contributor\n}\n\nenum PermissionLevel {\n read\n write\n admin\n}\n\nenum ModelInteractionKind {\n view\n run\n download\n share\n}\n\nenum ModelFileKind {\n model\n additional\n}\n\n// Better Auth core tables\n\nmodel User {\n id String @id @default(uuid())\n name String?\n email String? @unique\n emailVerified Boolean @default(false)\n image String?\n createdAt DateTime @default(now()) @db.Timestamptz(3)\n updatedAt DateTime @updatedAt @db.Timestamptz(3)\n\n // Extended fields\n systemRole SystemRole @default(user)\n userKind UserKind @default(other)\n isProfilePublic Boolean @default(false)\n deletedAt DateTime?\n\n // User profile fields\n bio String?\n country String?\n socialLinks Json? // e.g. [{ platform: 'twitter', url: '...' }]\n dob DateTime? @db.Date\n affiliation String?\n\n // Better Auth relations\n accounts Account[]\n sessions Session[]\n verifications Verification[]\n\n // Domain relations\n authoredModels ModelAuthor[]\n grantedPermissions ModelPermission[]\n events Event[]\n modelLikes ModelLike[]\n modelInteractions ModelInteraction[]\n modelDrafts ModelDraft[]\n comments ModelComment[]\n commentLikes ModelCommentLike[]\n\n // Better Auth Admin plugin\n role String?\n banned Boolean?\n banReason String?\n banExpires DateTime? @db.Timestamptz(3)\n\n // Application behavior\n onboardedAt DateTime? @db.Timestamptz(3)\n legacyId Int? @unique\n\n // Passkey relations\n passkeys Passkey[]\n}\n\nmodel Account {\n id String @id @default(uuid())\n userId String\n accountId String\n providerId String\n accessToken String?\n refreshToken String?\n accessTokenExpiresAt DateTime? @db.Timestamptz(3)\n refreshTokenExpiresAt DateTime? @db.Timestamptz(3)\n scope String?\n idToken String?\n password String?\n createdAt DateTime @default(now()) @db.Timestamptz(3)\n updatedAt DateTime @updatedAt @db.Timestamptz(3)\n\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n\n @@index([userId])\n}\n\nmodel Session {\n id String @id @default(uuid())\n userId String\n expiresAt DateTime\n token String @unique\n ipAddress String?\n userAgent String?\n createdAt DateTime @default(now()) @db.Timestamptz(3)\n updatedAt DateTime @updatedAt @db.Timestamptz(3)\n\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n\n // Better Auth Admin plugin fields\n impersonatedBy String?\n\n @@index([userId])\n}\n\nmodel Verification {\n id String @id @default(uuid())\n identifier String\n value String\n expiresAt DateTime\n createdAt DateTime? @default(now()) @db.Timestamptz(3)\n updatedAt DateTime? @updatedAt @db.Timestamptz(3)\n\n user User? @relation(fields: [userId], references: [id], onDelete: Cascade)\n userId String?\n\n @@index([userId])\n}\n\nmodel Passkey {\n id String @id @default(uuid())\n name String?\n publicKey String\n userId String\n credentialID String\n counter Int\n deviceType String\n backedUp Boolean\n transports String?\n createdAt DateTime? @default(now()) @db.Timestamptz(3)\n aaguid String?\n\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n}\n\n// Domain models\n\nmodel Model {\n id String @id @default(uuid())\n legacyId Int? @unique\n latestVersionNumber Int?\n parentModelId String?\n parentVersionNumber Int?\n visibility ModelVisibility @default(public)\n isEndorsed Boolean @default(false)\n isLibraryModel Boolean @default(false)\n viewCount Int @default(0)\n runCount Int @default(0)\n downloadCount Int @default(0)\n shareCount Int @default(0)\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n deletedAt DateTime?\n\n latestVersion ModelVersion? @relation(\"LatestVersion\", fields: [id, latestVersionNumber], references: [modelId, versionNumber])\n parentModel Model? @relation(\"ModelParent\", fields: [parentModelId], references: [id])\n childModels Model[] @relation(\"ModelParent\")\n parentVersion ModelVersion? @relation(\"ParentVersion\", fields: [parentModelId, parentVersionNumber], references: [modelId, versionNumber])\n\n versions ModelVersion[] @relation(\"ModelVersions\")\n authors ModelAuthor[]\n permissions ModelPermission[]\n additionalFiles ModelAdditionalFile[]\n likes ModelLike[]\n interactions ModelInteraction[]\n drafts ModelDraft[]\n comments ModelComment[]\n\n @@unique([id, latestVersionNumber])\n @@index([parentModelId])\n @@index([parentModelId, parentVersionNumber])\n @@index([viewCount])\n @@index([runCount])\n @@index([downloadCount])\n}\n\nmodel ModelVersion {\n modelId String\n versionNumber Int\n title String\n description String?\n changeSummary String?\n previewImageFileKey String?\n netlogoFileKey String\n netlogoVersion String?\n infoTab String?\n createdAt DateTime @default(now())\n finalizedAt DateTime?\n\n model Model @relation(\"ModelVersions\", fields: [modelId], references: [id], onDelete: Cascade)\n\n // Reverse relations\n latestOfModel Model? @relation(\"LatestVersion\")\n parentOfModels Model[] @relation(\"ParentVersion\")\n\n tags ModelVersionTag[]\n taggedAdditionalFiles ModelAdditionalFile[]\n\n @@id([modelId, versionNumber])\n @@index([modelId])\n}\n\nmodel ModelVersionTag {\n modelId String\n versionNumber Int\n tagId String\n createdAt DateTime @default(now())\n\n modelVersion ModelVersion @relation(fields: [modelId, versionNumber], references: [modelId, versionNumber], onDelete: Cascade)\n tag Tag @relation(fields: [tagId], references: [id], onDelete: Cascade)\n\n @@id([modelId, versionNumber, tagId])\n @@index([tagId])\n}\n\nmodel ModelAdditionalFile {\n id String @id @default(uuid())\n modelId String\n taggedVersionNumber Int\n fileKey String\n kind ModelFileKind @default(additional)\n createdAt DateTime @default(now())\n\n model Model @relation(fields: [modelId], references: [id], onDelete: Cascade)\n taggedVersion ModelVersion @relation(fields: [modelId, taggedVersionNumber], references: [modelId, versionNumber])\n\n @@index([modelId])\n @@index([modelId, taggedVersionNumber])\n}\n\nmodel Tag {\n id String @id @default(uuid())\n legacyId Int? @unique\n name String @unique\n displayName String?\n createdAt DateTime @default(now())\n\n modelVersions ModelVersionTag[]\n}\n\nmodel ModelAuthor {\n modelId String\n userId String\n role AuthorRole\n createdAt DateTime @default(now())\n\n model Model @relation(fields: [modelId], references: [id], onDelete: Cascade)\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n\n @@id([modelId, userId])\n @@index([userId])\n}\n\nmodel ModelPermission {\n id String @id @default(uuid())\n modelId String\n granteeUserId String?\n permissionLevel PermissionLevel\n createdAt DateTime @default(now())\n\n model Model @relation(fields: [modelId], references: [id], onDelete: Cascade)\n granteeUser User? @relation(fields: [granteeUserId], references: [id], onDelete: Cascade)\n\n @@unique([modelId, granteeUserId])\n @@index([modelId])\n @@index([granteeUserId])\n}\n\nmodel ModelLike {\n modelId String\n userId String\n createdAt DateTime @default(now()) @db.Timestamptz(3)\n\n model Model @relation(fields: [modelId], references: [id], onDelete: Cascade)\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n\n @@id([modelId, userId])\n @@index([userId])\n @@index([modelId, createdAt])\n}\n\nmodel ModelInteraction {\n id String @id @default(uuid())\n modelId String\n versionNumber Int?\n kind ModelInteractionKind\n userId String?\n sessionId String?\n ipHash String?\n userAgent String?\n referer String?\n geo Json?\n cookie String?\n\n createdAt DateTime @default(now()) @db.Timestamptz(3)\n\n model Model @relation(fields: [modelId], references: [id], onDelete: Cascade)\n user User? @relation(fields: [userId], references: [id], onDelete: SetNull)\n\n @@index([modelId, kind, createdAt])\n @@index([modelId, kind, userId])\n @@index([userId, createdAt])\n @@index([createdAt])\n}\n\nmodel ModelDraft {\n id String @id @default(cuid())\n userId String\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n\n modelId String?\n model Model? @relation(fields: [modelId], references: [id], onDelete: Cascade)\n\n schemaVersion Int\n data Json\n\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n @@index([userId])\n @@index([modelId])\n}\n\nmodel ModelComment {\n id String @id @default(uuid())\n legacyId Int? @unique\n\n parentId String?\n userId String?\n\n modelId String\n versionNumber Int?\n\n content String? @db.Text\n likesCount Int @default(0)\n\n createdAt DateTime @default(now()) @db.Timestamptz(3)\n updatedAt DateTime @updatedAt @db.Timestamptz(3)\n editedAt DateTime? @db.Timestamptz(3)\n deletedAt DateTime? @db.Timestamptz(3)\n\n model Model @relation(fields: [modelId], references: [id], onDelete: Cascade)\n user User? @relation(fields: [userId], references: [id], onDelete: SetNull)\n parent ModelComment? @relation(\"CommentReplies\", fields: [parentId], references: [id], onDelete: Cascade)\n replies ModelComment[] @relation(\"CommentReplies\")\n\n likes ModelCommentLike[]\n\n @@index([modelId, parentId, createdAt])\n @@index([parentId])\n @@index([userId])\n}\n\nmodel ModelCommentLike {\n modelCommentId String\n userId String\n createdAt DateTime @default(now()) @db.Timestamptz(3)\n\n modelComment ModelComment @relation(fields: [modelCommentId], references: [id], onDelete: Cascade)\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n\n @@id([modelCommentId, userId])\n @@index([userId])\n}\n\nmodel Event {\n id String @id @default(uuid())\n type String\n actorId String\n resourceType String\n resourceId String\n payload Json\n createdAt DateTime @default(now())\n processedAt DateTime?\n\n actor User @relation(fields: [actorId], references: [id])\n\n @@index([actorId])\n @@index([resourceType, resourceId])\n @@index([type])\n @@index([processedAt])\n}\n" } -config.runtimeDataModel = JSON.parse("{\"models\":{\"User\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"email\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"emailVerified\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"image\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"systemRole\",\"kind\":\"enum\",\"type\":\"SystemRole\"},{\"name\":\"userKind\",\"kind\":\"enum\",\"type\":\"UserKind\"},{\"name\":\"isProfilePublic\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"deletedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"bio\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"country\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"socialLinks\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"dob\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"affiliation\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"accounts\",\"kind\":\"object\",\"type\":\"Account\",\"relationName\":\"AccountToUser\"},{\"name\":\"sessions\",\"kind\":\"object\",\"type\":\"Session\",\"relationName\":\"SessionToUser\"},{\"name\":\"verifications\",\"kind\":\"object\",\"type\":\"Verification\",\"relationName\":\"UserToVerification\"},{\"name\":\"authoredModels\",\"kind\":\"object\",\"type\":\"ModelAuthor\",\"relationName\":\"ModelAuthorToUser\"},{\"name\":\"grantedPermissions\",\"kind\":\"object\",\"type\":\"ModelPermission\",\"relationName\":\"ModelPermissionToUser\"},{\"name\":\"events\",\"kind\":\"object\",\"type\":\"Event\",\"relationName\":\"EventToUser\"},{\"name\":\"modelLikes\",\"kind\":\"object\",\"type\":\"ModelLike\",\"relationName\":\"ModelLikeToUser\"},{\"name\":\"modelInteractions\",\"kind\":\"object\",\"type\":\"ModelInteraction\",\"relationName\":\"ModelInteractionToUser\"},{\"name\":\"modelDrafts\",\"kind\":\"object\",\"type\":\"ModelDraft\",\"relationName\":\"ModelDraftToUser\"},{\"name\":\"role\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"banned\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"banReason\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"banExpires\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"onboardedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"legacyId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"passkeys\",\"kind\":\"object\",\"type\":\"Passkey\",\"relationName\":\"PasskeyToUser\"}],\"dbName\":null},\"Account\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"accountId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"providerId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"accessToken\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"refreshToken\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"accessTokenExpiresAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"refreshTokenExpiresAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"scope\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"idToken\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"password\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"AccountToUser\"}],\"dbName\":null},\"Session\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"expiresAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"token\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"ipAddress\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"userAgent\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"SessionToUser\"},{\"name\":\"impersonatedBy\",\"kind\":\"scalar\",\"type\":\"String\"}],\"dbName\":null},\"Verification\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"identifier\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"value\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"expiresAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"UserToVerification\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"}],\"dbName\":null},\"Passkey\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"publicKey\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"credentialID\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"counter\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"deviceType\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"backedUp\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"transports\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"aaguid\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"PasskeyToUser\"}],\"dbName\":null},\"Model\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"legacyId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"latestVersionNumber\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"parentModelId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"parentVersionNumber\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"visibility\",\"kind\":\"enum\",\"type\":\"ModelVisibility\"},{\"name\":\"isEndorsed\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"isLibraryModel\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"viewCount\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"runCount\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"downloadCount\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"shareCount\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"deletedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"latestVersion\",\"kind\":\"object\",\"type\":\"ModelVersion\",\"relationName\":\"LatestVersion\"},{\"name\":\"parentModel\",\"kind\":\"object\",\"type\":\"Model\",\"relationName\":\"ModelParent\"},{\"name\":\"childModels\",\"kind\":\"object\",\"type\":\"Model\",\"relationName\":\"ModelParent\"},{\"name\":\"parentVersion\",\"kind\":\"object\",\"type\":\"ModelVersion\",\"relationName\":\"ParentVersion\"},{\"name\":\"versions\",\"kind\":\"object\",\"type\":\"ModelVersion\",\"relationName\":\"ModelVersions\"},{\"name\":\"authors\",\"kind\":\"object\",\"type\":\"ModelAuthor\",\"relationName\":\"ModelToModelAuthor\"},{\"name\":\"permissions\",\"kind\":\"object\",\"type\":\"ModelPermission\",\"relationName\":\"ModelToModelPermission\"},{\"name\":\"additionalFiles\",\"kind\":\"object\",\"type\":\"ModelAdditionalFile\",\"relationName\":\"ModelToModelAdditionalFile\"},{\"name\":\"likes\",\"kind\":\"object\",\"type\":\"ModelLike\",\"relationName\":\"ModelToModelLike\"},{\"name\":\"interactions\",\"kind\":\"object\",\"type\":\"ModelInteraction\",\"relationName\":\"ModelToModelInteraction\"},{\"name\":\"drafts\",\"kind\":\"object\",\"type\":\"ModelDraft\",\"relationName\":\"ModelToModelDraft\"}],\"dbName\":null},\"ModelVersion\":{\"fields\":[{\"name\":\"modelId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"versionNumber\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"title\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"description\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"changeSummary\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"previewImageFileKey\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"netlogoFileKey\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"netlogoVersion\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"infoTab\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"finalizedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"model\",\"kind\":\"object\",\"type\":\"Model\",\"relationName\":\"ModelVersions\"},{\"name\":\"latestOfModel\",\"kind\":\"object\",\"type\":\"Model\",\"relationName\":\"LatestVersion\"},{\"name\":\"parentOfModels\",\"kind\":\"object\",\"type\":\"Model\",\"relationName\":\"ParentVersion\"},{\"name\":\"tags\",\"kind\":\"object\",\"type\":\"ModelVersionTag\",\"relationName\":\"ModelVersionToModelVersionTag\"},{\"name\":\"taggedAdditionalFiles\",\"kind\":\"object\",\"type\":\"ModelAdditionalFile\",\"relationName\":\"ModelAdditionalFileToModelVersion\"}],\"dbName\":null},\"ModelVersionTag\":{\"fields\":[{\"name\":\"modelId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"versionNumber\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"tagId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"modelVersion\",\"kind\":\"object\",\"type\":\"ModelVersion\",\"relationName\":\"ModelVersionToModelVersionTag\"},{\"name\":\"tag\",\"kind\":\"object\",\"type\":\"Tag\",\"relationName\":\"ModelVersionTagToTag\"}],\"dbName\":null},\"ModelAdditionalFile\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"modelId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"taggedVersionNumber\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"fileKey\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"kind\",\"kind\":\"enum\",\"type\":\"ModelFileKind\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"model\",\"kind\":\"object\",\"type\":\"Model\",\"relationName\":\"ModelToModelAdditionalFile\"},{\"name\":\"taggedVersion\",\"kind\":\"object\",\"type\":\"ModelVersion\",\"relationName\":\"ModelAdditionalFileToModelVersion\"}],\"dbName\":null},\"Tag\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"legacyId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"displayName\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"modelVersions\",\"kind\":\"object\",\"type\":\"ModelVersionTag\",\"relationName\":\"ModelVersionTagToTag\"}],\"dbName\":null},\"ModelAuthor\":{\"fields\":[{\"name\":\"modelId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"role\",\"kind\":\"enum\",\"type\":\"AuthorRole\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"model\",\"kind\":\"object\",\"type\":\"Model\",\"relationName\":\"ModelToModelAuthor\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"ModelAuthorToUser\"}],\"dbName\":null},\"ModelPermission\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"modelId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"granteeUserId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"permissionLevel\",\"kind\":\"enum\",\"type\":\"PermissionLevel\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"model\",\"kind\":\"object\",\"type\":\"Model\",\"relationName\":\"ModelToModelPermission\"},{\"name\":\"granteeUser\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"ModelPermissionToUser\"}],\"dbName\":null},\"ModelLike\":{\"fields\":[{\"name\":\"modelId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"model\",\"kind\":\"object\",\"type\":\"Model\",\"relationName\":\"ModelToModelLike\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"ModelLikeToUser\"}],\"dbName\":null},\"ModelInteraction\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"modelId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"versionNumber\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"kind\",\"kind\":\"enum\",\"type\":\"ModelInteractionKind\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"sessionId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"ipHash\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"userAgent\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"referer\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"geo\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"cookie\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"model\",\"kind\":\"object\",\"type\":\"Model\",\"relationName\":\"ModelToModelInteraction\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"ModelInteractionToUser\"}],\"dbName\":null},\"ModelDraft\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"ModelDraftToUser\"},{\"name\":\"modelId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"model\",\"kind\":\"object\",\"type\":\"Model\",\"relationName\":\"ModelToModelDraft\"},{\"name\":\"schemaVersion\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"data\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"Event\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"type\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"actorId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"resourceType\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"resourceId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"payload\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"processedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"actor\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"EventToUser\"}],\"dbName\":null}},\"enums\":{},\"types\":{}}") +config.runtimeDataModel = JSON.parse("{\"models\":{\"User\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"email\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"emailVerified\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"image\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"systemRole\",\"kind\":\"enum\",\"type\":\"SystemRole\"},{\"name\":\"userKind\",\"kind\":\"enum\",\"type\":\"UserKind\"},{\"name\":\"isProfilePublic\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"deletedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"bio\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"country\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"socialLinks\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"dob\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"affiliation\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"accounts\",\"kind\":\"object\",\"type\":\"Account\",\"relationName\":\"AccountToUser\"},{\"name\":\"sessions\",\"kind\":\"object\",\"type\":\"Session\",\"relationName\":\"SessionToUser\"},{\"name\":\"verifications\",\"kind\":\"object\",\"type\":\"Verification\",\"relationName\":\"UserToVerification\"},{\"name\":\"authoredModels\",\"kind\":\"object\",\"type\":\"ModelAuthor\",\"relationName\":\"ModelAuthorToUser\"},{\"name\":\"grantedPermissions\",\"kind\":\"object\",\"type\":\"ModelPermission\",\"relationName\":\"ModelPermissionToUser\"},{\"name\":\"events\",\"kind\":\"object\",\"type\":\"Event\",\"relationName\":\"EventToUser\"},{\"name\":\"modelLikes\",\"kind\":\"object\",\"type\":\"ModelLike\",\"relationName\":\"ModelLikeToUser\"},{\"name\":\"modelInteractions\",\"kind\":\"object\",\"type\":\"ModelInteraction\",\"relationName\":\"ModelInteractionToUser\"},{\"name\":\"modelDrafts\",\"kind\":\"object\",\"type\":\"ModelDraft\",\"relationName\":\"ModelDraftToUser\"},{\"name\":\"comments\",\"kind\":\"object\",\"type\":\"ModelComment\",\"relationName\":\"ModelCommentToUser\"},{\"name\":\"commentLikes\",\"kind\":\"object\",\"type\":\"ModelCommentLike\",\"relationName\":\"ModelCommentLikeToUser\"},{\"name\":\"role\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"banned\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"banReason\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"banExpires\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"onboardedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"legacyId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"passkeys\",\"kind\":\"object\",\"type\":\"Passkey\",\"relationName\":\"PasskeyToUser\"}],\"dbName\":null},\"Account\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"accountId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"providerId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"accessToken\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"refreshToken\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"accessTokenExpiresAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"refreshTokenExpiresAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"scope\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"idToken\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"password\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"AccountToUser\"}],\"dbName\":null},\"Session\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"expiresAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"token\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"ipAddress\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"userAgent\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"SessionToUser\"},{\"name\":\"impersonatedBy\",\"kind\":\"scalar\",\"type\":\"String\"}],\"dbName\":null},\"Verification\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"identifier\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"value\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"expiresAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"UserToVerification\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"}],\"dbName\":null},\"Passkey\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"publicKey\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"credentialID\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"counter\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"deviceType\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"backedUp\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"transports\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"aaguid\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"PasskeyToUser\"}],\"dbName\":null},\"Model\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"legacyId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"latestVersionNumber\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"parentModelId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"parentVersionNumber\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"visibility\",\"kind\":\"enum\",\"type\":\"ModelVisibility\"},{\"name\":\"isEndorsed\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"isLibraryModel\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"viewCount\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"runCount\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"downloadCount\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"shareCount\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"deletedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"latestVersion\",\"kind\":\"object\",\"type\":\"ModelVersion\",\"relationName\":\"LatestVersion\"},{\"name\":\"parentModel\",\"kind\":\"object\",\"type\":\"Model\",\"relationName\":\"ModelParent\"},{\"name\":\"childModels\",\"kind\":\"object\",\"type\":\"Model\",\"relationName\":\"ModelParent\"},{\"name\":\"parentVersion\",\"kind\":\"object\",\"type\":\"ModelVersion\",\"relationName\":\"ParentVersion\"},{\"name\":\"versions\",\"kind\":\"object\",\"type\":\"ModelVersion\",\"relationName\":\"ModelVersions\"},{\"name\":\"authors\",\"kind\":\"object\",\"type\":\"ModelAuthor\",\"relationName\":\"ModelToModelAuthor\"},{\"name\":\"permissions\",\"kind\":\"object\",\"type\":\"ModelPermission\",\"relationName\":\"ModelToModelPermission\"},{\"name\":\"additionalFiles\",\"kind\":\"object\",\"type\":\"ModelAdditionalFile\",\"relationName\":\"ModelToModelAdditionalFile\"},{\"name\":\"likes\",\"kind\":\"object\",\"type\":\"ModelLike\",\"relationName\":\"ModelToModelLike\"},{\"name\":\"interactions\",\"kind\":\"object\",\"type\":\"ModelInteraction\",\"relationName\":\"ModelToModelInteraction\"},{\"name\":\"drafts\",\"kind\":\"object\",\"type\":\"ModelDraft\",\"relationName\":\"ModelToModelDraft\"},{\"name\":\"comments\",\"kind\":\"object\",\"type\":\"ModelComment\",\"relationName\":\"ModelToModelComment\"}],\"dbName\":null},\"ModelVersion\":{\"fields\":[{\"name\":\"modelId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"versionNumber\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"title\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"description\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"changeSummary\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"previewImageFileKey\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"netlogoFileKey\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"netlogoVersion\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"infoTab\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"finalizedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"model\",\"kind\":\"object\",\"type\":\"Model\",\"relationName\":\"ModelVersions\"},{\"name\":\"latestOfModel\",\"kind\":\"object\",\"type\":\"Model\",\"relationName\":\"LatestVersion\"},{\"name\":\"parentOfModels\",\"kind\":\"object\",\"type\":\"Model\",\"relationName\":\"ParentVersion\"},{\"name\":\"tags\",\"kind\":\"object\",\"type\":\"ModelVersionTag\",\"relationName\":\"ModelVersionToModelVersionTag\"},{\"name\":\"taggedAdditionalFiles\",\"kind\":\"object\",\"type\":\"ModelAdditionalFile\",\"relationName\":\"ModelAdditionalFileToModelVersion\"}],\"dbName\":null},\"ModelVersionTag\":{\"fields\":[{\"name\":\"modelId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"versionNumber\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"tagId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"modelVersion\",\"kind\":\"object\",\"type\":\"ModelVersion\",\"relationName\":\"ModelVersionToModelVersionTag\"},{\"name\":\"tag\",\"kind\":\"object\",\"type\":\"Tag\",\"relationName\":\"ModelVersionTagToTag\"}],\"dbName\":null},\"ModelAdditionalFile\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"modelId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"taggedVersionNumber\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"fileKey\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"kind\",\"kind\":\"enum\",\"type\":\"ModelFileKind\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"model\",\"kind\":\"object\",\"type\":\"Model\",\"relationName\":\"ModelToModelAdditionalFile\"},{\"name\":\"taggedVersion\",\"kind\":\"object\",\"type\":\"ModelVersion\",\"relationName\":\"ModelAdditionalFileToModelVersion\"}],\"dbName\":null},\"Tag\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"legacyId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"displayName\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"modelVersions\",\"kind\":\"object\",\"type\":\"ModelVersionTag\",\"relationName\":\"ModelVersionTagToTag\"}],\"dbName\":null},\"ModelAuthor\":{\"fields\":[{\"name\":\"modelId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"role\",\"kind\":\"enum\",\"type\":\"AuthorRole\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"model\",\"kind\":\"object\",\"type\":\"Model\",\"relationName\":\"ModelToModelAuthor\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"ModelAuthorToUser\"}],\"dbName\":null},\"ModelPermission\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"modelId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"granteeUserId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"permissionLevel\",\"kind\":\"enum\",\"type\":\"PermissionLevel\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"model\",\"kind\":\"object\",\"type\":\"Model\",\"relationName\":\"ModelToModelPermission\"},{\"name\":\"granteeUser\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"ModelPermissionToUser\"}],\"dbName\":null},\"ModelLike\":{\"fields\":[{\"name\":\"modelId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"model\",\"kind\":\"object\",\"type\":\"Model\",\"relationName\":\"ModelToModelLike\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"ModelLikeToUser\"}],\"dbName\":null},\"ModelInteraction\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"modelId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"versionNumber\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"kind\",\"kind\":\"enum\",\"type\":\"ModelInteractionKind\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"sessionId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"ipHash\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"userAgent\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"referer\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"geo\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"cookie\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"model\",\"kind\":\"object\",\"type\":\"Model\",\"relationName\":\"ModelToModelInteraction\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"ModelInteractionToUser\"}],\"dbName\":null},\"ModelDraft\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"ModelDraftToUser\"},{\"name\":\"modelId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"model\",\"kind\":\"object\",\"type\":\"Model\",\"relationName\":\"ModelToModelDraft\"},{\"name\":\"schemaVersion\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"data\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"ModelComment\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"legacyId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"parentId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"modelId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"versionNumber\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"content\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"likesCount\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"editedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"deletedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"model\",\"kind\":\"object\",\"type\":\"Model\",\"relationName\":\"ModelToModelComment\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"ModelCommentToUser\"},{\"name\":\"parent\",\"kind\":\"object\",\"type\":\"ModelComment\",\"relationName\":\"CommentReplies\"},{\"name\":\"replies\",\"kind\":\"object\",\"type\":\"ModelComment\",\"relationName\":\"CommentReplies\"},{\"name\":\"likes\",\"kind\":\"object\",\"type\":\"ModelCommentLike\",\"relationName\":\"ModelCommentToModelCommentLike\"}],\"dbName\":null},\"ModelCommentLike\":{\"fields\":[{\"name\":\"modelCommentId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"modelComment\",\"kind\":\"object\",\"type\":\"ModelComment\",\"relationName\":\"ModelCommentToModelCommentLike\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"ModelCommentLikeToUser\"}],\"dbName\":null},\"Event\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"type\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"actorId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"resourceType\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"resourceId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"payload\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"processedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"actor\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"EventToUser\"}],\"dbName\":null}},\"enums\":{},\"types\":{}}") defineDmmfProperty(exports.Prisma, config.runtimeDataModel) config.parameterizationSchema = { - strings: JSON.parse("[\"where\",\"orderBy\",\"cursor\",\"user\",\"accounts\",\"sessions\",\"verifications\",\"model\",\"latestOfModel\",\"parentOfModels\",\"modelVersion\",\"modelVersions\",\"_count\",\"tag\",\"tags\",\"taggedVersion\",\"taggedAdditionalFiles\",\"latestVersion\",\"parentModel\",\"childModels\",\"parentVersion\",\"versions\",\"authors\",\"granteeUser\",\"permissions\",\"additionalFiles\",\"likes\",\"interactions\",\"drafts\",\"authoredModels\",\"grantedPermissions\",\"actor\",\"events\",\"modelLikes\",\"modelInteractions\",\"modelDrafts\",\"passkeys\",\"User.findUnique\",\"User.findUniqueOrThrow\",\"User.findFirst\",\"User.findFirstOrThrow\",\"User.findMany\",\"data\",\"User.createOne\",\"User.createMany\",\"User.createManyAndReturn\",\"User.updateOne\",\"User.updateMany\",\"User.updateManyAndReturn\",\"create\",\"update\",\"User.upsertOne\",\"User.deleteOne\",\"User.deleteMany\",\"having\",\"_avg\",\"_sum\",\"_min\",\"_max\",\"User.groupBy\",\"User.aggregate\",\"Account.findUnique\",\"Account.findUniqueOrThrow\",\"Account.findFirst\",\"Account.findFirstOrThrow\",\"Account.findMany\",\"Account.createOne\",\"Account.createMany\",\"Account.createManyAndReturn\",\"Account.updateOne\",\"Account.updateMany\",\"Account.updateManyAndReturn\",\"Account.upsertOne\",\"Account.deleteOne\",\"Account.deleteMany\",\"Account.groupBy\",\"Account.aggregate\",\"Session.findUnique\",\"Session.findUniqueOrThrow\",\"Session.findFirst\",\"Session.findFirstOrThrow\",\"Session.findMany\",\"Session.createOne\",\"Session.createMany\",\"Session.createManyAndReturn\",\"Session.updateOne\",\"Session.updateMany\",\"Session.updateManyAndReturn\",\"Session.upsertOne\",\"Session.deleteOne\",\"Session.deleteMany\",\"Session.groupBy\",\"Session.aggregate\",\"Verification.findUnique\",\"Verification.findUniqueOrThrow\",\"Verification.findFirst\",\"Verification.findFirstOrThrow\",\"Verification.findMany\",\"Verification.createOne\",\"Verification.createMany\",\"Verification.createManyAndReturn\",\"Verification.updateOne\",\"Verification.updateMany\",\"Verification.updateManyAndReturn\",\"Verification.upsertOne\",\"Verification.deleteOne\",\"Verification.deleteMany\",\"Verification.groupBy\",\"Verification.aggregate\",\"Passkey.findUnique\",\"Passkey.findUniqueOrThrow\",\"Passkey.findFirst\",\"Passkey.findFirstOrThrow\",\"Passkey.findMany\",\"Passkey.createOne\",\"Passkey.createMany\",\"Passkey.createManyAndReturn\",\"Passkey.updateOne\",\"Passkey.updateMany\",\"Passkey.updateManyAndReturn\",\"Passkey.upsertOne\",\"Passkey.deleteOne\",\"Passkey.deleteMany\",\"Passkey.groupBy\",\"Passkey.aggregate\",\"Model.findUnique\",\"Model.findUniqueOrThrow\",\"Model.findFirst\",\"Model.findFirstOrThrow\",\"Model.findMany\",\"Model.createOne\",\"Model.createMany\",\"Model.createManyAndReturn\",\"Model.updateOne\",\"Model.updateMany\",\"Model.updateManyAndReturn\",\"Model.upsertOne\",\"Model.deleteOne\",\"Model.deleteMany\",\"Model.groupBy\",\"Model.aggregate\",\"ModelVersion.findUnique\",\"ModelVersion.findUniqueOrThrow\",\"ModelVersion.findFirst\",\"ModelVersion.findFirstOrThrow\",\"ModelVersion.findMany\",\"ModelVersion.createOne\",\"ModelVersion.createMany\",\"ModelVersion.createManyAndReturn\",\"ModelVersion.updateOne\",\"ModelVersion.updateMany\",\"ModelVersion.updateManyAndReturn\",\"ModelVersion.upsertOne\",\"ModelVersion.deleteOne\",\"ModelVersion.deleteMany\",\"ModelVersion.groupBy\",\"ModelVersion.aggregate\",\"ModelVersionTag.findUnique\",\"ModelVersionTag.findUniqueOrThrow\",\"ModelVersionTag.findFirst\",\"ModelVersionTag.findFirstOrThrow\",\"ModelVersionTag.findMany\",\"ModelVersionTag.createOne\",\"ModelVersionTag.createMany\",\"ModelVersionTag.createManyAndReturn\",\"ModelVersionTag.updateOne\",\"ModelVersionTag.updateMany\",\"ModelVersionTag.updateManyAndReturn\",\"ModelVersionTag.upsertOne\",\"ModelVersionTag.deleteOne\",\"ModelVersionTag.deleteMany\",\"ModelVersionTag.groupBy\",\"ModelVersionTag.aggregate\",\"ModelAdditionalFile.findUnique\",\"ModelAdditionalFile.findUniqueOrThrow\",\"ModelAdditionalFile.findFirst\",\"ModelAdditionalFile.findFirstOrThrow\",\"ModelAdditionalFile.findMany\",\"ModelAdditionalFile.createOne\",\"ModelAdditionalFile.createMany\",\"ModelAdditionalFile.createManyAndReturn\",\"ModelAdditionalFile.updateOne\",\"ModelAdditionalFile.updateMany\",\"ModelAdditionalFile.updateManyAndReturn\",\"ModelAdditionalFile.upsertOne\",\"ModelAdditionalFile.deleteOne\",\"ModelAdditionalFile.deleteMany\",\"ModelAdditionalFile.groupBy\",\"ModelAdditionalFile.aggregate\",\"Tag.findUnique\",\"Tag.findUniqueOrThrow\",\"Tag.findFirst\",\"Tag.findFirstOrThrow\",\"Tag.findMany\",\"Tag.createOne\",\"Tag.createMany\",\"Tag.createManyAndReturn\",\"Tag.updateOne\",\"Tag.updateMany\",\"Tag.updateManyAndReturn\",\"Tag.upsertOne\",\"Tag.deleteOne\",\"Tag.deleteMany\",\"Tag.groupBy\",\"Tag.aggregate\",\"ModelAuthor.findUnique\",\"ModelAuthor.findUniqueOrThrow\",\"ModelAuthor.findFirst\",\"ModelAuthor.findFirstOrThrow\",\"ModelAuthor.findMany\",\"ModelAuthor.createOne\",\"ModelAuthor.createMany\",\"ModelAuthor.createManyAndReturn\",\"ModelAuthor.updateOne\",\"ModelAuthor.updateMany\",\"ModelAuthor.updateManyAndReturn\",\"ModelAuthor.upsertOne\",\"ModelAuthor.deleteOne\",\"ModelAuthor.deleteMany\",\"ModelAuthor.groupBy\",\"ModelAuthor.aggregate\",\"ModelPermission.findUnique\",\"ModelPermission.findUniqueOrThrow\",\"ModelPermission.findFirst\",\"ModelPermission.findFirstOrThrow\",\"ModelPermission.findMany\",\"ModelPermission.createOne\",\"ModelPermission.createMany\",\"ModelPermission.createManyAndReturn\",\"ModelPermission.updateOne\",\"ModelPermission.updateMany\",\"ModelPermission.updateManyAndReturn\",\"ModelPermission.upsertOne\",\"ModelPermission.deleteOne\",\"ModelPermission.deleteMany\",\"ModelPermission.groupBy\",\"ModelPermission.aggregate\",\"ModelLike.findUnique\",\"ModelLike.findUniqueOrThrow\",\"ModelLike.findFirst\",\"ModelLike.findFirstOrThrow\",\"ModelLike.findMany\",\"ModelLike.createOne\",\"ModelLike.createMany\",\"ModelLike.createManyAndReturn\",\"ModelLike.updateOne\",\"ModelLike.updateMany\",\"ModelLike.updateManyAndReturn\",\"ModelLike.upsertOne\",\"ModelLike.deleteOne\",\"ModelLike.deleteMany\",\"ModelLike.groupBy\",\"ModelLike.aggregate\",\"ModelInteraction.findUnique\",\"ModelInteraction.findUniqueOrThrow\",\"ModelInteraction.findFirst\",\"ModelInteraction.findFirstOrThrow\",\"ModelInteraction.findMany\",\"ModelInteraction.createOne\",\"ModelInteraction.createMany\",\"ModelInteraction.createManyAndReturn\",\"ModelInteraction.updateOne\",\"ModelInteraction.updateMany\",\"ModelInteraction.updateManyAndReturn\",\"ModelInteraction.upsertOne\",\"ModelInteraction.deleteOne\",\"ModelInteraction.deleteMany\",\"ModelInteraction.groupBy\",\"ModelInteraction.aggregate\",\"ModelDraft.findUnique\",\"ModelDraft.findUniqueOrThrow\",\"ModelDraft.findFirst\",\"ModelDraft.findFirstOrThrow\",\"ModelDraft.findMany\",\"ModelDraft.createOne\",\"ModelDraft.createMany\",\"ModelDraft.createManyAndReturn\",\"ModelDraft.updateOne\",\"ModelDraft.updateMany\",\"ModelDraft.updateManyAndReturn\",\"ModelDraft.upsertOne\",\"ModelDraft.deleteOne\",\"ModelDraft.deleteMany\",\"ModelDraft.groupBy\",\"ModelDraft.aggregate\",\"Event.findUnique\",\"Event.findUniqueOrThrow\",\"Event.findFirst\",\"Event.findFirstOrThrow\",\"Event.findMany\",\"Event.createOne\",\"Event.createMany\",\"Event.createManyAndReturn\",\"Event.updateOne\",\"Event.updateMany\",\"Event.updateManyAndReturn\",\"Event.upsertOne\",\"Event.deleteOne\",\"Event.deleteMany\",\"Event.groupBy\",\"Event.aggregate\",\"AND\",\"OR\",\"NOT\",\"id\",\"type\",\"actorId\",\"resourceType\",\"resourceId\",\"payload\",\"createdAt\",\"processedAt\",\"equals\",\"in\",\"notIn\",\"lt\",\"lte\",\"gt\",\"gte\",\"not\",\"string_contains\",\"string_starts_with\",\"string_ends_with\",\"array_starts_with\",\"array_ends_with\",\"array_contains\",\"contains\",\"startsWith\",\"endsWith\",\"userId\",\"modelId\",\"schemaVersion\",\"updatedAt\",\"versionNumber\",\"ModelInteractionKind\",\"kind\",\"sessionId\",\"ipHash\",\"userAgent\",\"referer\",\"geo\",\"cookie\",\"granteeUserId\",\"PermissionLevel\",\"permissionLevel\",\"AuthorRole\",\"role\",\"legacyId\",\"name\",\"displayName\",\"every\",\"some\",\"none\",\"taggedVersionNumber\",\"fileKey\",\"ModelFileKind\",\"tagId\",\"title\",\"description\",\"changeSummary\",\"previewImageFileKey\",\"netlogoFileKey\",\"netlogoVersion\",\"infoTab\",\"finalizedAt\",\"latestVersionNumber\",\"parentModelId\",\"parentVersionNumber\",\"ModelVisibility\",\"visibility\",\"isEndorsed\",\"isLibraryModel\",\"viewCount\",\"runCount\",\"downloadCount\",\"shareCount\",\"deletedAt\",\"publicKey\",\"credentialID\",\"counter\",\"deviceType\",\"backedUp\",\"transports\",\"aaguid\",\"identifier\",\"value\",\"expiresAt\",\"token\",\"ipAddress\",\"impersonatedBy\",\"accountId\",\"providerId\",\"accessToken\",\"refreshToken\",\"accessTokenExpiresAt\",\"refreshTokenExpiresAt\",\"scope\",\"idToken\",\"password\",\"email\",\"emailVerified\",\"image\",\"SystemRole\",\"systemRole\",\"UserKind\",\"userKind\",\"isProfilePublic\",\"bio\",\"country\",\"socialLinks\",\"dob\",\"affiliation\",\"banned\",\"banReason\",\"banExpires\",\"onboardedAt\",\"modelId_userId\",\"modelId_granteeUserId\",\"modelId_versionNumber\",\"modelId_versionNumber_tagId\",\"id_latestVersionNumber\",\"is\",\"isNot\",\"connectOrCreate\",\"upsert\",\"createMany\",\"set\",\"disconnect\",\"delete\",\"connect\",\"updateMany\",\"deleteMany\",\"increment\",\"decrement\",\"multiply\",\"divide\"]"), - graph: "jwmXAYACIwQAAMoEACAFAADLBAAgBgAAzAQAIB0AAL4EACAeAAC_BAAgIAAAzQQAICEAAMAEACAiAADBBAAgIwAAwgQAICQAAM4EACCtAgAAxgQAMK4CAAAPABCvAgAAxgQAMLACAQAAAAG2AkAA_wMAIcwCQAD_AwAh2gIBAP4DACHbAgIAAAAB3AIBAP4DACH4AkAAnwQAIY8DAQAAAAGQAyAAngQAIZEDAQD-AwAhkwMAAMcEkwMilQMAAMgElQMilgMgAJ4EACGXAwEA_gMAIZgDAQD-AwAhmQMAAKcEACCaA0AAnwQAIZsDAQD-AwAhnAMgAMkEACGdAwEA_gMAIZ4DQACfBAAhnwNAAJ8EACEBAAAAAQAgEQMAAKAEACCtAgAA0QQAMK4CAAADABCvAgAA0QQAMLACAQD8AwAhtgJAAP8DACHJAgEA_AMAIcwCQAD_AwAhhgMBAPwDACGHAwEA_AMAIYgDAQD-AwAhiQMBAP4DACGKA0AAnwQAIYsDQACfBAAhjAMBAP4DACGNAwEA_gMAIY4DAQD-AwAhCAMAAIAIACCIAwAA0gQAIIkDAADSBAAgigMAANIEACCLAwAA0gQAIIwDAADSBAAgjQMAANIEACCOAwAA0gQAIBEDAACgBAAgrQIAANEEADCuAgAAAwAQrwIAANEEADCwAgEAAAABtgJAAP8DACHJAgEA_AMAIcwCQAD_AwAhhgMBAPwDACGHAwEA_AMAIYgDAQD-AwAhiQMBAP4DACGKA0AAnwQAIYsDQACfBAAhjAMBAP4DACGNAwEA_gMAIY4DAQD-AwAhAwAAAAMAIAEAAAQAMAIAAAUAIA0DAACgBAAgrQIAANAEADCuAgAABwAQrwIAANAEADCwAgEA_AMAIbYCQAD_AwAhyQIBAPwDACHMAkAA_wMAIdICAQD-AwAhggNAAP8DACGDAwEA_AMAIYQDAQD-AwAhhQMBAP4DACEEAwAAgAgAINICAADSBAAghAMAANIEACCFAwAA0gQAIA0DAACgBAAgrQIAANAEADCuAgAABwAQrwIAANAEADCwAgEAAAABtgJAAP8DACHJAgEA_AMAIcwCQAD_AwAh0gIBAP4DACGCA0AA_wMAIYMDAQAAAAGEAwEA_gMAIYUDAQD-AwAhAwAAAAcAIAEAAAgAMAIAAAkAIAsDAACpBAAgrQIAAM8EADCuAgAACwAQrwIAAM8EADCwAgEA_AMAIbYCQACfBAAhyQIBAP4DACHMAkAAnwQAIYADAQD8AwAhgQMBAPwDACGCA0AA_wMAIQQDAACACAAgtgIAANIEACDJAgAA0gQAIMwCAADSBAAgCwMAAKkEACCtAgAAzwQAMK4CAAALABCvAgAAzwQAMLACAQAAAAG2AkAAnwQAIckCAQD-AwAhzAJAAJ8EACGAAwEA_AMAIYEDAQD8AwAhggNAAP8DACEDAAAACwAgAQAADAAwAgAADQAgIwQAAMoEACAFAADLBAAgBgAAzAQAIB0AAL4EACAeAAC_BAAgIAAAzQQAICEAAMAEACAiAADBBAAgIwAAwgQAICQAAM4EACCtAgAAxgQAMK4CAAAPABCvAgAAxgQAMLACAQD8AwAhtgJAAP8DACHMAkAA_wMAIdoCAQD-AwAh2wICAP0DACHcAgEA_gMAIfgCQACfBAAhjwMBAP4DACGQAyAAngQAIZEDAQD-AwAhkwMAAMcEkwMilQMAAMgElQMilgMgAJ4EACGXAwEA_gMAIZgDAQD-AwAhmQMAAKcEACCaA0AAnwQAIZsDAQD-AwAhnAMgAMkEACGdAwEA_gMAIZ4DQACfBAAhnwNAAJ8EACEBAAAADwAgCQMAAKAEACAHAACoBAAgrQIAAMQEADCuAgAAEQAQrwIAAMQEADC2AkAA_wMAIckCAQD8AwAhygIBAPwDACHaAgAAxQTaAiICAwAAgAgAIAcAAIEIACAKAwAAoAQAIAcAAKgEACCtAgAAxAQAMK4CAAARABCvAgAAxAQAMLYCQAD_AwAhyQIBAPwDACHKAgEA_AMAIdoCAADFBNoCIqADAADDBAAgAwAAABEAIAEAABIAMAIAABMAIBMHAACoBAAgCAAApAQAIAkAALEEACAOAACABAAgEAAAsgQAIK0CAACwBAAwrgIAABUAEK8CAACwBAAwtgJAAP8DACHKAgEA_AMAIc0CAgCdBAAh5QIBAPwDACHmAgEA_gMAIecCAQD-AwAh6AIBAP4DACHpAgEA_AMAIeoCAQD-AwAh6wIBAP4DACHsAkAAnwQAIQEAAAAVACAdEQAAvAQAIBIAAKQEACATAACxBAAgFAAAvAQAIBUAAL0EACAWAAC-BAAgGAAAvwQAIBkAALIEACAaAADABAAgGwAAwQQAIBwAAMIEACCtAgAAugQAMK4CAAAXABCvAgAAugQAMLACAQD8AwAhtgJAAP8DACHMAkAA_wMAIdsCAgD9AwAh7QICAP0DACHuAgEA_gMAIe8CAgD9AwAh8QIAALsE8QIi8gIgAJ4EACHzAiAAngQAIfQCAgCdBAAh9QICAJ0EACH2AgIAnQQAIfcCAgCdBAAh-AJAAJ8EACEBAAAAFwAgEBEAAIQIACASAACBCAAgEwAAgggAIBQAAIQIACAVAACGCAAgFgAA-QcAIBgAAPoHACAZAACDCAAgGgAA_AcAIBsAAP0HACAcAAD-BwAg2wIAANIEACDtAgAA0gQAIO4CAADSBAAg7wIAANIEACD4AgAA0gQAIB4RAAC8BAAgEgAApAQAIBMAALEEACAUAAC8BAAgFQAAvQQAIBYAAL4EACAYAAC_BAAgGQAAsgQAIBoAAMAEACAbAADBBAAgHAAAwgQAIK0CAAC6BAAwrgIAABcAEK8CAAC6BAAwsAIBAAAAAbYCQAD_AwAhzAJAAP8DACHbAgIAAAAB7QICAP0DACHuAgEA_gMAIe8CAgD9AwAh8QIAALsE8QIi8gIgAJ4EACHzAiAAngQAIfQCAgCdBAAh9QICAJ0EACH2AgIAnQQAIfcCAgCdBAAh-AJAAJ8EACGkAwAAuQQAIAMAAAAXACABAAAZADACAAAaACAJCgAAtQQAIA0AALgEACCtAgAAtwQAMK4CAAAcABCvAgAAtwQAMLYCQAD_AwAhygIBAPwDACHNAgIAnQQAIeQCAQD8AwAhAgoAAIQIACANAACFCAAgCgoAALUEACANAAC4BAAgrQIAALcEADCuAgAAHAAQrwIAALcEADC2AkAA_wMAIcoCAQD8AwAhzQICAJ0EACHkAgEA_AMAIaMDAAC2BAAgAwAAABwAIAEAAB0AMAIAAB4AIAMAAAAcACABAAAdADACAAAeACABAAAAHAAgCwcAAKgEACAPAAC1BAAgrQIAALMEADCuAgAAIgAQrwIAALMEADCwAgEA_AMAIbYCQAD_AwAhygIBAPwDACHPAgAAtATkAiLhAgIAnQQAIeICAQD8AwAhAgcAAIEIACAPAACECAAgCwcAAKgEACAPAAC1BAAgrQIAALMEADCuAgAAIgAQrwIAALMEADCwAgEAAAABtgJAAP8DACHKAgEA_AMAIc8CAAC0BOQCIuECAgCdBAAh4gIBAPwDACEDAAAAIgAgAQAAIwAwAgAAJAAgAQAAABcAIAEAAAAcACABAAAAIgAgAQAAABcAIAMAAAAXACABAAAZADACAAAaACABAAAAFQAgCwcAAIEIACAIAACBCAAgCQAAgggAIA4AAJ0FACAQAACDCAAg5gIAANIEACDnAgAA0gQAIOgCAADSBAAg6gIAANIEACDrAgAA0gQAIOwCAADSBAAgFAcAAKgEACAIAACkBAAgCQAAsQQAIA4AAIAEACAQAACyBAAgrQIAALAEADCuAgAAFQAQrwIAALAEADC2AkAA_wMAIcoCAQD8AwAhzQICAJ0EACHlAgEA_AMAIeYCAQD-AwAh5wIBAP4DACHoAgEA_gMAIekCAQD8AwAh6gIBAP4DACHrAgEA_gMAIewCQACfBAAhogMAAK8EACADAAAAFQAgAQAALAAwAgAALQAgAwAAABEAIAEAABIAMAIAABMAIAoHAACoBAAgFwAAqQQAIK0CAACtBAAwrgIAADAAEK8CAACtBAAwsAIBAPwDACG2AkAA_wMAIcoCAQD8AwAh1gIBAP4DACHYAgAArgTYAiIDBwAAgQgAIBcAAIAIACDWAgAA0gQAIAsHAACoBAAgFwAAqQQAIK0CAACtBAAwrgIAADAAEK8CAACtBAAwsAIBAAAAAbYCQAD_AwAhygIBAPwDACHWAgEA_gMAIdgCAACuBNgCIqEDAACsBAAgAwAAADAAIAEAADEAMAIAADIAIAEAAAAPACADAAAAIgAgAQAAIwAwAgAAJAAgCAMAAKAEACAHAACoBAAgrQIAAKsEADCuAgAANgAQrwIAAKsEADC2AkAA_wMAIckCAQD8AwAhygIBAPwDACECAwAAgAgAIAcAAIEIACAJAwAAoAQAIAcAAKgEACCtAgAAqwQAMK4CAAA2ABCvAgAAqwQAMLYCQAD_AwAhyQIBAPwDACHKAgEA_AMAIaADAACqBAAgAwAAADYAIAEAADcAMAIAADgAIBEDAACpBAAgBwAAqAQAIK0CAAClBAAwrgIAADoAEK8CAAClBAAwsAIBAPwDACG2AkAA_wMAIckCAQD-AwAhygIBAPwDACHNAgIA_QMAIc8CAACmBM8CItACAQD-AwAh0QIBAP4DACHSAgEA_gMAIdMCAQD-AwAh1AIAAKcEACDVAgEA_gMAIQoDAACACAAgBwAAgQgAIMkCAADSBAAgzQIAANIEACDQAgAA0gQAINECAADSBAAg0gIAANIEACDTAgAA0gQAINQCAADSBAAg1QIAANIEACARAwAAqQQAIAcAAKgEACCtAgAApQQAMK4CAAA6ABCvAgAApQQAMLACAQAAAAG2AkAA_wMAIckCAQD-AwAhygIBAPwDACHNAgIA_QMAIc8CAACmBM8CItACAQD-AwAh0QIBAP4DACHSAgEA_gMAIdMCAQD-AwAh1AIAAKcEACDVAgEA_gMAIQMAAAA6ACABAAA7ADACAAA8ACABAAAADwAgDAMAAKAEACAHAACkBAAgKgAAogQAIK0CAACjBAAwrgIAAD8AEK8CAACjBAAwsAIBAPwDACG2AkAA_wMAIckCAQD8AwAhygIBAP4DACHLAgIAnQQAIcwCQAD_AwAhAwMAAIAIACAHAACBCAAgygIAANIEACAMAwAAoAQAIAcAAKQEACAqAACiBAAgrQIAAKMEADCuAgAAPwAQrwIAAKMEADCwAgEAAAABtgJAAP8DACHJAgEA_AMAIcoCAQD-AwAhywICAJ0EACHMAkAA_wMAIQMAAAA_ACABAABAADACAABBACABAAAAFwAgAQAAABcAIAEAAAAVACABAAAAEQAgAQAAADAAIAEAAAAiACABAAAANgAgAQAAADoAIAEAAAA_ACADAAAAMAAgAQAAMQAwAgAAMgAgDB8AAKAEACCtAgAAoQQAMK4CAABNABCvAgAAoQQAMLACAQD8AwAhsQIBAPwDACGyAgEA_AMAIbMCAQD8AwAhtAIBAPwDACG1AgAAogQAILYCQAD_AwAhtwJAAJ8EACECHwAAgAgAILcCAADSBAAgDB8AAKAEACCtAgAAoQQAMK4CAABNABCvAgAAoQQAMLACAQAAAAGxAgEA_AMAIbICAQD8AwAhswIBAPwDACG0AgEA_AMAIbUCAACiBAAgtgJAAP8DACG3AkAAnwQAIQMAAABNACABAABOADACAABPACADAAAANgAgAQAANwAwAgAAOAAgAwAAADoAIAEAADsAMAIAADwAIAMAAAA_ACABAABAADACAABBACAPAwAAoAQAIK0CAACcBAAwrgIAAFQAEK8CAACcBAAwsAIBAPwDACG2AkAAnwQAIckCAQD8AwAh3AIBAP4DACH5AgEA_AMAIfoCAQD8AwAh-wICAJ0EACH8AgEA_AMAIf0CIACeBAAh_gIBAP4DACH_AgEA_gMAIQUDAACACAAgtgIAANIEACDcAgAA0gQAIP4CAADSBAAg_wIAANIEACAPAwAAoAQAIK0CAACcBAAwrgIAAFQAEK8CAACcBAAwsAIBAAAAAbYCQACfBAAhyQIBAPwDACHcAgEA_gMAIfkCAQD8AwAh-gIBAPwDACH7AgIAnQQAIfwCAQD8AwAh_QIgAJ4EACH-AgEA_gMAIf8CAQD-AwAhAwAAAFQAIAEAAFUAMAIAAFYAIAEAAAADACABAAAABwAgAQAAAAsAIAEAAAARACABAAAAMAAgAQAAAE0AIAEAAAA2ACABAAAAOgAgAQAAAD8AIAEAAABUACABAAAAAQAgGQQAAPYHACAFAAD3BwAgBgAA-AcAIB0AAPkHACAeAAD6BwAgIAAA-wcAICEAAPwHACAiAAD9BwAgIwAA_gcAICQAAP8HACDaAgAA0gQAINsCAADSBAAg3AIAANIEACD4AgAA0gQAII8DAADSBAAgkQMAANIEACCXAwAA0gQAIJgDAADSBAAgmQMAANIEACCaAwAA0gQAIJsDAADSBAAgnAMAANIEACCdAwAA0gQAIJ4DAADSBAAgnwMAANIEACADAAAADwAgAQAAYwAwAgAAAQAgAwAAAA8AIAEAAGMAMAIAAAEAIAMAAAAPACABAABjADACAAABACAgBAAA7AcAIAUAAO0HACAGAADuBwAgHQAA7wcAIB4AAPAHACAgAADxBwAgIQAA8gcAICIAAPMHACAjAAD0BwAgJAAA9QcAILACAQAAAAG2AkAAAAABzAJAAAAAAdoCAQAAAAHbAgIAAAAB3AIBAAAAAfgCQAAAAAGPAwEAAAABkAMgAAAAAZEDAQAAAAGTAwAAAJMDApUDAAAAlQMClgMgAAAAAZcDAQAAAAGYAwEAAAABmQOAAAAAAZoDQAAAAAGbAwEAAAABnAMgAAAAAZ0DAQAAAAGeA0AAAAABnwNAAAAAAQEqAABnACAWsAIBAAAAAbYCQAAAAAHMAkAAAAAB2gIBAAAAAdsCAgAAAAHcAgEAAAAB-AJAAAAAAY8DAQAAAAGQAyAAAAABkQMBAAAAAZMDAAAAkwMClQMAAACVAwKWAyAAAAABlwMBAAAAAZgDAQAAAAGZA4AAAAABmgNAAAAAAZsDAQAAAAGcAyAAAAABnQMBAAAAAZ4DQAAAAAGfA0AAAAABASoAAGkAMAEqAABpADAgBAAA-QYAIAUAAPoGACAGAAD7BgAgHQAA_AYAIB4AAP0GACAgAAD-BgAgIQAA_wYAICIAAIAHACAjAACBBwAgJAAAggcAILACAQDWBAAhtgJAANcEACHMAkAA1wQAIdoCAQDhBAAh2wICAOsEACHcAgEA4QQAIfgCQADYBAAhjwMBAOEEACGQAyAA2QUAIZEDAQDhBAAhkwMAAPYGkwMilQMAAPcGlQMilgMgANkFACGXAwEA4QQAIZgDAQDhBAAhmQOAAAAAAZoDQADYBAAhmwMBAOEEACGcAyAA-AYAIZ0DAQDhBAAhngNAANgEACGfA0AA2AQAIQIAAAABACAqAABsACAWsAIBANYEACG2AkAA1wQAIcwCQADXBAAh2gIBAOEEACHbAgIA6wQAIdwCAQDhBAAh-AJAANgEACGPAwEA4QQAIZADIADZBQAhkQMBAOEEACGTAwAA9gaTAyKVAwAA9waVAyKWAyAA2QUAIZcDAQDhBAAhmAMBAOEEACGZA4AAAAABmgNAANgEACGbAwEA4QQAIZwDIAD4BgAhnQMBAOEEACGeA0AA2AQAIZ8DQADYBAAhAgAAAA8AICoAAG4AIAIAAAAPACAqAABuACADAAAAAQAgMQAAZwAgMgAAbAAgAQAAAAEAIAEAAAAPACAUDAAA8QYAIDcAAPIGACA4AAD1BgAgOQAA9AYAIDoAAPMGACDaAgAA0gQAINsCAADSBAAg3AIAANIEACD4AgAA0gQAII8DAADSBAAgkQMAANIEACCXAwAA0gQAIJgDAADSBAAgmQMAANIEACCaAwAA0gQAIJsDAADSBAAgnAMAANIEACCdAwAA0gQAIJ4DAADSBAAgnwMAANIEACAZrQIAAJIEADCuAgAAdQAQrwIAAJIEADCwAgEA1AMAIbYCQADWAwAhzAJAANYDACHaAgEA4gMAIdsCAgDpAwAh3AIBAOIDACH4AkAA1wMAIY8DAQDiAwAhkAMgAIkEACGRAwEA4gMAIZMDAACTBJMDIpUDAACUBJUDIpYDIACJBAAhlwMBAOIDACGYAwEA4gMAIZkDAADrAwAgmgNAANcDACGbAwEA4gMAIZwDIACVBAAhnQMBAOIDACGeA0AA1wMAIZ8DQADXAwAhAwAAAA8AIAEAAHQAMDYAAHUAIAMAAAAPACABAABjADACAAABACABAAAABQAgAQAAAAUAIAMAAAADACABAAAEADACAAAFACADAAAAAwAgAQAABAAwAgAABQAgAwAAAAMAIAEAAAQAMAIAAAUAIA4DAADwBgAgsAIBAAAAAbYCQAAAAAHJAgEAAAABzAJAAAAAAYYDAQAAAAGHAwEAAAABiAMBAAAAAYkDAQAAAAGKA0AAAAABiwNAAAAAAYwDAQAAAAGNAwEAAAABjgMBAAAAAQEqAAB9ACANsAIBAAAAAbYCQAAAAAHJAgEAAAABzAJAAAAAAYYDAQAAAAGHAwEAAAABiAMBAAAAAYkDAQAAAAGKA0AAAAABiwNAAAAAAYwDAQAAAAGNAwEAAAABjgMBAAAAAQEqAAB_ADABKgAAfwAwDgMAAO8GACCwAgEA1gQAIbYCQADXBAAhyQIBANYEACHMAkAA1wQAIYYDAQDWBAAhhwMBANYEACGIAwEA4QQAIYkDAQDhBAAhigNAANgEACGLA0AA2AQAIYwDAQDhBAAhjQMBAOEEACGOAwEA4QQAIQIAAAAFACAqAACCAQAgDbACAQDWBAAhtgJAANcEACHJAgEA1gQAIcwCQADXBAAhhgMBANYEACGHAwEA1gQAIYgDAQDhBAAhiQMBAOEEACGKA0AA2AQAIYsDQADYBAAhjAMBAOEEACGNAwEA4QQAIY4DAQDhBAAhAgAAAAMAICoAAIQBACACAAAAAwAgKgAAhAEAIAMAAAAFACAxAAB9ACAyAACCAQAgAQAAAAUAIAEAAAADACAKDAAA7AYAIDkAAO4GACA6AADtBgAgiAMAANIEACCJAwAA0gQAIIoDAADSBAAgiwMAANIEACCMAwAA0gQAII0DAADSBAAgjgMAANIEACAQrQIAAJEEADCuAgAAiwEAEK8CAACRBAAwsAIBANQDACG2AkAA1gMAIckCAQDUAwAhzAJAANYDACGGAwEA1AMAIYcDAQDUAwAhiAMBAOIDACGJAwEA4gMAIYoDQADXAwAhiwNAANcDACGMAwEA4gMAIY0DAQDiAwAhjgMBAOIDACEDAAAAAwAgAQAAigEAMDYAAIsBACADAAAAAwAgAQAABAAwAgAABQAgAQAAAAkAIAEAAAAJACADAAAABwAgAQAACAAwAgAACQAgAwAAAAcAIAEAAAgAMAIAAAkAIAMAAAAHACABAAAIADACAAAJACAKAwAA6wYAILACAQAAAAG2AkAAAAAByQIBAAAAAcwCQAAAAAHSAgEAAAABggNAAAAAAYMDAQAAAAGEAwEAAAABhQMBAAAAAQEqAACTAQAgCbACAQAAAAG2AkAAAAAByQIBAAAAAcwCQAAAAAHSAgEAAAABggNAAAAAAYMDAQAAAAGEAwEAAAABhQMBAAAAAQEqAACVAQAwASoAAJUBADAKAwAA6gYAILACAQDWBAAhtgJAANcEACHJAgEA1gQAIcwCQADXBAAh0gIBAOEEACGCA0AA1wQAIYMDAQDWBAAhhAMBAOEEACGFAwEA4QQAIQIAAAAJACAqAACYAQAgCbACAQDWBAAhtgJAANcEACHJAgEA1gQAIcwCQADXBAAh0gIBAOEEACGCA0AA1wQAIYMDAQDWBAAhhAMBAOEEACGFAwEA4QQAIQIAAAAHACAqAACaAQAgAgAAAAcAICoAAJoBACADAAAACQAgMQAAkwEAIDIAAJgBACABAAAACQAgAQAAAAcAIAYMAADnBgAgOQAA6QYAIDoAAOgGACDSAgAA0gQAIIQDAADSBAAghQMAANIEACAMrQIAAJAEADCuAgAAoQEAEK8CAACQBAAwsAIBANQDACG2AkAA1gMAIckCAQDUAwAhzAJAANYDACHSAgEA4gMAIYIDQADWAwAhgwMBANQDACGEAwEA4gMAIYUDAQDiAwAhAwAAAAcAIAEAAKABADA2AAChAQAgAwAAAAcAIAEAAAgAMAIAAAkAIAEAAAANACABAAAADQAgAwAAAAsAIAEAAAwAMAIAAA0AIAMAAAALACABAAAMADACAAANACADAAAACwAgAQAADAAwAgAADQAgCAMAAOYGACCwAgEAAAABtgJAAAAAAckCAQAAAAHMAkAAAAABgAMBAAAAAYEDAQAAAAGCA0AAAAABASoAAKkBACAHsAIBAAAAAbYCQAAAAAHJAgEAAAABzAJAAAAAAYADAQAAAAGBAwEAAAABggNAAAAAAQEqAACrAQAwASoAAKsBADABAAAADwAgCAMAAOUGACCwAgEA1gQAIbYCQADYBAAhyQIBAOEEACHMAkAA2AQAIYADAQDWBAAhgQMBANYEACGCA0AA1wQAIQIAAAANACAqAACvAQAgB7ACAQDWBAAhtgJAANgEACHJAgEA4QQAIcwCQADYBAAhgAMBANYEACGBAwEA1gQAIYIDQADXBAAhAgAAAAsAICoAALEBACACAAAACwAgKgAAsQEAIAEAAAAPACADAAAADQAgMQAAqQEAIDIAAK8BACABAAAADQAgAQAAAAsAIAYMAADiBgAgOQAA5AYAIDoAAOMGACC2AgAA0gQAIMkCAADSBAAgzAIAANIEACAKrQIAAI8EADCuAgAAuQEAEK8CAACPBAAwsAIBANQDACG2AkAA1wMAIckCAQDiAwAhzAJAANcDACGAAwEA1AMAIYEDAQDUAwAhggNAANYDACEDAAAACwAgAQAAuAEAMDYAALkBACADAAAACwAgAQAADAAwAgAADQAgAQAAAFYAIAEAAABWACADAAAAVAAgAQAAVQAwAgAAVgAgAwAAAFQAIAEAAFUAMAIAAFYAIAMAAABUACABAABVADACAABWACAMAwAA4QYAILACAQAAAAG2AkAAAAAByQIBAAAAAdwCAQAAAAH5AgEAAAAB-gIBAAAAAfsCAgAAAAH8AgEAAAAB_QIgAAAAAf4CAQAAAAH_AgEAAAABASoAAMEBACALsAIBAAAAAbYCQAAAAAHJAgEAAAAB3AIBAAAAAfkCAQAAAAH6AgEAAAAB-wICAAAAAfwCAQAAAAH9AiAAAAAB_gIBAAAAAf8CAQAAAAEBKgAAwwEAMAEqAADDAQAwDAMAAOAGACCwAgEA1gQAIbYCQADYBAAhyQIBANYEACHcAgEA4QQAIfkCAQDWBAAh-gIBANYEACH7AgIA4AQAIfwCAQDWBAAh_QIgANkFACH-AgEA4QQAIf8CAQDhBAAhAgAAAFYAICoAAMYBACALsAIBANYEACG2AkAA2AQAIckCAQDWBAAh3AIBAOEEACH5AgEA1gQAIfoCAQDWBAAh-wICAOAEACH8AgEA1gQAIf0CIADZBQAh_gIBAOEEACH_AgEA4QQAIQIAAABUACAqAADIAQAgAgAAAFQAICoAAMgBACADAAAAVgAgMQAAwQEAIDIAAMYBACABAAAAVgAgAQAAAFQAIAkMAADbBgAgNwAA3AYAIDgAAN8GACA5AADeBgAgOgAA3QYAILYCAADSBAAg3AIAANIEACD-AgAA0gQAIP8CAADSBAAgDq0CAACOBAAwrgIAAM8BABCvAgAAjgQAMLACAQDUAwAhtgJAANcDACHJAgEA1AMAIdwCAQDiAwAh-QIBANQDACH6AgEA1AMAIfsCAgDjAwAh_AIBANQDACH9AiAAiQQAIf4CAQDiAwAh_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_AwAh2wICAAAAAdwCAQAAAAHdAgEA_gMAIQEAAACwAgAgAQAAALACACAJCwAAgAQAIK0CAAD7AwAwrgIAALMCABCvAgAA-wMAMLACAQD8AwAhtgJAAP8DACHbAgIA_QMAIdwCAQD8AwAh3QIBAP4DACEDCwAAnQUAINsCAADSBAAg3QIAANIEACADAAAAswIAIAEAALQCADACAACwAgAgAwAAALMCACABAAC0AgAwAgAAsAIAIAMAAACzAgAgAQAAtAIAMAIAALACACAGCwAAnAUAILACAQAAAAG2AkAAAAAB2wICAAAAAdwCAQAAAAHdAgEAAAABASoAALgCACAFsAIBAAAAAbYCQAAAAAHbAgIAAAAB3AIBAAAAAd0CAQAAAAEBKgAAugIAMAEqAAC6AgAwBgsAAI0FACCwAgEA1gQAIbYCQADXBAAh2wICAOsEACHcAgEA1gQAId0CAQDhBAAhAgAAALACACAqAAC9AgAgBbACAQDWBAAhtgJAANcEACHbAgIA6wQAIdwCAQDWBAAh3QIBAOEEACECAAAAswIAICoAAL8CACACAAAAswIAICoAAL8CACADAAAAsAIAIDEAALgCACAyAAC9AgAgAQAAALACACABAAAAswIAIAcMAACIBQAgNwAAiQUAIDgAAIwFACA5AACLBQAgOgAAigUAINsCAADSBAAg3QIAANIEACAIrQIAAPoDADCuAgAAxgIAEK8CAAD6AwAwsAIBANQDACG2AkAA1gMAIdsCAgDpAwAh3AIBANQDACHdAgEA4gMAIQMAAACzAgAgAQAAxQIAMDYAAMYCACADAAAAswIAIAEAALQCADACAACwAgAgAQAAABMAIAEAAAATACADAAAAEQAgAQAAEgAwAgAAEwAgAwAAABEAIAEAABIAMAIAABMAIAMAAAARACABAAASADACAAATACAGAwAAhwUAIAcAAIYFACC2AkAAAAAByQIBAAAAAcoCAQAAAAHaAgAAANoCAgEqAADOAgAgBLYCQAAAAAHJAgEAAAABygIBAAAAAdoCAAAA2gICASoAANACADABKgAA0AIAMAYDAACFBQAgBwAAhAUAILYCQADXBAAhyQIBANYEACHKAgEA1gQAIdoCAACDBdoCIgIAAAATACAqAADTAgAgBLYCQADXBAAhyQIBANYEACHKAgEA1gQAIdoCAACDBdoCIgIAAAARACAqAADVAgAgAgAAABEAICoAANUCACADAAAAEwAgMQAAzgIAIDIAANMCACABAAAAEwAgAQAAABEAIAMMAACABQAgOQAAggUAIDoAAIEFACAHrQIAAPYDADCuAgAA3AIAEK8CAAD2AwAwtgJAANYDACHJAgEA1AMAIcoCAQDUAwAh2gIAAPcD2gIiAwAAABEAIAEAANsCADA2AADcAgAgAwAAABEAIAEAABIAMAIAABMAIAEAAAAyACABAAAAMgAgAwAAADAAIAEAADEAMAIAADIAIAMAAAAwACABAAAxADACAAAyACADAAAAMAAgAQAAMQAwAgAAMgAgBwcAAP4EACAXAAD_BAAgsAIBAAAAAbYCQAAAAAHKAgEAAAAB1gIBAAAAAdgCAAAA2AICASoAAOQCACAFsAIBAAAAAbYCQAAAAAHKAgEAAAAB1gIBAAAAAdgCAAAA2AICASoAAOYCADABKgAA5gIAMAEAAAAPACAHBwAA_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_ACABAABAADACAABBACADAAAAPwAgAQAAQAAwAgAAQQAgCQMAAOQEACAHAADlBAAgKoAAAAABsAIBAAAAAbYCQAAAAAHJAgEAAAABygIBAAAAAcsCAgAAAAHMAkAAAAABASoAAKoDACAHKoAAAAABsAIBAAAAAbYCQAAAAAHJAgEAAAABygIBAAAAAcsCAgAAAAHMAkAAAAABASoAAKwDADABKgAArAMAMAEAAAAXACAJAwAA4gQAIAcAAOMEACAqgAAAAAGwAgEA1gQAIbYCQADXBAAhyQIBANYEACHKAgEA4QQAIcsCAgDgBAAhzAJAANcEACECAAAAQQAgKgAAsAMAIAcqgAAAAAGwAgEA1gQAIbYCQADXBAAhyQIBANYEACHKAgEA4QQAIcsCAgDgBAAhzAJAANcEACECAAAAPwAgKgAAsgMAIAIAAAA_ACAqAACyAwAgAQAAABcAIAMAAABBACAxAACqAwAgMgAAsAMAIAEAAABBACABAAAAPwAgBgwAANsEACA3AADcBAAgOAAA3wQAIDkAAN4EACA6AADdBAAgygIAANIEACAKKgAA1QMAIK0CAADhAwAwrgIAALoDABCvAgAA4QMAMLACAQDUAwAhtgJAANYDACHJAgEA1AMAIcoCAQDiAwAhywICAOMDACHMAkAA1gMAIQMAAAA_ACABAAC5AwAwNgAAugMAIAMAAAA_ACABAABAADACAABBACABAAAATwAgAQAAAE8AIAMAAABNACABAABOADACAABPACADAAAATQAgAQAATgAwAgAATwAgAwAAAE0AIAEAAE4AMAIAAE8AIAkfAADaBAAgsAIBAAAAAbECAQAAAAGyAgEAAAABswIBAAAAAbQCAQAAAAG1AoAAAAABtgJAAAAAAbcCQAAAAAEBKgAAwgMAIAiwAgEAAAABsQIBAAAAAbICAQAAAAGzAgEAAAABtAIBAAAAAbUCgAAAAAG2AkAAAAABtwJAAAAAAQEqAADEAwAwASoAAMQDADAJHwAA2QQAILACAQDWBAAhsQIBANYEACGyAgEA1gQAIbMCAQDWBAAhtAIBANYEACG1AoAAAAABtgJAANcEACG3AkAA2AQAIQIAAABPACAqAADHAwAgCLACAQDWBAAhsQIBANYEACGyAgEA1gQAIbMCAQDWBAAhtAIBANYEACG1AoAAAAABtgJAANcEACG3AkAA2AQAIQIAAABNACAqAADJAwAgAgAAAE0AICoAAMkDACADAAAATwAgMQAAwgMAIDIAAMcDACABAAAATwAgAQAAAE0AIAQMAADTBAAgOQAA1QQAIDoAANQEACC3AgAA0gQAIAutAgAA0wMAMK4CAADQAwAQrwIAANMDADCwAgEA1AMAIbECAQDUAwAhsgIBANQDACGzAgEA1AMAIbQCAQDUAwAhtQIAANUDACC2AkAA1gMAIbcCQADXAwAhAwAAAE0AIAEAAM8DADA2AADQAwAgAwAAAE0AIAEAAE4AMAIAAE8AIAutAgAA0wMAMK4CAADQAwAQrwIAANMDADCwAgEA1AMAIbECAQDUAwAhsgIBANQDACGzAgEA1AMAIbQCAQDUAwAhtQIAANUDACC2AkAA1gMAIbcCQADXAwAhDgwAANwDACA5AADgAwAgOgAA4AMAILgCAQAAAAG5AgEAAAAEugIBAAAABLsCAQAAAAG8AgEAAAABvQIBAAAAAb4CAQAAAAG_AgEA3wMAIcYCAQAAAAHHAgEAAAAByAIBAAAAAQ8MAADcAwAgOQAA3gMAIDoAAN4DACC4AoAAAAABuwKAAAAAAbwCgAAAAAG9AoAAAAABvgKAAAAAAb8CgAAAAAHAAgEAAAABwQIBAAAAAcICAQAAAAHDAoAAAAABxAKAAAAAAcUCgAAAAAELDAAA3AMAIDkAAN0DACA6AADdAwAguAJAAAAAAbkCQAAAAAS6AkAAAAAEuwJAAAAAAbwCQAAAAAG9AkAAAAABvgJAAAAAAb8CQADbAwAhCwwAANkDACA5AADaAwAgOgAA2gMAILgCQAAAAAG5AkAAAAAFugJAAAAABbsCQAAAAAG8AkAAAAABvQJAAAAAAb4CQAAAAAG_AkAA2AMAIQsMAADZAwAgOQAA2gMAIDoAANoDACC4AkAAAAABuQJAAAAABboCQAAAAAW7AkAAAAABvAJAAAAAAb0CQAAAAAG-AkAAAAABvwJAANgDACEIuAICAAAAAbkCAgAAAAW6AgIAAAAFuwICAAAAAbwCAgAAAAG9AgIAAAABvgICAAAAAb8CAgDZAwAhCLgCQAAAAAG5AkAAAAAFugJAAAAABbsCQAAAAAG8AkAAAAABvQJAAAAAAb4CQAAAAAG_AkAA2gMAIQsMAADcAwAgOQAA3QMAIDoAAN0DACC4AkAAAAABuQJAAAAABLoCQAAAAAS7AkAAAAABvAJAAAAAAb0CQAAAAAG-AkAAAAABvwJAANsDACEIuAICAAAAAbkCAgAAAAS6AgIAAAAEuwICAAAAAbwCAgAAAAG9AgIAAAABvgICAAAAAb8CAgDcAwAhCLgCQAAAAAG5AkAAAAAEugJAAAAABLsCQAAAAAG8AkAAAAABvQJAAAAAAb4CQAAAAAG_AkAA3QMAIQy4AoAAAAABuwKAAAAAAbwCgAAAAAG9AoAAAAABvgKAAAAAAb8CgAAAAAHAAgEAAAABwQIBAAAAAcICAQAAAAHDAoAAAAABxAKAAAAAAcUCgAAAAAEODAAA3AMAIDkAAOADACA6AADgAwAguAIBAAAAAbkCAQAAAAS6AgEAAAAEuwIBAAAAAbwCAQAAAAG9AgEAAAABvgIBAAAAAb8CAQDfAwAhxgIBAAAAAccCAQAAAAHIAgEAAAABC7gCAQAAAAG5AgEAAAAEugIBAAAABLsCAQAAAAG8AgEAAAABvQIBAAAAAb4CAQAAAAG_AgEA4AMAIcYCAQAAAAHHAgEAAAAByAIBAAAAAQoqAADVAwAgrQIAAOEDADCuAgAAugMAEK8CAADhAwAwsAIBANQDACG2AkAA1gMAIckCAQDUAwAhygIBAOIDACHLAgIA4wMAIcwCQADWAwAhDgwAANkDACA5AADnAwAgOgAA5wMAILgCAQAAAAG5AgEAAAAFugIBAAAABbsCAQAAAAG8AgEAAAABvQIBAAAAAb4CAQAAAAG_AgEA5gMAIcYCAQAAAAHHAgEAAAAByAIBAAAAAQ0MAADcAwAgNwAA5QMAIDgAANwDACA5AADcAwAgOgAA3AMAILgCAgAAAAG5AgIAAAAEugICAAAABLsCAgAAAAG8AgIAAAABvQICAAAAAb4CAgAAAAG_AgIA5AMAIQ0MAADcAwAgNwAA5QMAIDgAANwDACA5AADcAwAgOgAA3AMAILgCAgAAAAG5AgIAAAAEugICAAAABLsCAgAAAAG8AgIAAAABvQICAAAAAb4CAgAAAAG_AgIA5AMAIQi4AggAAAABuQIIAAAABLoCCAAAAAS7AggAAAABvAIIAAAAAb0CCAAAAAG-AggAAAABvwIIAOUDACEODAAA2QMAIDkAAOcDACA6AADnAwAguAIBAAAAAbkCAQAAAAW6AgEAAAAFuwIBAAAAAbwCAQAAAAG9AgEAAAABvgIBAAAAAb8CAQDmAwAhxgIBAAAAAccCAQAAAAHIAgEAAAABC7gCAQAAAAG5AgEAAAAFugIBAAAABbsCAQAAAAG8AgEAAAABvQIBAAAAAb4CAQAAAAG_AgEA5wMAIcYCAQAAAAHHAgEAAAAByAIBAAAAAQ-tAgAA6AMAMK4CAACiAwAQrwIAAOgDADCwAgEA1AMAIbYCQADWAwAhyQIBAOIDACHKAgEA1AMAIc0CAgDpAwAhzwIAAOoDzwIi0AIBAOIDACHRAgEA4gMAIdICAQDiAwAh0wIBAOIDACHUAgAA6wMAINUCAQDiAwAhDQwAANkDACA3AADwAwAgOAAA2QMAIDkAANkDACA6AADZAwAguAICAAAAAbkCAgAAAAW6AgIAAAAFuwICAAAAAbwCAgAAAAG9AgIAAAABvgICAAAAAb8CAgDvAwAhBwwAANwDACA5AADuAwAgOgAA7gMAILgCAAAAzwICuQIAAADPAgi6AgAAAM8CCL8CAADtA88CIg8MAADZAwAgOQAA7AMAIDoAAOwDACC4AoAAAAABuwKAAAAAAbwCgAAAAAG9AoAAAAABvgKAAAAAAb8CgAAAAAHAAgEAAAABwQIBAAAAAcICAQAAAAHDAoAAAAABxAKAAAAAAcUCgAAAAAEMuAKAAAAAAbsCgAAAAAG8AoAAAAABvQKAAAAAAb4CgAAAAAG_AoAAAAABwAIBAAAAAcECAQAAAAHCAgEAAAABwwKAAAAAAcQCgAAAAAHFAoAAAAABBwwAANwDACA5AADuAwAgOgAA7gMAILgCAAAAzwICuQIAAADPAgi6AgAAAM8CCL8CAADtA88CIgS4AgAAAM8CArkCAAAAzwIIugIAAADPAgi_AgAA7gPPAiINDAAA2QMAIDcAAPADACA4AADZAwAgOQAA2QMAIDoAANkDACC4AgIAAAABuQICAAAABboCAgAAAAW7AgIAAAABvAICAAAAAb0CAgAAAAG-AgIAAAABvwICAO8DACEIuAIIAAAAAbkCCAAAAAW6AggAAAAFuwIIAAAAAbwCCAAAAAG9AggAAAABvgIIAAAAAb8CCADwAwAhBq0CAADxAwAwrgIAAIoDABCvAgAA8QMAMLYCQADWAwAhyQIBANQDACHKAgEA1AMAIQitAgAA8gMAMK4CAAD0AgAQrwIAAPIDADCwAgEA1AMAIbYCQADWAwAhygIBANQDACHWAgEA4gMAIdgCAADzA9gCIgcMAADcAwAgOQAA9QMAIDoAAPUDACC4AgAAANgCArkCAAAA2AIIugIAAADYAgi_AgAA9APYAiIHDAAA3AMAIDkAAPUDACA6AAD1AwAguAIAAADYAgK5AgAAANgCCLoCAAAA2AIIvwIAAPQD2AIiBLgCAAAA2AICuQIAAADYAgi6AgAAANgCCL8CAAD1A9gCIgetAgAA9gMAMK4CAADcAgAQrwIAAPYDADC2AkAA1gMAIckCAQDUAwAhygIBANQDACHaAgAA9wPaAiIHDAAA3AMAIDkAAPkDACA6AAD5AwAguAIAAADaAgK5AgAAANoCCLoCAAAA2gIIvwIAAPgD2gIiBwwAANwDACA5AAD5AwAgOgAA-QMAILgCAAAA2gICuQIAAADaAgi6AgAAANoCCL8CAAD4A9oCIgS4AgAAANoCArkCAAAA2gIIugIAAADaAgi_AgAA-QPaAiIIrQIAAPoDADCuAgAAxgIAEK8CAAD6AwAwsAIBANQDACG2AkAA1gMAIdsCAgDpAwAh3AIBANQDACHdAgEA4gMAIQkLAACABAAgrQIAAPsDADCuAgAAswIAEK8CAAD7AwAwsAIBAPwDACG2AkAA_wMAIdsCAgD9AwAh3AIBAPwDACHdAgEA_gMAIQu4AgEAAAABuQIBAAAABLoCAQAAAAS7AgEAAAABvAIBAAAAAb0CAQAAAAG-AgEAAAABvwIBAOADACHGAgEAAAABxwIBAAAAAcgCAQAAAAEIuAICAAAAAbkCAgAAAAW6AgIAAAAFuwICAAAAAbwCAgAAAAG9AgIAAAABvgICAAAAAb8CAgDZAwAhC7gCAQAAAAG5AgEAAAAFugIBAAAABbsCAQAAAAG8AgEAAAABvQIBAAAAAb4CAQAAAAG_AgEA5wMAIcYCAQAAAAHHAgEAAAAByAIBAAAAAQi4AkAAAAABuQJAAAAABLoCQAAAAAS7AkAAAAABvAJAAAAAAb0CQAAAAAG-AkAAAAABvwJAAN0DACED3gIAABwAIN8CAAAcACDgAgAAHAAgCa0CAACBBAAwrgIAAK0CABCvAgAAgQQAMLACAQDUAwAhtgJAANYDACHKAgEA1AMAIc8CAACCBOQCIuECAgDjAwAh4gIBANQDACEHDAAA3AMAIDkAAIQEACA6AACEBAAguAIAAADkAgK5AgAAAOQCCLoCAAAA5AIIvwIAAIME5AIiBwwAANwDACA5AACEBAAgOgAAhAQAILgCAAAA5AICuQIAAADkAgi6AgAAAOQCCL8CAACDBOQCIgS4AgAAAOQCArkCAAAA5AIIugIAAADkAgi_AgAAhATkAiIHrQIAAIUEADCuAgAAlwIAEK8CAACFBAAwtgJAANYDACHKAgEA1AMAIc0CAgDjAwAh5AIBANQDACEOrQIAAIYEADCuAgAAgQIAEK8CAACGBAAwtgJAANYDACHKAgEA1AMAIc0CAgDjAwAh5QIBANQDACHmAgEA4gMAIecCAQDiAwAh6AIBAOIDACHpAgEA1AMAIeoCAQDiAwAh6wIBAOIDACHsAkAA1wMAIRKtAgAAhwQAMK4CAADrAQAQrwIAAIcEADCwAgEA1AMAIbYCQADWAwAhzAJAANYDACHbAgIA6QMAIe0CAgDpAwAh7gIBAOIDACHvAgIA6QMAIfECAACIBPECIvICIACJBAAh8wIgAIkEACH0AgIA4wMAIfUCAgDjAwAh9gICAOMDACH3AgIA4wMAIfgCQADXAwAhBwwAANwDACA5AACNBAAgOgAAjQQAILgCAAAA8QICuQIAAADxAgi6AgAAAPECCL8CAACMBPECIgUMAADcAwAgOQAAiwQAIDoAAIsEACC4AiAAAAABvwIgAIoEACEFDAAA3AMAIDkAAIsEACA6AACLBAAguAIgAAAAAb8CIACKBAAhArgCIAAAAAG_AiAAiwQAIQcMAADcAwAgOQAAjQQAIDoAAI0EACC4AgAAAPECArkCAAAA8QIIugIAAADxAgi_AgAAjATxAiIEuAIAAADxAgK5AgAAAPECCLoCAAAA8QIIvwIAAI0E8QIiDq0CAACOBAAwrgIAAM8BABCvAgAAjgQAMLACAQDUAwAhtgJAANcDACHJAgEA1AMAIdwCAQDiAwAh-QIBANQDACH6AgEA1AMAIfsCAgDjAwAh_AIBANQDACH9AiAAiQQAIf4CAQDiAwAh_wIBAOIDACEKrQIAAI8EADCuAgAAuQEAEK8CAACPBAAwsAIBANQDACG2AkAA1wMAIckCAQDiAwAhzAJAANcDACGAAwEA1AMAIYEDAQDUAwAhggNAANYDACEMrQIAAJAEADCuAgAAoQEAEK8CAACQBAAwsAIBANQDACG2AkAA1gMAIckCAQDUAwAhzAJAANYDACHSAgEA4gMAIYIDQADWAwAhgwMBANQDACGEAwEA4gMAIYUDAQDiAwAhEK0CAACRBAAwrgIAAIsBABCvAgAAkQQAMLACAQDUAwAhtgJAANYDACHJAgEA1AMAIcwCQADWAwAhhgMBANQDACGHAwEA1AMAIYgDAQDiAwAhiQMBAOIDACGKA0AA1wMAIYsDQADXAwAhjAMBAOIDACGNAwEA4gMAIY4DAQDiAwAhGa0CAACSBAAwrgIAAHUAEK8CAACSBAAwsAIBANQDACG2AkAA1gMAIcwCQADWAwAh2gIBAOIDACHbAgIA6QMAIdwCAQDiAwAh-AJAANcDACGPAwEA4gMAIZADIACJBAAhkQMBAOIDACGTAwAAkwSTAyKVAwAAlASVAyKWAyAAiQQAIZcDAQDiAwAhmAMBAOIDACGZAwAA6wMAIJoDQADXAwAhmwMBAOIDACGcAyAAlQQAIZ0DAQDiAwAhngNAANcDACGfA0AA1wMAIQcMAADcAwAgOQAAmwQAIDoAAJsEACC4AgAAAJMDArkCAAAAkwMIugIAAACTAwi_AgAAmgSTAyIHDAAA3AMAIDkAAJkEACA6AACZBAAguAIAAACVAwK5AgAAAJUDCLoCAAAAlQMIvwIAAJgElQMiBQwAANkDACA5AACXBAAgOgAAlwQAILgCIAAAAAG_AiAAlgQAIQUMAADZAwAgOQAAlwQAIDoAAJcEACC4AiAAAAABvwIgAJYEACECuAIgAAAAAb8CIACXBAAhBwwAANwDACA5AACZBAAgOgAAmQQAILgCAAAAlQMCuQIAAACVAwi6AgAAAJUDCL8CAACYBJUDIgS4AgAAAJUDArkCAAAAlQMIugIAAACVAwi_AgAAmQSVAyIHDAAA3AMAIDkAAJsEACA6AACbBAAguAIAAACTAwK5AgAAAJMDCLoCAAAAkwMIvwIAAJoEkwMiBLgCAAAAkwMCuQIAAACTAwi6AgAAAJMDCL8CAACbBJMDIg8DAACgBAAgrQIAAJwEADCuAgAAVAAQrwIAAJwEADCwAgEA_AMAIbYCQACfBAAhyQIBAPwDACHcAgEA_gMAIfkCAQD8AwAh-gIBAPwDACH7AgIAnQQAIfwCAQD8AwAh_QIgAJ4EACH-AgEA_gMAIf8CAQD-AwAhCLgCAgAAAAG5AgIAAAAEugICAAAABLsCAgAAAAG8AgIAAAABvQICAAAAAb4CAgAAAAG_AgIA3AMAIQK4AiAAAAABvwIgAIsEACEIuAJAAAAAAbkCQAAAAAW6AkAAAAAFuwJAAAAAAbwCQAAAAAG9AkAAAAABvgJAAAAAAb8CQADaAwAhJQQAAMoEACAFAADLBAAgBgAAzAQAIB0AAL4EACAeAAC_BAAgIAAAzQQAICEAAMAEACAiAADBBAAgIwAAwgQAICQAAM4EACCtAgAAxgQAMK4CAAAPABCvAgAAxgQAMLACAQD8AwAhtgJAAP8DACHMAkAA_wMAIdoCAQD-AwAh2wICAP0DACHcAgEA_gMAIfgCQACfBAAhjwMBAP4DACGQAyAAngQAIZEDAQD-AwAhkwMAAMcEkwMilQMAAMgElQMilgMgAJ4EACGXAwEA_gMAIZgDAQD-AwAhmQMAAKcEACCaA0AAnwQAIZsDAQD-AwAhnAMgAMkEACGdAwEA_gMAIZ4DQACfBAAhnwNAAJ8EACGlAwAADwAgpgMAAA8AIAwfAACgBAAgrQIAAKEEADCuAgAATQAQrwIAAKEEADCwAgEA_AMAIbECAQD8AwAhsgIBAPwDACGzAgEA_AMAIbQCAQD8AwAhtQIAAKIEACC2AkAA_wMAIbcCQACfBAAhDLgCgAAAAAG7AoAAAAABvAKAAAAAAb0CgAAAAAG-AoAAAAABvwKAAAAAAcACAQAAAAHBAgEAAAABwgIBAAAAAcMCgAAAAAHEAoAAAAABxQKAAAAAAQwDAACgBAAgBwAApAQAICoAAKIEACCtAgAAowQAMK4CAAA_ABCvAgAAowQAMLACAQD8AwAhtgJAAP8DACHJAgEA_AMAIcoCAQD-AwAhywICAJ0EACHMAkAA_wMAIR8RAAC8BAAgEgAApAQAIBMAALEEACAUAAC8BAAgFQAAvQQAIBYAAL4EACAYAAC_BAAgGQAAsgQAIBoAAMAEACAbAADBBAAgHAAAwgQAIK0CAAC6BAAwrgIAABcAEK8CAAC6BAAwsAIBAPwDACG2AkAA_wMAIcwCQAD_AwAh2wICAP0DACHtAgIA_QMAIe4CAQD-AwAh7wICAP0DACHxAgAAuwTxAiLyAiAAngQAIfMCIACeBAAh9AICAJ0EACH1AgIAnQQAIfYCAgCdBAAh9wICAJ0EACH4AkAAnwQAIaUDAAAXACCmAwAAFwAgEQMAAKkEACAHAACoBAAgrQIAAKUEADCuAgAAOgAQrwIAAKUEADCwAgEA_AMAIbYCQAD_AwAhyQIBAP4DACHKAgEA_AMAIc0CAgD9AwAhzwIAAKYEzwIi0AIBAP4DACHRAgEA_gMAIdICAQD-AwAh0wIBAP4DACHUAgAApwQAINUCAQD-AwAhBLgCAAAAzwICuQIAAADPAgi6AgAAAM8CCL8CAADuA88CIgy4AoAAAAABuwKAAAAAAbwCgAAAAAG9AoAAAAABvgKAAAAAAb8CgAAAAAHAAgEAAAABwQIBAAAAAcICAQAAAAHDAoAAAAABxAKAAAAAAcUCgAAAAAEfEQAAvAQAIBIAAKQEACATAACxBAAgFAAAvAQAIBUAAL0EACAWAAC-BAAgGAAAvwQAIBkAALIEACAaAADABAAgGwAAwQQAIBwAAMIEACCtAgAAugQAMK4CAAAXABCvAgAAugQAMLACAQD8AwAhtgJAAP8DACHMAkAA_wMAIdsCAgD9AwAh7QICAP0DACHuAgEA_gMAIe8CAgD9AwAh8QIAALsE8QIi8gIgAJ4EACHzAiAAngQAIfQCAgCdBAAh9QICAJ0EACH2AgIAnQQAIfcCAgCdBAAh-AJAAJ8EACGlAwAAFwAgpgMAABcAICUEAADKBAAgBQAAywQAIAYAAMwEACAdAAC-BAAgHgAAvwQAICAAAM0EACAhAADABAAgIgAAwQQAICMAAMIEACAkAADOBAAgrQIAAMYEADCuAgAADwAQrwIAAMYEADCwAgEA_AMAIbYCQAD_AwAhzAJAAP8DACHaAgEA_gMAIdsCAgD9AwAh3AIBAP4DACH4AkAAnwQAIY8DAQD-AwAhkAMgAJ4EACGRAwEA_gMAIZMDAADHBJMDIpUDAADIBJUDIpYDIACeBAAhlwMBAP4DACGYAwEA_gMAIZkDAACnBAAgmgNAAJ8EACGbAwEA_gMAIZwDIADJBAAhnQMBAP4DACGeA0AAnwQAIZ8DQACfBAAhpQMAAA8AIKYDAAAPACACyQIBAAAAAcoCAQAAAAEIAwAAoAQAIAcAAKgEACCtAgAAqwQAMK4CAAA2ABCvAgAAqwQAMLYCQAD_AwAhyQIBAPwDACHKAgEA_AMAIQLKAgEAAAAB1gIBAAAAAQoHAACoBAAgFwAAqQQAIK0CAACtBAAwrgIAADAAEK8CAACtBAAwsAIBAPwDACG2AkAA_wMAIcoCAQD8AwAh1gIBAP4DACHYAgAArgTYAiIEuAIAAADYAgK5AgAAANgCCLoCAAAA2AIIvwIAAPUD2AIiAsoCAQAAAAHNAgIAAAABEwcAAKgEACAIAACkBAAgCQAAsQQAIA4AAIAEACAQAACyBAAgrQIAALAEADCuAgAAFQAQrwIAALAEADC2AkAA_wMAIcoCAQD8AwAhzQICAJ0EACHlAgEA_AMAIeYCAQD-AwAh5wIBAP4DACHoAgEA_gMAIekCAQD8AwAh6gIBAP4DACHrAgEA_gMAIewCQACfBAAhA94CAAAXACDfAgAAFwAg4AIAABcAIAPeAgAAIgAg3wIAACIAIOACAAAiACALBwAAqAQAIA8AALUEACCtAgAAswQAMK4CAAAiABCvAgAAswQAMLACAQD8AwAhtgJAAP8DACHKAgEA_AMAIc8CAAC0BOQCIuECAgCdBAAh4gIBAPwDACEEuAIAAADkAgK5AgAAAOQCCLoCAAAA5AIIvwIAAIQE5AIiFQcAAKgEACAIAACkBAAgCQAAsQQAIA4AAIAEACAQAACyBAAgrQIAALAEADCuAgAAFQAQrwIAALAEADC2AkAA_wMAIcoCAQD8AwAhzQICAJ0EACHlAgEA_AMAIeYCAQD-AwAh5wIBAP4DACHoAgEA_gMAIekCAQD8AwAh6gIBAP4DACHrAgEA_gMAIewCQACfBAAhpQMAABUAIKYDAAAVACADygIBAAAAAc0CAgAAAAHkAgEAAAABCQoAALUEACANAAC4BAAgrQIAALcEADCuAgAAHAAQrwIAALcEADC2AkAA_wMAIcoCAQD8AwAhzQICAJ0EACHkAgEA_AMAIQsLAACABAAgrQIAAPsDADCuAgAAswIAEK8CAAD7AwAwsAIBAPwDACG2AkAA_wMAIdsCAgD9AwAh3AIBAPwDACHdAgEA_gMAIaUDAACzAgAgpgMAALMCACACsAIBAAAAAe0CAgAAAAEdEQAAvAQAIBIAAKQEACATAACxBAAgFAAAvAQAIBUAAL0EACAWAAC-BAAgGAAAvwQAIBkAALIEACAaAADABAAgGwAAwQQAIBwAAMIEACCtAgAAugQAMK4CAAAXABCvAgAAugQAMLACAQD8AwAhtgJAAP8DACHMAkAA_wMAIdsCAgD9AwAh7QICAP0DACHuAgEA_gMAIe8CAgD9AwAh8QIAALsE8QIi8gIgAJ4EACHzAiAAngQAIfQCAgCdBAAh9QICAJ0EACH2AgIAnQQAIfcCAgCdBAAh-AJAAJ8EACEEuAIAAADxAgK5AgAAAPECCLoCAAAA8QIIvwIAAI0E8QIiFQcAAKgEACAIAACkBAAgCQAAsQQAIA4AAIAEACAQAACyBAAgrQIAALAEADCuAgAAFQAQrwIAALAEADC2AkAA_wMAIcoCAQD8AwAhzQICAJ0EACHlAgEA_AMAIeYCAQD-AwAh5wIBAP4DACHoAgEA_gMAIekCAQD8AwAh6gIBAP4DACHrAgEA_gMAIewCQACfBAAhpQMAABUAIKYDAAAVACAD3gIAABUAIN8CAAAVACDgAgAAFQAgA94CAAARACDfAgAAEQAg4AIAABEAIAPeAgAAMAAg3wIAADAAIOACAAAwACAD3gIAADYAIN8CAAA2ACDgAgAANgAgA94CAAA6ACDfAgAAOgAg4AIAADoAIAPeAgAAPwAg3wIAAD8AIOACAAA_ACACyQIBAAAAAcoCAQAAAAEJAwAAoAQAIAcAAKgEACCtAgAAxAQAMK4CAAARABCvAgAAxAQAMLYCQAD_AwAhyQIBAPwDACHKAgEA_AMAIdoCAADFBNoCIgS4AgAAANoCArkCAAAA2gIIugIAAADaAgi_AgAA-QPaAiIjBAAAygQAIAUAAMsEACAGAADMBAAgHQAAvgQAIB4AAL8EACAgAADNBAAgIQAAwAQAICIAAMEEACAjAADCBAAgJAAAzgQAIK0CAADGBAAwrgIAAA8AEK8CAADGBAAwsAIBAPwDACG2AkAA_wMAIcwCQAD_AwAh2gIBAP4DACHbAgIA_QMAIdwCAQD-AwAh-AJAAJ8EACGPAwEA_gMAIZADIACeBAAhkQMBAP4DACGTAwAAxwSTAyKVAwAAyASVAyKWAyAAngQAIZcDAQD-AwAhmAMBAP4DACGZAwAApwQAIJoDQACfBAAhmwMBAP4DACGcAyAAyQQAIZ0DAQD-AwAhngNAAJ8EACGfA0AAnwQAIQS4AgAAAJMDArkCAAAAkwMIugIAAACTAwi_AgAAmwSTAyIEuAIAAACVAwK5AgAAAJUDCLoCAAAAlQMIvwIAAJkElQMiArgCIAAAAAG_AiAAlwQAIQPeAgAAAwAg3wIAAAMAIOACAAADACAD3gIAAAcAIN8CAAAHACDgAgAABwAgA94CAAALACDfAgAACwAg4AIAAAsAIAPeAgAATQAg3wIAAE0AIOACAABNACAD3gIAAFQAIN8CAABUACDgAgAAVAAgCwMAAKkEACCtAgAAzwQAMK4CAAALABCvAgAAzwQAMLACAQD8AwAhtgJAAJ8EACHJAgEA_gMAIcwCQACfBAAhgAMBAPwDACGBAwEA_AMAIYIDQAD_AwAhDQMAAKAEACCtAgAA0AQAMK4CAAAHABCvAgAA0AQAMLACAQD8AwAhtgJAAP8DACHJAgEA_AMAIcwCQAD_AwAh0gIBAP4DACGCA0AA_wMAIYMDAQD8AwAhhAMBAP4DACGFAwEA_gMAIREDAACgBAAgrQIAANEEADCuAgAAAwAQrwIAANEEADCwAgEA_AMAIbYCQAD_AwAhyQIBAPwDACHMAkAA_wMAIYYDAQD8AwAhhwMBAPwDACGIAwEA_gMAIYkDAQD-AwAhigNAAJ8EACGLA0AAnwQAIYwDAQD-AwAhjQMBAP4DACGOAwEA_gMAIQAAAAABqgMBAAAAAQGqA0AAAAABAaoDQAAAAAEFMQAAiwkAIDIAAI4JACCnAwAAjAkAIKgDAACNCQAgrQMAAAEAIAMxAACLCQAgpwMAAIwJACCtAwAAAQAgAAAAAAAFqgMCAAAAAbADAgAAAAGxAwIAAAABsgMCAAAAAbMDAgAAAAEBqgMBAAAAAQUxAACDCQAgMgAAiQkAIKcDAACECQAgqAMAAIgJACCtAwAAAQAgBzEAAIEJACAyAACGCQAgpwMAAIIJACCoAwAAhQkAIKsDAAAXACCsAwAAFwAgrQMAABoAIAMxAACDCQAgpwMAAIQJACCtAwAAAQAgAzEAAIEJACCnAwAAggkAIK0DAAAaACAAAAAAAAWqAwIAAAABsAMCAAAAAbEDAgAAAAGyAwIAAAABswMCAAAAAQGqAwAAAM8CAgUxAAD5CAAgMgAA_wgAIKcDAAD6CAAgqAMAAP4IACCtAwAAGgAgBzEAAPcIACAyAAD8CAAgpwMAAPgIACCoAwAA-wgAIKsDAAAPACCsAwAADwAgrQMAAAEAIAMxAAD5CAAgpwMAAPoIACCtAwAAGgAgAzEAAPcIACCnAwAA-AgAIK0DAAABACAAAAAFMQAA7wgAIDIAAPUIACCnAwAA8AgAIKgDAAD0CAAgrQMAABoAIAUxAADtCAAgMgAA8ggAIKcDAADuCAAgqAMAAPEIACCtAwAAAQAgAzEAAO8IACCnAwAA8AgAIK0DAAAaACADMQAA7QgAIKcDAADuCAAgrQMAAAEAIAAAAAGqAwAAANgCAgUxAADlCAAgMgAA6wgAIKcDAADmCAAgqAMAAOoIACCtAwAAGgAgBzEAAOMIACAyAADoCAAgpwMAAOQIACCoAwAA5wgAIKsDAAAPACCsAwAADwAgrQMAAAEAIAMxAADlCAAgpwMAAOYIACCtAwAAGgAgAzEAAOMIACCnAwAA5AgAIK0DAAABACAAAAABqgMAAADaAgIFMQAA2wgAIDIAAOEIACCnAwAA3AgAIKgDAADgCAAgrQMAABoAIAUxAADZCAAgMgAA3ggAIKcDAADaCAAgqAMAAN0IACCtAwAAAQAgAzEAANsIACCnAwAA3AgAIK0DAAAaACADMQAA2QgAIKcDAADaCAAgrQMAAAEAIAAAAAAACzEAAI4FADAyAACTBQAwpwMAAI8FADCoAwAAkAUAMKkDAACRBQAgqgMAAJIFADCrAwAAkgUAMKwDAACSBQAwrQMAAJIFADCuAwAAlAUAMK8DAACVBQAwBAoAAJsFACC2AkAAAAABygIBAAAAAc0CAgAAAAECAAAAHgAgMQAAmgUAIAMAAAAeACAxAACaBQAgMgAAmAUAIAEqAADYCAAwCgoAALUEACANAAC4BAAgrQIAALcEADCuAgAAHAAQrwIAALcEADC2AkAA_wMAIcoCAQD8AwAhzQICAJ0EACHkAgEA_AMAIaMDAAC2BAAgAgAAAB4AICoAAJgFACACAAAAlgUAICoAAJcFACAHrQIAAJUFADCuAgAAlgUAEK8CAACVBQAwtgJAAP8DACHKAgEA_AMAIc0CAgCdBAAh5AIBAPwDACEHrQIAAJUFADCuAgAAlgUAEK8CAACVBQAwtgJAAP8DACHKAgEA_AMAIc0CAgCdBAAh5AIBAPwDACEDtgJAANcEACHKAgEA1gQAIc0CAgDgBAAhBAoAAJkFACC2AkAA1wQAIcoCAQDWBAAhzQICAOAEACEFMQAA0wgAIDIAANYIACCnAwAA1AgAIKgDAADVCAAgrQMAAC0AIAQKAACbBQAgtgJAAAAAAcoCAQAAAAHNAgIAAAABAzEAANMIACCnAwAA1AgAIK0DAAAtACAEMQAAjgUAMKcDAACPBQAwqQMAAJEFACCtAwAAkgUAMAAAAAAAAAGqAwAAAOQCAgUxAADLCAAgMgAA0QgAIKcDAADMCAAgqAMAANAIACCtAwAAGgAgBTEAAMkIACAyAADOCAAgpwMAAMoIACCoAwAAzQgAIK0DAAAtACADMQAAywgAIKcDAADMCAAgrQMAABoAIAMxAADJCAAgpwMAAMoIACCtAwAALQAgAAAAAAAFMQAAxAgAIDIAAMcIACCnAwAAxQgAIKgDAADGCAAgrQMAALACACADMQAAxAgAIKcDAADFCAAgrQMAALACACAAAAAAAAUxAAClCAAgMgAAwggAIKcDAACmCAAgqAMAAMEIACCtAwAAGgAgBzEAALoGACAyAADTBgAgpwMAALsGACCoAwAA0gYAIKsDAAAXACCsAwAAFwAgrQMAABoAIAsxAADOBQAwMgAA0wUAMKcDAADPBQAwqAMAANAFADCpAwAA0QUAIKoDAADSBQAwqwMAANIFADCsAwAA0gUAMK0DAADSBQAwrgMAANQFADCvAwAA1QUAMAsxAADFBQAwMgAAyQUAMKcDAADGBQAwqAMAAMcFADCpAwAAyAUAIKoDAACSBQAwqwMAAJIFADCsAwAAkgUAMK0DAACSBQAwrgMAAMoFADCvAwAAlQUAMAsxAAC5BQAwMgAAvgUAMKcDAAC6BQAwqAMAALsFADCpAwAAvAUAIKoDAAC9BQAwqwMAAL0FADCsAwAAvQUAMK0DAAC9BQAwrgMAAL8FADCvAwAAwAUAMAUHAACmBQAgsAIBAAAAAbYCQAAAAAHPAgAAAOQCAuICAQAAAAECAAAAJAAgMQAAxAUAIAMAAAAkACAxAADEBQAgMgAAwwUAIAEqAADACAAwCwcAAKgEACAPAAC1BAAgrQIAALMEADCuAgAAIgAQrwIAALMEADCwAgEAAAABtgJAAP8DACHKAgEA_AMAIc8CAAC0BOQCIuECAgCdBAAh4gIBAPwDACECAAAAJAAgKgAAwwUAIAIAAADBBQAgKgAAwgUAIAmtAgAAwAUAMK4CAADBBQAQrwIAAMAFADCwAgEA_AMAIbYCQAD_AwAhygIBAPwDACHPAgAAtATkAiLhAgIAnQQAIeICAQD8AwAhCa0CAADABQAwrgIAAMEFABCvAgAAwAUAMLACAQD8AwAhtgJAAP8DACHKAgEA_AMAIc8CAAC0BOQCIuECAgCdBAAh4gIBAPwDACEEsAIBANYEACG2AkAA1wQAIc8CAACjBeQCIuICAQDWBAAhBQcAAKQFACCwAgEA1gQAIbYCQADXBAAhzwIAAKMF5AIi4gIBANYEACEFBwAApgUAILACAQAAAAG2AkAAAAABzwIAAADkAgLiAgEAAAABAw0AAK4FACC2AkAAAAAB5AIBAAAAAQIAAAAeACAxAADNBQAgAwAAAB4AIDEAAM0FACAyAADMBQAgASoAAL8IADACAAAAHgAgKgAAzAUAIAIAAACWBQAgKgAAywUAIAK2AkAA1wQAIeQCAQDWBAAhAw0AAK0FACC2AkAA1wQAIeQCAQDWBAAhAw0AAK4FACC2AkAAAAAB5AIBAAAAARcRAADKBgAgEgAAvAYAIBMAAL0GACAVAAC_BgAgFgAAwAYAIBgAAMEGACAZAADCBgAgGgAAwwYAIBsAAMQGACAcAADFBgAgsAIBAAAAAbYCQAAAAAHMAkAAAAAB2wICAAAAAe0CAgAAAAHxAgAAAPECAvICIAAAAAHzAiAAAAAB9AICAAAAAfUCAgAAAAH2AgIAAAAB9wICAAAAAfgCQAAAAAECAAAAGgAgMQAA0QYAIAMAAAAaACAxAADRBgAgMgAA2gUAIAEqAAC-CAAwHhEAALwEACASAACkBAAgEwAAsQQAIBQAALwEACAVAAC9BAAgFgAAvgQAIBgAAL8EACAZAACyBAAgGgAAwAQAIBsAAMEEACAcAADCBAAgrQIAALoEADCuAgAAFwAQrwIAALoEADCwAgEAAAABtgJAAP8DACHMAkAA_wMAIdsCAgAAAAHtAgIA_QMAIe4CAQD-AwAh7wICAP0DACHxAgAAuwTxAiLyAiAAngQAIfMCIACeBAAh9AICAJ0EACH1AgIAnQQAIfYCAgCdBAAh9wICAJ0EACH4AkAAnwQAIaQDAAC5BAAgAgAAABoAICoAANoFACACAAAA1gUAICoAANcFACASrQIAANUFADCuAgAA1gUAEK8CAADVBQAwsAIBAPwDACG2AkAA_wMAIcwCQAD_AwAh2wICAP0DACHtAgIA_QMAIe4CAQD-AwAh7wICAP0DACHxAgAAuwTxAiLyAiAAngQAIfMCIACeBAAh9AICAJ0EACH1AgIAnQQAIfYCAgCdBAAh9wICAJ0EACH4AkAAnwQAIRKtAgAA1QUAMK4CAADWBQAQrwIAANUFADCwAgEA_AMAIbYCQAD_AwAhzAJAAP8DACHbAgIA_QMAIe0CAgD9AwAh7gIBAP4DACHvAgIA_QMAIfECAAC7BPECIvICIACeBAAh8wIgAJ4EACH0AgIAnQQAIfUCAgCdBAAh9gICAJ0EACH3AgIAnQQAIfgCQACfBAAhDbACAQDWBAAhtgJAANcEACHMAkAA1wQAIdsCAgDrBAAh7QICAOsEACHxAgAA2AXxAiLyAiAA2QUAIfMCIADZBQAh9AICAOAEACH1AgIA4AQAIfYCAgDgBAAh9wICAOAEACH4AkAA2AQAIQGqAwAAAPECAgGqAyAAAAABFxEAANsFACASAADcBQAgEwAA3QUAIBUAAN4FACAWAADfBQAgGAAA4AUAIBkAAOEFACAaAADiBQAgGwAA4wUAIBwAAOQFACCwAgEA1gQAIbYCQADXBAAhzAJAANcEACHbAgIA6wQAIe0CAgDrBAAh8QIAANgF8QIi8gIgANkFACHzAiAA2QUAIfQCAgDgBAAh9QICAOAEACH2AgIA4AQAIfcCAgDgBAAh-AJAANgEACEHMQAArAgAIDIAALwIACCnAwAArQgAIKgDAAC7CAAgqwMAABUAIKwDAAAVACCtAwAALQAgBzEAAK8IACAyAAC5CAAgpwMAALAIACCoAwAAuAgAIKsDAAAXACCsAwAAFwAgrQMAABoAIAsxAADGBgAwMgAAzAYAMKcDAADHBgAwqAMAAMsGADCpAwAAyAYAIKoDAADSBQAwqwMAANIFADCsAwAA0gUAMK0DAADSBQAwrgMAAM0GADCvAwAA1QUAMAsxAACqBgAwMgAArwYAMKcDAACrBgAwqAMAAKwGADCpAwAArQYAIKoDAACuBgAwqwMAAK4GADCsAwAArgYAMK0DAACuBgAwrgMAALAGADCvAwAAsQYAMAsxAACeBgAwMgAAowYAMKcDAACfBgAwqAMAAKAGADCpAwAAoQYAIKoDAACiBgAwqwMAAKIGADCsAwAAogYAMK0DAACiBgAwrgMAAKQGADCvAwAApQYAMAsxAACSBgAwMgAAlwYAMKcDAACTBgAwqAMAAJQGADCpAwAAlQYAIKoDAACWBgAwqwMAAJYGADCsAwAAlgYAMK0DAACWBgAwrgMAAJgGADCvAwAAmQYAMAsxAACJBgAwMgAAjQYAMKcDAACKBgAwqAMAAIsGADCpAwAAjAYAIKoDAAC9BQAwqwMAAL0FADCsAwAAvQUAMK0DAAC9BQAwrgMAAI4GADCvAwAAwAUAMAsxAAD9BQAwMgAAggYAMKcDAAD-BQAwqAMAAP8FADCpAwAAgAYAIKoDAACBBgAwqwMAAIEGADCsAwAAgQYAMK0DAACBBgAwrgMAAIMGADCvAwAAhAYAMAsxAADxBQAwMgAA9gUAMKcDAADyBQAwqAMAAPMFADCpAwAA9AUAIKoDAAD1BQAwqwMAAPUFADCsAwAA9QUAMK0DAAD1BQAwrgMAAPcFADCvAwAA-AUAMAsxAADlBQAwMgAA6gUAMKcDAADmBQAwqAMAAOcFADCpAwAA6AUAIKoDAADpBQAwqwMAAOkFADCsAwAA6QUAMK0DAADpBQAwrgMAAOsFADCvAwAA7AUAMAcDAADkBAAgKoAAAAABsAIBAAAAAbYCQAAAAAHJAgEAAAABywICAAAAAcwCQAAAAAECAAAAQQAgMQAA8AUAIAMAAABBACAxAADwBQAgMgAA7wUAIAEqAAC3CAAwDAMAAKAEACAHAACkBAAgKgAAogQAIK0CAACjBAAwrgIAAD8AEK8CAACjBAAwsAIBAAAAAbYCQAD_AwAhyQIBAPwDACHKAgEA_gMAIcsCAgCdBAAhzAJAAP8DACECAAAAQQAgKgAA7wUAIAIAAADtBQAgKgAA7gUAIAoqAACiBAAgrQIAAOwFADCuAgAA7QUAEK8CAADsBQAwsAIBAPwDACG2AkAA_wMAIckCAQD8AwAhygIBAP4DACHLAgIAnQQAIcwCQAD_AwAhCioAAKIEACCtAgAA7AUAMK4CAADtBQAQrwIAAOwFADCwAgEA_AMAIbYCQAD_AwAhyQIBAPwDACHKAgEA_gMAIcsCAgCdBAAhzAJAAP8DACEGKoAAAAABsAIBANYEACG2AkAA1wQAIckCAQDWBAAhywICAOAEACHMAkAA1wQAIQcDAADiBAAgKoAAAAABsAIBANYEACG2AkAA1wQAIckCAQDWBAAhywICAOAEACHMAkAA1wQAIQcDAADkBAAgKoAAAAABsAIBAAAAAbYCQAAAAAHJAgEAAAABywICAAAAAcwCQAAAAAEMAwAA8AQAILACAQAAAAG2AkAAAAAByQIBAAAAAc0CAgAAAAHPAgAAAM8CAtACAQAAAAHRAgEAAAAB0gIBAAAAAdMCAQAAAAHUAoAAAAAB1QIBAAAAAQIAAAA8ACAxAAD8BQAgAwAAADwAIDEAAPwFACAyAAD7BQAgASoAALYIADARAwAAqQQAIAcAAKgEACCtAgAApQQAMK4CAAA6ABCvAgAApQQAMLACAQAAAAG2AkAA_wMAIckCAQD-AwAhygIBAPwDACHNAgIA_QMAIc8CAACmBM8CItACAQD-AwAh0QIBAP4DACHSAgEA_gMAIdMCAQD-AwAh1AIAAKcEACDVAgEA_gMAIQIAAAA8ACAqAAD7BQAgAgAAAPkFACAqAAD6BQAgD60CAAD4BQAwrgIAAPkFABCvAgAA-AUAMLACAQD8AwAhtgJAAP8DACHJAgEA_gMAIcoCAQD8AwAhzQICAP0DACHPAgAApgTPAiLQAgEA_gMAIdECAQD-AwAh0gIBAP4DACHTAgEA_gMAIdQCAACnBAAg1QIBAP4DACEPrQIAAPgFADCuAgAA-QUAEK8CAAD4BQAwsAIBAPwDACG2AkAA_wMAIckCAQD-AwAhygIBAPwDACHNAgIA_QMAIc8CAACmBM8CItACAQD-AwAh0QIBAP4DACHSAgEA_gMAIdMCAQD-AwAh1AIAAKcEACDVAgEA_gMAIQuwAgEA1gQAIbYCQADXBAAhyQIBAOEEACHNAgIA6wQAIc8CAADsBM8CItACAQDhBAAh0QIBAOEEACHSAgEA4QQAIdMCAQDhBAAh1AKAAAAAAdUCAQDhBAAhDAMAAO4EACCwAgEA1gQAIbYCQADXBAAhyQIBAOEEACHNAgIA6wQAIc8CAADsBM8CItACAQDhBAAh0QIBAOEEACHSAgEA4QQAIdMCAQDhBAAh1AKAAAAAAdUCAQDhBAAhDAMAAPAEACCwAgEAAAABtgJAAAAAAckCAQAAAAHNAgIAAAABzwIAAADPAgLQAgEAAAAB0QIBAAAAAdICAQAAAAHTAgEAAAAB1AKAAAAAAdUCAQAAAAEDAwAA9wQAILYCQAAAAAHJAgEAAAABAgAAADgAIDEAAIgGACADAAAAOAAgMQAAiAYAIDIAAIcGACABKgAAtQgAMAkDAACgBAAgBwAAqAQAIK0CAACrBAAwrgIAADYAEK8CAACrBAAwtgJAAP8DACHJAgEA_AMAIcoCAQD8AwAhoAMAAKoEACACAAAAOAAgKgAAhwYAIAIAAACFBgAgKgAAhgYAIAatAgAAhAYAMK4CAACFBgAQrwIAAIQGADC2AkAA_wMAIckCAQD8AwAhygIBAPwDACEGrQIAAIQGADCuAgAAhQYAEK8CAACEBgAwtgJAAP8DACHJAgEA_AMAIcoCAQD8AwAhArYCQADXBAAhyQIBANYEACEDAwAA9QQAILYCQADXBAAhyQIBANYEACEDAwAA9wQAILYCQAAAAAHJAgEAAAABBg8AAKcFACCwAgEAAAABtgJAAAAAAc8CAAAA5AIC4QICAAAAAeICAQAAAAECAAAAJAAgMQAAkQYAIAMAAAAkACAxAACRBgAgMgAAkAYAIAEqAAC0CAAwAgAAACQAICoAAJAGACACAAAAwQUAICoAAI8GACAFsAIBANYEACG2AkAA1wQAIc8CAACjBeQCIuECAgDgBAAh4gIBANYEACEGDwAApQUAILACAQDWBAAhtgJAANcEACHPAgAAowXkAiLhAgIA4AQAIeICAQDWBAAhBg8AAKcFACCwAgEAAAABtgJAAAAAAc8CAAAA5AIC4QICAAAAAeICAQAAAAEFFwAA_wQAILACAQAAAAG2AkAAAAAB1gIBAAAAAdgCAAAA2AICAgAAADIAIDEAAJ0GACADAAAAMgAgMQAAnQYAIDIAAJwGACABKgAAswgAMAsHAACoBAAgFwAAqQQAIK0CAACtBAAwrgIAADAAEK8CAACtBAAwsAIBAAAAAbYCQAD_AwAhygIBAPwDACHWAgEA_gMAIdgCAACuBNgCIqEDAACsBAAgAgAAADIAICoAAJwGACACAAAAmgYAICoAAJsGACAIrQIAAJkGADCuAgAAmgYAEK8CAACZBgAwsAIBAPwDACG2AkAA_wMAIcoCAQD8AwAh1gIBAP4DACHYAgAArgTYAiIIrQIAAJkGADCuAgAAmgYAEK8CAACZBgAwsAIBAPwDACG2AkAA_wMAIcoCAQD8AwAh1gIBAP4DACHYAgAArgTYAiIEsAIBANYEACG2AkAA1wQAIdYCAQDhBAAh2AIAAPsE2AIiBRcAAP0EACCwAgEA1gQAIbYCQADXBAAh1gIBAOEEACHYAgAA-wTYAiIFFwAA_wQAILACAQAAAAG2AkAAAAAB1gIBAAAAAdgCAAAA2AICBAMAAIcFACC2AkAAAAAByQIBAAAAAdoCAAAA2gICAgAAABMAIDEAAKkGACADAAAAEwAgMQAAqQYAIDIAAKgGACABKgAAsggAMAoDAACgBAAgBwAAqAQAIK0CAADEBAAwrgIAABEAEK8CAADEBAAwtgJAAP8DACHJAgEA_AMAIcoCAQD8AwAh2gIAAMUE2gIioAMAAMMEACACAAAAEwAgKgAAqAYAIAIAAACmBgAgKgAApwYAIAetAgAApQYAMK4CAACmBgAQrwIAAKUGADC2AkAA_wMAIckCAQD8AwAhygIBAPwDACHaAgAAxQTaAiIHrQIAAKUGADCuAgAApgYAEK8CAAClBgAwtgJAAP8DACHJAgEA_AMAIcoCAQD8AwAh2gIAAMUE2gIiA7YCQADXBAAhyQIBANYEACHaAgAAgwXaAiIEAwAAhQUAILYCQADXBAAhyQIBANYEACHaAgAAgwXaAiIEAwAAhwUAILYCQAAAAAHJAgEAAAAB2gIAAADaAgIOCAAAtgYAIAkAALcGACAOAAC4BgAgEAAAuQYAILYCQAAAAAHNAgIAAAAB5QIBAAAAAeYCAQAAAAHnAgEAAAAB6AIBAAAAAekCAQAAAAHqAgEAAAAB6wIBAAAAAewCQAAAAAECAAAALQAgMQAAtQYAIAMAAAAtACAxAAC1BgAgMgAAtAYAIAEqAACxCAAwFAcAAKgEACAIAACkBAAgCQAAsQQAIA4AAIAEACAQAACyBAAgrQIAALAEADCuAgAAFQAQrwIAALAEADC2AkAA_wMAIcoCAQD8AwAhzQICAJ0EACHlAgEA_AMAIeYCAQD-AwAh5wIBAP4DACHoAgEA_gMAIekCAQD8AwAh6gIBAP4DACHrAgEA_gMAIewCQACfBAAhogMAAK8EACACAAAALQAgKgAAtAYAIAIAAACyBgAgKgAAswYAIA6tAgAAsQYAMK4CAACyBgAQrwIAALEGADC2AkAA_wMAIcoCAQD8AwAhzQICAJ0EACHlAgEA_AMAIeYCAQD-AwAh5wIBAP4DACHoAgEA_gMAIekCAQD8AwAh6gIBAP4DACHrAgEA_gMAIewCQACfBAAhDq0CAACxBgAwrgIAALIGABCvAgAAsQYAMLYCQAD_AwAhygIBAPwDACHNAgIAnQQAIeUCAQD8AwAh5gIBAP4DACHnAgEA_gMAIegCAQD-AwAh6QIBAPwDACHqAgEA_gMAIesCAQD-AwAh7AJAAJ8EACEKtgJAANcEACHNAgIA4AQAIeUCAQDWBAAh5gIBAOEEACHnAgEA4QQAIegCAQDhBAAh6QIBANYEACHqAgEA4QQAIesCAQDhBAAh7AJAANgEACEOCAAAtQUAIAkAALYFACAOAAC3BQAgEAAAuAUAILYCQADXBAAhzQICAOAEACHlAgEA1gQAIeYCAQDhBAAh5wIBAOEEACHoAgEA4QQAIekCAQDWBAAh6gIBAOEEACHrAgEA4QQAIewCQADYBAAhDggAALYGACAJAAC3BgAgDgAAuAYAIBAAALkGACC2AkAAAAABzQICAAAAAeUCAQAAAAHmAgEAAAAB5wIBAAAAAegCAQAAAAHpAgEAAAAB6gIBAAAAAesCAQAAAAHsAkAAAAABAzEAALoGACCnAwAAuwYAIK0DAAAaACAEMQAAzgUAMKcDAADPBQAwqQMAANEFACCtAwAA0gUAMAQxAADFBQAwpwMAAMYFADCpAwAAyAUAIK0DAACSBQAwBDEAALkFADCnAwAAugUAMKkDAAC8BQAgrQMAAL0FADAXEgAAvAYAIBMAAL0GACAUAAC-BgAgFQAAvwYAIBYAAMAGACAYAADBBgAgGQAAwgYAIBoAAMMGACAbAADEBgAgHAAAxQYAILYCQAAAAAHMAkAAAAAB2wICAAAAAe4CAQAAAAHvAgIAAAAB8QIAAADxAgLyAiAAAAAB8wIgAAAAAfQCAgAAAAH1AgIAAAAB9gICAAAAAfcCAgAAAAH4AkAAAAABAgAAABoAIDEAALoGACADMQAArwgAIKcDAACwCAAgrQMAABoAIAQxAADGBgAwpwMAAMcGADCpAwAAyAYAIK0DAADSBQAwAzEAAKcIACCnAwAAqAgAIK0DAAAtACAEMQAAqgYAMKcDAACrBgAwqQMAAK0GACCtAwAArgYAMAQxAACeBgAwpwMAAJ8GADCpAwAAoQYAIK0DAACiBgAwBDEAAJIGADCnAwAAkwYAMKkDAACVBgAgrQMAAJYGADAEMQAAiQYAMKcDAACKBgAwqQMAAIwGACCtAwAAvQUAMAQxAAD9BQAwpwMAAP4FADCpAwAAgAYAIK0DAACBBgAwBDEAAPEFADCnAwAA8gUAMKkDAAD0BQAgrQMAAPUFADAEMQAA5QUAMKcDAADmBQAwqQMAAOgFACCtAwAA6QUAMBgRAADKBgAgEwAAvQYAIBQAAL4GACAVAAC_BgAgFgAAwAYAIBgAAMEGACAZAADCBgAgGgAAwwYAIBsAAMQGACAcAADFBgAgsAIBAAAAAbYCQAAAAAHMAkAAAAAB2wICAAAAAe0CAgAAAAHvAgIAAAAB8QIAAADxAgLyAiAAAAAB8wIgAAAAAfQCAgAAAAH1AgIAAAAB9gICAAAAAfcCAgAAAAH4AkAAAAABAgAAABoAIDEAAMkGACABKgAArggAMBgRAADKBgAgEwAAvQYAIBQAAL4GACAVAAC_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_BwAwMgAAwwcAMKcDAADABwAwqAMAAMEHADCpAwAAwgcAIKoDAACiBgAwqwMAAKIGADCsAwAAogYAMK0DAACiBgAwrgMAAMQHADCvAwAApQYAMAsxAAC2BwAwMgAAugcAMKcDAAC3BwAwqAMAALgHADCpAwAAuQcAIKoDAACWBgAwqwMAAJYGADCsAwAAlgYAMK0DAACWBgAwrgMAALsHADCvAwAAmQYAMAsxAACqBwAwMgAArwcAMKcDAACrBwAwqAMAAKwHADCpAwAArQcAIKoDAACuBwAwqwMAAK4HADCsAwAArgcAMK0DAACuBwAwrgMAALAHADCvAwAAsQcAMAsxAAChBwAwMgAApQcAMKcDAACiBwAwqAMAAKMHADCpAwAApAcAIKoDAACBBgAwqwMAAIEGADCsAwAAgQYAMK0DAACBBgAwrgMAAKYHADCvAwAAhAYAMAsxAACYBwAwMgAAnAcAMKcDAACZBwAwqAMAAJoHADCpAwAAmwcAIKoDAAD1BQAwqwMAAPUFADCsAwAA9QUAMK0DAAD1BQAwrgMAAJ0HADCvAwAA-AUAMAsxAACPBwAwMgAAkwcAMKcDAACQBwAwqAMAAJEHADCpAwAAkgcAIKoDAADpBQAwqwMAAOkFADCsAwAA6QUAMK0DAADpBQAwrgMAAJQHADCvAwAA7AUAMAsxAACDBwAwMgAAiAcAMKcDAACEBwAwqAMAAIUHADCpAwAAhgcAIKoDAACHBwAwqwMAAIcHADCsAwAAhwcAMK0DAACHBwAwrgMAAIkHADCvAwAAigcAMAqwAgEAAAABtgJAAAAAAdwCAQAAAAH5AgEAAAAB-gIBAAAAAfsCAgAAAAH8AgEAAAAB_QIgAAAAAf4CAQAAAAH_AgEAAAABAgAAAFYAIDEAAI4HACADAAAAVgAgMQAAjgcAIDIAAI0HACABKgAAkAgAMA8DAACgBAAgrQIAAJwEADCuAgAAVAAQrwIAAJwEADCwAgEAAAABtgJAAJ8EACHJAgEA_AMAIdwCAQD-AwAh-QIBAPwDACH6AgEA_AMAIfsCAgCdBAAh_AIBAPwDACH9AiAAngQAIf4CAQD-AwAh_wIBAP4DACECAAAAVgAgKgAAjQcAIAIAAACLBwAgKgAAjAcAIA6tAgAAigcAMK4CAACLBwAQrwIAAIoHADCwAgEA_AMAIbYCQACfBAAhyQIBAPwDACHcAgEA_gMAIfkCAQD8AwAh-gIBAPwDACH7AgIAnQQAIfwCAQD8AwAh_QIgAJ4EACH-AgEA_gMAIf8CAQD-AwAhDq0CAACKBwAwrgIAAIsHABCvAgAAigcAMLACAQD8AwAhtgJAAJ8EACHJAgEA_AMAIdwCAQD-AwAh-QIBAPwDACH6AgEA_AMAIfsCAgCdBAAh_AIBAPwDACH9AiAAngQAIf4CAQD-AwAh_wIBAP4DACEKsAIBANYEACG2AkAA2AQAIdwCAQDhBAAh-QIBANYEACH6AgEA1gQAIfsCAgDgBAAh_AIBANYEACH9AiAA2QUAIf4CAQDhBAAh_wIBAOEEACEKsAIBANYEACG2AkAA2AQAIdwCAQDhBAAh-QIBANYEACH6AgEA1gQAIfsCAgDgBAAh_AIBANYEACH9AiAA2QUAIf4CAQDhBAAh_wIBAOEEACEKsAIBAAAAAbYCQAAAAAHcAgEAAAAB-QIBAAAAAfoCAQAAAAH7AgIAAAAB_AIBAAAAAf0CIAAAAAH-AgEAAAAB_wIBAAAAAQcHAADlBAAgKoAAAAABsAIBAAAAAbYCQAAAAAHKAgEAAAABywICAAAAAcwCQAAAAAECAAAAQQAgMQAAlwcAIAMAAABBACAxAACXBwAgMgAAlgcAIAEqAACPCAAwAgAAAEEAICoAAJYHACACAAAA7QUAICoAAJUHACAGKoAAAAABsAIBANYEACG2AkAA1wQAIcoCAQDhBAAhywICAOAEACHMAkAA1wQAIQcHAADjBAAgKoAAAAABsAIBANYEACG2AkAA1wQAIcoCAQDhBAAhywICAOAEACHMAkAA1wQAIQcHAADlBAAgKoAAAAABsAIBAAAAAbYCQAAAAAHKAgEAAAABywICAAAAAcwCQAAAAAEMBwAA7wQAILACAQAAAAG2AkAAAAABygIBAAAAAc0CAgAAAAHPAgAAAM8CAtACAQAAAAHRAgEAAAAB0gIBAAAAAdMCAQAAAAHUAoAAAAAB1QIBAAAAAQIAAAA8ACAxAACgBwAgAwAAADwAIDEAAKAHACAyAACfBwAgASoAAI4IADACAAAAPAAgKgAAnwcAIAIAAAD5BQAgKgAAngcAIAuwAgEA1gQAIbYCQADXBAAhygIBANYEACHNAgIA6wQAIc8CAADsBM8CItACAQDhBAAh0QIBAOEEACHSAgEA4QQAIdMCAQDhBAAh1AKAAAAAAdUCAQDhBAAhDAcAAO0EACCwAgEA1gQAIbYCQADXBAAhygIBANYEACHNAgIA6wQAIc8CAADsBM8CItACAQDhBAAh0QIBAOEEACHSAgEA4QQAIdMCAQDhBAAh1AKAAAAAAdUCAQDhBAAhDAcAAO8EACCwAgEAAAABtgJAAAAAAcoCAQAAAAHNAgIAAAABzwIAAADPAgLQAgEAAAAB0QIBAAAAAdICAQAAAAHTAgEAAAAB1AKAAAAAAdUCAQAAAAEDBwAA9gQAILYCQAAAAAHKAgEAAAABAgAAADgAIDEAAKkHACADAAAAOAAgMQAAqQcAIDIAAKgHACABKgAAjQgAMAIAAAA4ACAqAACoBwAgAgAAAIUGACAqAACnBwAgArYCQADXBAAhygIBANYEACEDBwAA9AQAILYCQADXBAAhygIBANYEACEDBwAA9gQAILYCQAAAAAHKAgEAAAABB7ACAQAAAAGxAgEAAAABswIBAAAAAbQCAQAAAAG1AoAAAAABtgJAAAAAAbcCQAAAAAECAAAATwAgMQAAtQcAIAMAAABPACAxAAC1BwAgMgAAtAcAIAEqAACMCAAwDB8AAKAEACCtAgAAoQQAMK4CAABNABCvAgAAoQQAMLACAQAAAAGxAgEA_AMAIbICAQD8AwAhswIBAPwDACG0AgEA_AMAIbUCAACiBAAgtgJAAP8DACG3AkAAnwQAIQIAAABPACAqAAC0BwAgAgAAALIHACAqAACzBwAgC60CAACxBwAwrgIAALIHABCvAgAAsQcAMLACAQD8AwAhsQIBAPwDACGyAgEA_AMAIbMCAQD8AwAhtAIBAPwDACG1AgAAogQAILYCQAD_AwAhtwJAAJ8EACELrQIAALEHADCuAgAAsgcAEK8CAACxBwAwsAIBAPwDACGxAgEA_AMAIbICAQD8AwAhswIBAPwDACG0AgEA_AMAIbUCAACiBAAgtgJAAP8DACG3AkAAnwQAIQewAgEA1gQAIbECAQDWBAAhswIBANYEACG0AgEA1gQAIbUCgAAAAAG2AkAA1wQAIbcCQADYBAAhB7ACAQDWBAAhsQIBANYEACGzAgEA1gQAIbQCAQDWBAAhtQKAAAAAAbYCQADXBAAhtwJAANgEACEHsAIBAAAAAbECAQAAAAGzAgEAAAABtAIBAAAAAbUCgAAAAAG2AkAAAAABtwJAAAAAAQUHAAD-BAAgsAIBAAAAAbYCQAAAAAHKAgEAAAAB2AIAAADYAgICAAAAMgAgMQAAvgcAIAMAAAAyACAxAAC-BwAgMgAAvQcAIAEqAACLCAAwAgAAADIAICoAAL0HACACAAAAmgYAICoAALwHACAEsAIBANYEACG2AkAA1wQAIcoCAQDWBAAh2AIAAPsE2AIiBQcAAPwEACCwAgEA1gQAIbYCQADXBAAhygIBANYEACHYAgAA-wTYAiIFBwAA_gQAILACAQAAAAG2AkAAAAABygIBAAAAAdgCAAAA2AICBAcAAIYFACC2AkAAAAABygIBAAAAAdoCAAAA2gICAgAAABMAIDEAAMcHACADAAAAEwAgMQAAxwcAIDIAAMYHACABKgAAiggAMAIAAAATACAqAADGBwAgAgAAAKYGACAqAADFBwAgA7YCQADXBAAhygIBANYEACHaAgAAgwXaAiIEBwAAhAUAILYCQADXBAAhygIBANYEACHaAgAAgwXaAiIEBwAAhgUAILYCQAAAAAHKAgEAAAAB2gIAAADaAgIGsAIBAAAAAbYCQAAAAAHMAkAAAAABgAMBAAAAAYEDAQAAAAGCA0AAAAABAgAAAA0AIDEAANMHACADAAAADQAgMQAA0wcAIDIAANIHACABKgAAiQgAMAsDAACpBAAgrQIAAM8EADCuAgAACwAQrwIAAM8EADCwAgEAAAABtgJAAJ8EACHJAgEA_gMAIcwCQACfBAAhgAMBAPwDACGBAwEA_AMAIYIDQAD_AwAhAgAAAA0AICoAANIHACACAAAA0AcAICoAANEHACAKrQIAAM8HADCuAgAA0AcAEK8CAADPBwAwsAIBAPwDACG2AkAAnwQAIckCAQD-AwAhzAJAAJ8EACGAAwEA_AMAIYEDAQD8AwAhggNAAP8DACEKrQIAAM8HADCuAgAA0AcAEK8CAADPBwAwsAIBAPwDACG2AkAAnwQAIckCAQD-AwAhzAJAAJ8EACGAAwEA_AMAIYEDAQD8AwAhggNAAP8DACEGsAIBANYEACG2AkAA2AQAIcwCQADYBAAhgAMBANYEACGBAwEA1gQAIYIDQADXBAAhBrACAQDWBAAhtgJAANgEACHMAkAA2AQAIYADAQDWBAAhgQMBANYEACGCA0AA1wQAIQawAgEAAAABtgJAAAAAAcwCQAAAAAGAAwEAAAABgQMBAAAAAYIDQAAAAAEIsAIBAAAAAbYCQAAAAAHMAkAAAAAB0gIBAAAAAYIDQAAAAAGDAwEAAAABhAMBAAAAAYUDAQAAAAECAAAACQAgMQAA3wcAIAMAAAAJACAxAADfBwAgMgAA3gcAIAEqAACICAAwDQMAAKAEACCtAgAA0AQAMK4CAAAHABCvAgAA0AQAMLACAQAAAAG2AkAA_wMAIckCAQD8AwAhzAJAAP8DACHSAgEA_gMAIYIDQAD_AwAhgwMBAAAAAYQDAQD-AwAhhQMBAP4DACECAAAACQAgKgAA3gcAIAIAAADcBwAgKgAA3QcAIAytAgAA2wcAMK4CAADcBwAQrwIAANsHADCwAgEA_AMAIbYCQAD_AwAhyQIBAPwDACHMAkAA_wMAIdICAQD-AwAhggNAAP8DACGDAwEA_AMAIYQDAQD-AwAhhQMBAP4DACEMrQIAANsHADCuAgAA3AcAEK8CAADbBwAwsAIBAPwDACG2AkAA_wMAIckCAQD8AwAhzAJAAP8DACHSAgEA_gMAIYIDQAD_AwAhgwMBAPwDACGEAwEA_gMAIYUDAQD-AwAhCLACAQDWBAAhtgJAANcEACHMAkAA1wQAIdICAQDhBAAhggNAANcEACGDAwEA1gQAIYQDAQDhBAAhhQMBAOEEACEIsAIBANYEACG2AkAA1wQAIcwCQADXBAAh0gIBAOEEACGCA0AA1wQAIYMDAQDWBAAhhAMBAOEEACGFAwEA4QQAIQiwAgEAAAABtgJAAAAAAcwCQAAAAAHSAgEAAAABggNAAAAAAYMDAQAAAAGEAwEAAAABhQMBAAAAAQywAgEAAAABtgJAAAAAAcwCQAAAAAGGAwEAAAABhwMBAAAAAYgDAQAAAAGJAwEAAAABigNAAAAAAYsDQAAAAAGMAwEAAAABjQMBAAAAAY4DAQAAAAECAAAABQAgMQAA6wcAIAMAAAAFACAxAADrBwAgMgAA6gcAIAEqAACHCAAwEQMAAKAEACCtAgAA0QQAMK4CAAADABCvAgAA0QQAMLACAQAAAAG2AkAA_wMAIckCAQD8AwAhzAJAAP8DACGGAwEA_AMAIYcDAQD8AwAhiAMBAP4DACGJAwEA_gMAIYoDQACfBAAhiwNAAJ8EACGMAwEA_gMAIY0DAQD-AwAhjgMBAP4DACECAAAABQAgKgAA6gcAIAIAAADoBwAgKgAA6QcAIBCtAgAA5wcAMK4CAADoBwAQrwIAAOcHADCwAgEA_AMAIbYCQAD_AwAhyQIBAPwDACHMAkAA_wMAIYYDAQD8AwAhhwMBAPwDACGIAwEA_gMAIYkDAQD-AwAhigNAAJ8EACGLA0AAnwQAIYwDAQD-AwAhjQMBAP4DACGOAwEA_gMAIRCtAgAA5wcAMK4CAADoBwAQrwIAAOcHADCwAgEA_AMAIbYCQAD_AwAhyQIBAPwDACHMAkAA_wMAIYYDAQD8AwAhhwMBAPwDACGIAwEA_gMAIYkDAQD-AwAhigNAAJ8EACGLA0AAnwQAIYwDAQD-AwAhjQMBAP4DACGOAwEA_gMAIQywAgEA1gQAIbYCQADXBAAhzAJAANcEACGGAwEA1gQAIYcDAQDWBAAhiAMBAOEEACGJAwEA4QQAIYoDQADYBAAhiwNAANgEACGMAwEA4QQAIY0DAQDhBAAhjgMBAOEEACEMsAIBANYEACG2AkAA1wQAIcwCQADXBAAhhgMBANYEACGHAwEA1gQAIYgDAQDhBAAhiQMBAOEEACGKA0AA2AQAIYsDQADYBAAhjAMBAOEEACGNAwEA4QQAIY4DAQDhBAAhDLACAQAAAAG2AkAAAAABzAJAAAAAAYYDAQAAAAGHAwEAAAABiAMBAAAAAYkDAQAAAAGKA0AAAAABiwNAAAAAAYwDAQAAAAGNAwEAAAABjgMBAAAAAQQxAADgBwAwpwMAAOEHADCpAwAA4wcAIK0DAADkBwAwBDEAANQHADCnAwAA1QcAMKkDAADXBwAgrQMAANgHADAEMQAAyAcAMKcDAADJBwAwqQMAAMsHACCtAwAAzAcAMAQxAAC_BwAwpwMAAMAHADCpAwAAwgcAIK0DAACiBgAwBDEAALYHADCnAwAAtwcAMKkDAAC5BwAgrQMAAJYGADAEMQAAqgcAMKcDAACrBwAwqQMAAK0HACCtAwAArgcAMAQxAAChBwAwpwMAAKIHADCpAwAApAcAIK0DAACBBgAwBDEAAJgHADCnAwAAmQcAMKkDAACbBwAgrQMAAPUFADAEMQAAjwcAMKcDAACQBwAwqQMAAJIHACCtAwAA6QUAMAQxAACDBwAwpwMAAIQHADCpAwAAhgcAIK0DAACHBwAwAAAAAAAAAAAAABkEAAD2BwAgBQAA9wcAIAYAAPgHACAdAAD5BwAgHgAA-gcAICAAAPsHACAhAAD8BwAgIgAA_QcAICMAAP4HACAkAAD_BwAg2gIAANIEACDbAgAA0gQAINwCAADSBAAg-AIAANIEACCPAwAA0gQAIJEDAADSBAAglwMAANIEACCYAwAA0gQAIJkDAADSBAAgmgMAANIEACCbAwAA0gQAIJwDAADSBAAgnQMAANIEACCeAwAA0gQAIJ8DAADSBAAgEBEAAIQIACASAACBCAAgEwAAgggAIBQAAIQIACAVAACGCAAgFgAA-QcAIBgAAPoHACAZAACDCAAgGgAA_AcAIBsAAP0HACAcAAD-BwAg2wIAANIEACDtAgAA0gQAIO4CAADSBAAg7wIAANIEACD4AgAA0gQAIAAACwcAAIEIACAIAACBCAAgCQAAgggAIA4AAJ0FACAQAACDCAAg5gIAANIEACDnAgAA0gQAIOgCAADSBAAg6gIAANIEACDrAgAA0gQAIOwCAADSBAAgAwsAAJ0FACDbAgAA0gQAIN0CAADSBAAgAAywAgEAAAABtgJAAAAAAcwCQAAAAAGGAwEAAAABhwMBAAAAAYgDAQAAAAGJAwEAAAABigNAAAAAAYsDQAAAAAGMAwEAAAABjQMBAAAAAY4DAQAAAAEIsAIBAAAAAbYCQAAAAAHMAkAAAAAB0gIBAAAAAYIDQAAAAAGDAwEAAAABhAMBAAAAAYUDAQAAAAEGsAIBAAAAAbYCQAAAAAHMAkAAAAABgAMBAAAAAYEDAQAAAAGCA0AAAAABA7YCQAAAAAHKAgEAAAAB2gIAAADaAgIEsAIBAAAAAbYCQAAAAAHKAgEAAAAB2AIAAADYAgIHsAIBAAAAAbECAQAAAAGzAgEAAAABtAIBAAAAAbUCgAAAAAG2AkAAAAABtwJAAAAAAQK2AkAAAAABygIBAAAAAQuwAgEAAAABtgJAAAAAAcoCAQAAAAHNAgIAAAABzwIAAADPAgLQAgEAAAAB0QIBAAAAAdICAQAAAAHTAgEAAAAB1AKAAAAAAdUCAQAAAAEGKoAAAAABsAIBAAAAAbYCQAAAAAHKAgEAAAABywICAAAAAcwCQAAAAAEKsAIBAAAAAbYCQAAAAAHcAgEAAAAB-QIBAAAAAfoCAQAAAAH7AgIAAAAB_AIBAAAAAf0CIAAAAAH-AgEAAAAB_wIBAAAAAR8FAADtBwAgBgAA7gcAIB0AAO8HACAeAADwBwAgIAAA8QcAICEAAPIHACAiAADzBwAgIwAA9AcAICQAAPUHACCwAgEAAAABtgJAAAAAAcwCQAAAAAHaAgEAAAAB2wICAAAAAdwCAQAAAAH4AkAAAAABjwMBAAAAAZADIAAAAAGRAwEAAAABkwMAAACTAwKVAwAAAJUDApYDIAAAAAGXAwEAAAABmAMBAAAAAZkDgAAAAAGaA0AAAAABmwMBAAAAAZwDIAAAAAGdAwEAAAABngNAAAAAAZ8DQAAAAAECAAAAAQAgMQAAkQgAIAMAAAAPACAxAACRCAAgMgAAlQgAICEAAAAPACAFAAD6BgAgBgAA-wYAIB0AAPwGACAeAAD9BgAgIAAA_gYAICEAAP8GACAiAACABwAgIwAAgQcAICQAAIIHACAqAACVCAAgsAIBANYEACG2AkAA1wQAIcwCQADXBAAh2gIBAOEEACHbAgIA6wQAIdwCAQDhBAAh-AJAANgEACGPAwEA4QQAIZADIADZBQAhkQMBAOEEACGTAwAA9gaTAyKVAwAA9waVAyKWAyAA2QUAIZcDAQDhBAAhmAMBAOEEACGZA4AAAAABmgNAANgEACGbAwEA4QQAIZwDIAD4BgAhnQMBAOEEACGeA0AA2AQAIZ8DQADYBAAhHwUAAPoGACAGAAD7BgAgHQAA_AYAIB4AAP0GACAgAAD-BgAgIQAA_wYAICIAAIAHACAjAACBBwAgJAAAggcAILACAQDWBAAhtgJAANcEACHMAkAA1wQAIdoCAQDhBAAh2wICAOsEACHcAgEA4QQAIfgCQADYBAAhjwMBAOEEACGQAyAA2QUAIZEDAQDhBAAhkwMAAPYGkwMilQMAAPcGlQMilgMgANkFACGXAwEA4QQAIZgDAQDhBAAhmQOAAAAAAZoDQADYBAAhmwMBAOEEACGcAyAA-AYAIZ0DAQDhBAAhngNAANgEACGfA0AA2AQAIR8EAADsBwAgBgAA7gcAIB0AAO8HACAeAADwBwAgIAAA8QcAICEAAPIHACAiAADzBwAgIwAA9AcAICQAAPUHACCwAgEAAAABtgJAAAAAAcwCQAAAAAHaAgEAAAAB2wICAAAAAdwCAQAAAAH4AkAAAAABjwMBAAAAAZADIAAAAAGRAwEAAAABkwMAAACTAwKVAwAAAJUDApYDIAAAAAGXAwEAAAABmAMBAAAAAZkDgAAAAAGaA0AAAAABmwMBAAAAAZwDIAAAAAGdAwEAAAABngNAAAAAAZ8DQAAAAAECAAAAAQAgMQAAlggAIAMAAAAPACAxAACWCAAgMgAAmggAICEAAAAPACAEAAD5BgAgBgAA-wYAIB0AAPwGACAeAAD9BgAgIAAA_gYAICEAAP8GACAiAACABwAgIwAAgQcAICQAAIIHACAqAACaCAAgsAIBANYEACG2AkAA1wQAIcwCQADXBAAh2gIBAOEEACHbAgIA6wQAIdwCAQDhBAAh-AJAANgEACGPAwEA4QQAIZADIADZBQAhkQMBAOEEACGTAwAA9gaTAyKVAwAA9waVAyKWAyAA2QUAIZcDAQDhBAAhmAMBAOEEACGZA4AAAAABmgNAANgEACGbAwEA4QQAIZwDIAD4BgAhnQMBAOEEACGeA0AA2AQAIZ8DQADYBAAhHwQAAPkGACAGAAD7BgAgHQAA_AYAIB4AAP0GACAgAAD-BgAgIQAA_wYAICIAAIAHACAjAACBBwAgJAAAggcAILACAQDWBAAhtgJAANcEACHMAkAA1wQAIdoCAQDhBAAh2wICAOsEACHcAgEA4QQAIfgCQADYBAAhjwMBAOEEACGQAyAA2QUAIZEDAQDhBAAhkwMAAPYGkwMilQMAAPcGlQMilgMgANkFACGXAwEA4QQAIZgDAQDhBAAhmQOAAAAAAZoDQADYBAAhmwMBAOEEACGcAyAA-AYAIZ0DAQDhBAAhngNAANgEACGfA0AA2AQAIR8EAADsBwAgBQAA7QcAIB0AAO8HACAeAADwBwAgIAAA8QcAICEAAPIHACAiAADzBwAgIwAA9AcAICQAAPUHACCwAgEAAAABtgJAAAAAAcwCQAAAAAHaAgEAAAAB2wICAAAAAdwCAQAAAAH4AkAAAAABjwMBAAAAAZADIAAAAAGRAwEAAAABkwMAAACTAwKVAwAAAJUDApYDIAAAAAGXAwEAAAABmAMBAAAAAZkDgAAAAAGaA0AAAAABmwMBAAAAAZwDIAAAAAGdAwEAAAABngNAAAAAAZ8DQAAAAAECAAAAAQAgMQAAmwgAIAMAAAAPACAxAACbCAAgMgAAnwgAICEAAAAPACAEAAD5BgAgBQAA-gYAIB0AAPwGACAeAAD9BgAgIAAA_gYAICEAAP8GACAiAACABwAgIwAAgQcAICQAAIIHACAqAACfCAAgsAIBANYEACG2AkAA1wQAIcwCQADXBAAh2gIBAOEEACHbAgIA6wQAIdwCAQDhBAAh-AJAANgEACGPAwEA4QQAIZADIADZBQAhkQMBAOEEACGTAwAA9gaTAyKVAwAA9waVAyKWAyAA2QUAIZcDAQDhBAAhmAMBAOEEACGZA4AAAAABmgNAANgEACGbAwEA4QQAIZwDIAD4BgAhnQMBAOEEACGeA0AA2AQAIZ8DQADYBAAhHwQAAPkGACAFAAD6BgAgHQAA_AYAIB4AAP0GACAgAAD-BgAgIQAA_wYAICIAAIAHACAjAACBBwAgJAAAggcAILACAQDWBAAhtgJAANcEACHMAkAA1wQAIdoCAQDhBAAh2wICAOsEACHcAgEA4QQAIfgCQADYBAAhjwMBAOEEACGQAyAA2QUAIZEDAQDhBAAhkwMAAPYGkwMilQMAAPcGlQMilgMgANkFACGXAwEA4QQAIZgDAQDhBAAhmQOAAAAAAZoDQADYBAAhmwMBAOEEACGcAyAA-AYAIZ0DAQDhBAAhngNAANgEACGfA0AA2AQAIR8EAADsBwAgBQAA7QcAIAYAAO4HACAdAADvBwAgHgAA8AcAICAAAPEHACAhAADyBwAgIgAA8wcAICMAAPQHACCwAgEAAAABtgJAAAAAAcwCQAAAAAHaAgEAAAAB2wICAAAAAdwCAQAAAAH4AkAAAAABjwMBAAAAAZADIAAAAAGRAwEAAAABkwMAAACTAwKVAwAAAJUDApYDIAAAAAGXAwEAAAABmAMBAAAAAZkDgAAAAAGaA0AAAAABmwMBAAAAAZwDIAAAAAGdAwEAAAABngNAAAAAAZ8DQAAAAAECAAAAAQAgMQAAoAgAIAMAAAAPACAxAACgCAAgMgAApAgAICEAAAAPACAEAAD5BgAgBQAA-gYAIAYAAPsGACAdAAD8BgAgHgAA_QYAICAAAP4GACAhAAD_BgAgIgAAgAcAICMAAIEHACAqAACkCAAgsAIBANYEACG2AkAA1wQAIcwCQADXBAAh2gIBAOEEACHbAgIA6wQAIdwCAQDhBAAh-AJAANgEACGPAwEA4QQAIZADIADZBQAhkQMBAOEEACGTAwAA9gaTAyKVAwAA9waVAyKWAyAA2QUAIZcDAQDhBAAhmAMBAOEEACGZA4AAAAABmgNAANgEACGbAwEA4QQAIZwDIAD4BgAhnQMBAOEEACGeA0AA2AQAIZ8DQADYBAAhHwQAAPkGACAFAAD6BgAgBgAA-wYAIB0AAPwGACAeAAD9BgAgIAAA_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-gYAIAYAAPsGACAeAAD9BgAgIAAA_gYAICEAAP8GACAiAACABwAgIwAAgQcAICQAAIIHACAqAADfCAAgsAIBANYEACG2AkAA1wQAIcwCQADXBAAh2gIBAOEEACHbAgIA6wQAIdwCAQDhBAAh-AJAANgEACGPAwEA4QQAIZADIADZBQAhkQMBAOEEACGTAwAA9gaTAyKVAwAA9waVAyKWAyAA2QUAIZcDAQDhBAAhmAMBAOEEACGZA4AAAAABmgNAANgEACGbAwEA4QQAIZwDIAD4BgAhnQMBAOEEACGeA0AA2AQAIZ8DQADYBAAhHwQAAPkGACAFAAD6BgAgBgAA-wYAIB4AAP0GACAgAAD-BgAgIQAA_wYAICIAAIAHACAjAACBBwAgJAAAggcAILACAQDWBAAhtgJAANcEACHMAkAA1wQAIdoCAQDhBAAh2wICAOsEACHcAgEA4QQAIfgCQADYBAAhjwMBAOEEACGQAyAA2QUAIZEDAQDhBAAhkwMAAPYGkwMilQMAAPcGlQMilgMgANkFACGXAwEA4QQAIZgDAQDhBAAhmQOAAAAAAZoDQADYBAAhmwMBAOEEACGcAyAA-AYAIZ0DAQDhBAAhngNAANgEACGfA0AA2AQAIQMAAAAXACAxAADbCAAgMgAA4ggAIBsAAAAXACARAADbBQAgEgAA3AUAIBMAAN0FACAUAADQBgAgFQAA3gUAIBgAAOAFACAZAADhBQAgGgAA4gUAIBsAAOMFACAcAADkBQAgKgAA4ggAILACAQDWBAAhtgJAANcEACHMAkAA1wQAIdsCAgDrBAAh7QICAOsEACHuAgEA4QQAIe8CAgDrBAAh8QIAANgF8QIi8gIgANkFACHzAiAA2QUAIfQCAgDgBAAh9QICAOAEACH2AgIA4AQAIfcCAgDgBAAh-AJAANgEACEZEQAA2wUAIBIAANwFACATAADdBQAgFAAA0AYAIBUAAN4FACAYAADgBQAgGQAA4QUAIBoAAOIFACAbAADjBQAgHAAA5AUAILACAQDWBAAhtgJAANcEACHMAkAA1wQAIdsCAgDrBAAh7QICAOsEACHuAgEA4QQAIe8CAgDrBAAh8QIAANgF8QIi8gIgANkFACHzAiAA2QUAIfQCAgDgBAAh9QICAOAEACH2AgIA4AQAIfcCAgDgBAAh-AJAANgEACEfBAAA7AcAIAUAAO0HACAGAADuBwAgHQAA7wcAICAAAPEHACAhAADyBwAgIgAA8wcAICMAAPQHACAkAAD1BwAgsAIBAAAAAbYCQAAAAAHMAkAAAAAB2gIBAAAAAdsCAgAAAAHcAgEAAAAB-AJAAAAAAY8DAQAAAAGQAyAAAAABkQMBAAAAAZMDAAAAkwMClQMAAACVAwKWAyAAAAABlwMBAAAAAZgDAQAAAAGZA4AAAAABmgNAAAAAAZsDAQAAAAGcAyAAAAABnQMBAAAAAZ4DQAAAAAGfA0AAAAABAgAAAAEAIDEAAOMIACAZEQAAygYAIBIAALwGACATAAC9BgAgFAAAvgYAIBUAAL8GACAWAADABgAgGQAAwgYAIBoAAMMGACAbAADEBgAgHAAAxQYAILACAQAAAAG2AkAAAAABzAJAAAAAAdsCAgAAAAHtAgIAAAAB7gIBAAAAAe8CAgAAAAHxAgAAAPECAvICIAAAAAHzAiAAAAAB9AICAAAAAfUCAgAAAAH2AgIAAAAB9wICAAAAAfgCQAAAAAECAAAAGgAgMQAA5QgAIAMAAAAPACAxAADjCAAgMgAA6QgAICEAAAAPACAEAAD5BgAgBQAA-gYAIAYAAPsGACAdAAD8BgAgIAAA_gYAICEAAP8GACAiAACABwAgIwAAgQcAICQAAIIHACAqAADpCAAgsAIBANYEACG2AkAA1wQAIcwCQADXBAAh2gIBAOEEACHbAgIA6wQAIdwCAQDhBAAh-AJAANgEACGPAwEA4QQAIZADIADZBQAhkQMBAOEEACGTAwAA9gaTAyKVAwAA9waVAyKWAyAA2QUAIZcDAQDhBAAhmAMBAOEEACGZA4AAAAABmgNAANgEACGbAwEA4QQAIZwDIAD4BgAhnQMBAOEEACGeA0AA2AQAIZ8DQADYBAAhHwQAAPkGACAFAAD6BgAgBgAA-wYAIB0AAPwGACAgAAD-BgAgIQAA_wYAICIAAIAHACAjAACBBwAgJAAAggcAILACAQDWBAAhtgJAANcEACHMAkAA1wQAIdoCAQDhBAAh2wICAOsEACHcAgEA4QQAIfgCQADYBAAhjwMBAOEEACGQAyAA2QUAIZEDAQDhBAAhkwMAAPYGkwMilQMAAPcGlQMilgMgANkFACGXAwEA4QQAIZgDAQDhBAAhmQOAAAAAAZoDQADYBAAhmwMBAOEEACGcAyAA-AYAIZ0DAQDhBAAhngNAANgEACGfA0AA2AQAIQMAAAAXACAxAADlCAAgMgAA7AgAIBsAAAAXACARAADbBQAgEgAA3AUAIBMAAN0FACAUAADQBgAgFQAA3gUAIBYAAN8FACAZAADhBQAgGgAA4gUAIBsAAOMFACAcAADkBQAgKgAA7AgAILACAQDWBAAhtgJAANcEACHMAkAA1wQAIdsCAgDrBAAh7QICAOsEACHuAgEA4QQAIe8CAgDrBAAh8QIAANgF8QIi8gIgANkFACHzAiAA2QUAIfQCAgDgBAAh9QICAOAEACH2AgIA4AQAIfcCAgDgBAAh-AJAANgEACEZEQAA2wUAIBIAANwFACATAADdBQAgFAAA0AYAIBUAAN4FACAWAADfBQAgGQAA4QUAIBoAAOIFACAbAADjBQAgHAAA5AUAILACAQDWBAAhtgJAANcEACHMAkAA1wQAIdsCAgDrBAAh7QICAOsEACHuAgEA4QQAIe8CAgDrBAAh8QIAANgF8QIi8gIgANkFACHzAiAA2QUAIfQCAgDgBAAh9QICAOAEACH2AgIA4AQAIfcCAgDgBAAh-AJAANgEACEfBAAA7AcAIAUAAO0HACAGAADuBwAgHQAA7wcAIB4AAPAHACAgAADxBwAgIgAA8wcAICMAAPQHACAkAAD1BwAgsAIBAAAAAbYCQAAAAAHMAkAAAAAB2gIBAAAAAdsCAgAAAAHcAgEAAAAB-AJAAAAAAY8DAQAAAAGQAyAAAAABkQMBAAAAAZMDAAAAkwMClQMAAACVAwKWAyAAAAABlwMBAAAAAZgDAQAAAAGZA4AAAAABmgNAAAAAAZsDAQAAAAGcAyAAAAABnQMBAAAAAZ4DQAAAAAGfA0AAAAABAgAAAAEAIDEAAO0IACAZEQAAygYAIBIAALwGACATAAC9BgAgFAAAvgYAIBUAAL8GACAWAADABgAgGAAAwQYAIBkAAMIGACAbAADEBgAgHAAAxQYAILACAQAAAAG2AkAAAAABzAJAAAAAAdsCAgAAAAHtAgIAAAAB7gIBAAAAAe8CAgAAAAHxAgAAAPECAvICIAAAAAHzAiAAAAAB9AICAAAAAfUCAgAAAAH2AgIAAAAB9wICAAAAAfgCQAAAAAECAAAAGgAgMQAA7wgAIAMAAAAPACAxAADtCAAgMgAA8wgAICEAAAAPACAEAAD5BgAgBQAA-gYAIAYAAPsGACAdAAD8BgAgHgAA_QYAICAAAP4GACAiAACABwAgIwAAgQcAICQAAIIHACAqAADzCAAgsAIBANYEACG2AkAA1wQAIcwCQADXBAAh2gIBAOEEACHbAgIA6wQAIdwCAQDhBAAh-AJAANgEACGPAwEA4QQAIZADIADZBQAhkQMBAOEEACGTAwAA9gaTAyKVAwAA9waVAyKWAyAA2QUAIZcDAQDhBAAhmAMBAOEEACGZA4AAAAABmgNAANgEACGbAwEA4QQAIZwDIAD4BgAhnQMBAOEEACGeA0AA2AQAIZ8DQADYBAAhHwQAAPkGACAFAAD6BgAgBgAA-wYAIB0AAPwGACAeAAD9BgAgIAAA_gYAICIAAIAHACAjAACBBwAgJAAAggcAILACAQDWBAAhtgJAANcEACHMAkAA1wQAIdoCAQDhBAAh2wICAOsEACHcAgEA4QQAIfgCQADYBAAhjwMBAOEEACGQAyAA2QUAIZEDAQDhBAAhkwMAAPYGkwMilQMAAPcGlQMilgMgANkFACGXAwEA4QQAIZgDAQDhBAAhmQOAAAAAAZoDQADYBAAhmwMBAOEEACGcAyAA-AYAIZ0DAQDhBAAhngNAANgEACGfA0AA2AQAIQMAAAAXACAxAADvCAAgMgAA9ggAIBsAAAAXACARAADbBQAgEgAA3AUAIBMAAN0FACAUAADQBgAgFQAA3gUAIBYAAN8FACAYAADgBQAgGQAA4QUAIBsAAOMFACAcAADkBQAgKgAA9ggAILACAQDWBAAhtgJAANcEACHMAkAA1wQAIdsCAgDrBAAh7QICAOsEACHuAgEA4QQAIe8CAgDrBAAh8QIAANgF8QIi8gIgANkFACHzAiAA2QUAIfQCAgDgBAAh9QICAOAEACH2AgIA4AQAIfcCAgDgBAAh-AJAANgEACEZEQAA2wUAIBIAANwFACATAADdBQAgFAAA0AYAIBUAAN4FACAWAADfBQAgGAAA4AUAIBkAAOEFACAbAADjBQAgHAAA5AUAILACAQDWBAAhtgJAANcEACHMAkAA1wQAIdsCAgDrBAAh7QICAOsEACHuAgEA4QQAIe8CAgDrBAAh8QIAANgF8QIi8gIgANkFACHzAiAA2QUAIfQCAgDgBAAh9QICAOAEACH2AgIA4AQAIfcCAgDgBAAh-AJAANgEACEfBAAA7AcAIAUAAO0HACAGAADuBwAgHQAA7wcAIB4AAPAHACAgAADxBwAgIQAA8gcAICMAAPQHACAkAAD1BwAgsAIBAAAAAbYCQAAAAAHMAkAAAAAB2gIBAAAAAdsCAgAAAAHcAgEAAAAB-AJAAAAAAY8DAQAAAAGQAyAAAAABkQMBAAAAAZMDAAAAkwMClQMAAACVAwKWAyAAAAABlwMBAAAAAZgDAQAAAAGZA4AAAAABmgNAAAAAAZsDAQAAAAGcAyAAAAABnQMBAAAAAZ4DQAAAAAGfA0AAAAABAgAAAAEAIDEAAPcIACAZEQAAygYAIBIAALwGACATAAC9BgAgFAAAvgYAIBUAAL8GACAWAADABgAgGAAAwQYAIBkAAMIGACAaAADDBgAgHAAAxQYAILACAQAAAAG2AkAAAAABzAJAAAAAAdsCAgAAAAHtAgIAAAAB7gIBAAAAAe8CAgAAAAHxAgAAAPECAvICIAAAAAHzAiAAAAAB9AICAAAAAfUCAgAAAAH2AgIAAAAB9wICAAAAAfgCQAAAAAECAAAAGgAgMQAA-QgAIAMAAAAPACAxAAD3CAAgMgAA_QgAICEAAAAPACAEAAD5BgAgBQAA-gYAIAYAAPsGACAdAAD8BgAgHgAA_QYAICAAAP4GACAhAAD_BgAgIwAAgQcAICQAAIIHACAqAAD9CAAgsAIBANYEACG2AkAA1wQAIcwCQADXBAAh2gIBAOEEACHbAgIA6wQAIdwCAQDhBAAh-AJAANgEACGPAwEA4QQAIZADIADZBQAhkQMBAOEEACGTAwAA9gaTAyKVAwAA9waVAyKWAyAA2QUAIZcDAQDhBAAhmAMBAOEEACGZA4AAAAABmgNAANgEACGbAwEA4QQAIZwDIAD4BgAhnQMBAOEEACGeA0AA2AQAIZ8DQADYBAAhHwQAAPkGACAFAAD6BgAgBgAA-wYAIB0AAPwGACAeAAD9BgAgIAAA_gYAICEAAP8GACAjAACBBwAgJAAAggcAILACAQDWBAAhtgJAANcEACHMAkAA1wQAIdoCAQDhBAAh2wICAOsEACHcAgEA4QQAIfgCQADYBAAhjwMBAOEEACGQAyAA2QUAIZEDAQDhBAAhkwMAAPYGkwMilQMAAPcGlQMilgMgANkFACGXAwEA4QQAIZgDAQDhBAAhmQOAAAAAAZoDQADYBAAhmwMBAOEEACGcAyAA-AYAIZ0DAQDhBAAhngNAANgEACGfA0AA2AQAIQMAAAAXACAxAAD5CAAgMgAAgAkAIBsAAAAXACARAADbBQAgEgAA3AUAIBMAAN0FACAUAADQBgAgFQAA3gUAIBYAAN8FACAYAADgBQAgGQAA4QUAIBoAAOIFACAcAADkBQAgKgAAgAkAILACAQDWBAAhtgJAANcEACHMAkAA1wQAIdsCAgDrBAAh7QICAOsEACHuAgEA4QQAIe8CAgDrBAAh8QIAANgF8QIi8gIgANkFACHzAiAA2QUAIfQCAgDgBAAh9QICAOAEACH2AgIA4AQAIfcCAgDgBAAh-AJAANgEACEZEQAA2wUAIBIAANwFACATAADdBQAgFAAA0AYAIBUAAN4FACAWAADfBQAgGAAA4AUAIBkAAOEFACAaAADiBQAgHAAA5AUAILACAQDWBAAhtgJAANcEACHMAkAA1wQAIdsCAgDrBAAh7QICAOsEACHuAgEA4QQAIe8CAgDrBAAh8QIAANgF8QIi8gIgANkFACHzAiAA2QUAIfQCAgDgBAAh9QICAOAEACH2AgIA4AQAIfcCAgDgBAAh-AJAANgEACEZEQAAygYAIBIAALwGACATAAC9BgAgFAAAvgYAIBUAAL8GACAWAADABgAgGAAAwQYAIBkAAMIGACAaAADDBgAgGwAAxAYAILACAQAAAAG2AkAAAAABzAJAAAAAAdsCAgAAAAHtAgIAAAAB7gIBAAAAAe8CAgAAAAHxAgAAAPECAvICIAAAAAHzAiAAAAAB9AICAAAAAfUCAgAAAAH2AgIAAAAB9wICAAAAAfgCQAAAAAECAAAAGgAgMQAAgQkAIB8EAADsBwAgBQAA7QcAIAYAAO4HACAdAADvBwAgHgAA8AcAICAAAPEHACAhAADyBwAgIgAA8wcAICQAAPUHACCwAgEAAAABtgJAAAAAAcwCQAAAAAHaAgEAAAAB2wICAAAAAdwCAQAAAAH4AkAAAAABjwMBAAAAAZADIAAAAAGRAwEAAAABkwMAAACTAwKVAwAAAJUDApYDIAAAAAGXAwEAAAABmAMBAAAAAZkDgAAAAAGaA0AAAAABmwMBAAAAAZwDIAAAAAGdAwEAAAABngNAAAAAAZ8DQAAAAAECAAAAAQAgMQAAgwkAIAMAAAAXACAxAACBCQAgMgAAhwkAIBsAAAAXACARAADbBQAgEgAA3AUAIBMAAN0FACAUAADQBgAgFQAA3gUAIBYAAN8FACAYAADgBQAgGQAA4QUAIBoAAOIFACAbAADjBQAgKgAAhwkAILACAQDWBAAhtgJAANcEACHMAkAA1wQAIdsCAgDrBAAh7QICAOsEACHuAgEA4QQAIe8CAgDrBAAh8QIAANgF8QIi8gIgANkFACHzAiAA2QUAIfQCAgDgBAAh9QICAOAEACH2AgIA4AQAIfcCAgDgBAAh-AJAANgEACEZEQAA2wUAIBIAANwFACATAADdBQAgFAAA0AYAIBUAAN4FACAWAADfBQAgGAAA4AUAIBkAAOEFACAaAADiBQAgGwAA4wUAILACAQDWBAAhtgJAANcEACHMAkAA1wQAIdsCAgDrBAAh7QICAOsEACHuAgEA4QQAIe8CAgDrBAAh8QIAANgF8QIi8gIgANkFACHzAiAA2QUAIfQCAgDgBAAh9QICAOAEACH2AgIA4AQAIfcCAgDgBAAh-AJAANgEACEDAAAADwAgMQAAgwkAIDIAAIoJACAhAAAADwAgBAAA-QYAIAUAAPoGACAGAAD7BgAgHQAA_AYAIB4AAP0GACAgAAD-BgAgIQAA_wYAICIAAIAHACAkAACCBwAgKgAAigkAILACAQDWBAAhtgJAANcEACHMAkAA1wQAIdoCAQDhBAAh2wICAOsEACHcAgEA4QQAIfgCQADYBAAhjwMBAOEEACGQAyAA2QUAIZEDAQDhBAAhkwMAAPYGkwMilQMAAPcGlQMilgMgANkFACGXAwEA4QQAIZgDAQDhBAAhmQOAAAAAAZoDQADYBAAhmwMBAOEEACGcAyAA-AYAIZ0DAQDhBAAhngNAANgEACGfA0AA2AQAIR8EAAD5BgAgBQAA-gYAIAYAAPsGACAdAAD8BgAgHgAA_QYAICAAAP4GACAhAAD_BgAgIgAAgAcAICQAAIIHACCwAgEA1gQAIbYCQADXBAAhzAJAANcEACHaAgEA4QQAIdsCAgDrBAAh3AIBAOEEACH4AkAA2AQAIY8DAQDhBAAhkAMgANkFACGRAwEA4QQAIZMDAAD2BpMDIpUDAAD3BpUDIpYDIADZBQAhlwMBAOEEACGYAwEA4QQAIZkDgAAAAAGaA0AA2AQAIZsDAQDhBAAhnAMgAPgGACGdAwEA4QQAIZ4DQADYBAAhnwNAANgEACEfBAAA7AcAIAUAAO0HACAGAADuBwAgHQAA7wcAIB4AAPAHACAhAADyBwAgIgAA8wcAICMAAPQHACAkAAD1BwAgsAIBAAAAAbYCQAAAAAHMAkAAAAAB2gIBAAAAAdsCAgAAAAHcAgEAAAAB-AJAAAAAAY8DAQAAAAGQAyAAAAABkQMBAAAAAZMDAAAAkwMClQMAAACVAwKWAyAAAAABlwMBAAAAAZgDAQAAAAGZA4AAAAABmgNAAAAAAZsDAQAAAAGcAyAAAAABnQMBAAAAAZ4DQAAAAAGfA0AAAAABAgAAAAEAIDEAAIsJACADAAAADwAgMQAAiwkAIDIAAI8JACAhAAAADwAgBAAA-QYAIAUAAPoGACAGAAD7BgAgHQAA_AYAIB4AAP0GACAhAAD_BgAgIgAAgAcAICMAAIEHACAkAACCBwAgKgAAjwkAILACAQDWBAAhtgJAANcEACHMAkAA1wQAIdoCAQDhBAAh2wICAOsEACHcAgEA4QQAIfgCQADYBAAhjwMBAOEEACGQAyAA2QUAIZEDAQDhBAAhkwMAAPYGkwMilQMAAPcGlQMilgMgANkFACGXAwEA4QQAIZgDAQDhBAAhmQOAAAAAAZoDQADYBAAhmwMBAOEEACGcAyAA-AYAIZ0DAQDhBAAhngNAANgEACGfA0AA2AQAIR8EAAD5BgAgBQAA-gYAIAYAAPsGACAdAAD8BgAgHgAA_QYAICEAAP8GACAiAACABwAgIwAAgQcAICQAAIIHACCwAgEA1gQAIbYCQADXBAAhzAJAANcEACHaAgEA4QQAIdsCAgDrBAAh3AIBAOEEACH4AkAA2AQAIY8DAQDhBAAhkAMgANkFACGRAwEA4QQAIZMDAAD2BpMDIpUDAAD3BpUDIpYDIADZBQAhlwMBAOEEACGYAwEA4QQAIZkDgAAAAAGaA0AA2AQAIZsDAQDhBAAhnAMgAPgGACGdAwEA4QQAIZ4DQADYBAAhnwNAANgEACELBAYCBQoDBg4EDAAUHRQFHkwNIFASIVEOIlIPI1MQJFcTAQMAAQEDAAEBAxABAgMAAQcABgwMABERFgcSKQYTKgYUKwcVLgcWLwUYMw0ZNQsaOQ4bPQ8cQhAGBwAGCBgGCRsGDAAMDh8IECULAgoABw0ACQILIAgMAAoBCyEAAgcABg8ABwMJJgAOJwAQKAACBwAGFzQBAgMAAQcABgIDPgEHAAYCAwABB0MGCBNEABVFABZGABhHABlIABpJABtKABxLAAEfAAEBAwABCgRYAAVZAAZaAB1bAB5cACBdACFeACJfACNgACRhAAAAAAUMABk3ABo4ABs5ABw6AB0AAAAAAAUMABk3ABo4ABs5ABw6AB0BAwABAQMAAQMMACI5ACM6ACQAAAADDAAiOQAjOgAkAQMAAQEDAAEDDAApOQAqOgArAAAAAwwAKTkAKjoAKwEDrgEBAQO0AQEDDAAwOQAxOgAyAAAAAwwAMDkAMToAMgEDAAEBAwABBQwANzcAODgAOTkAOjoAOwAAAAAABQwANzcAODgAOTkAOjoAOwMR3AEHEt0BBhTeAQcDEeQBBxLlAQYU5gEHBQwAQDcAQTgAQjkAQzoARAAAAAAABQwAQDcAQTgAQjkAQzoARAEHAAYBBwAGBQwASTcASjgASzkATDoATQAAAAAABQwASTcASjgASzkATDoATQIKAAcNAAkCCgAHDQAJBQwAUjcAUzgAVDkAVToAVgAAAAAABQwAUjcAUzgAVDkAVToAVgIHAAYPAAcCBwAGDwAHBQwAWzcAXDgAXTkAXjoAXwAAAAAABQwAWzcAXDgAXTkAXjoAXwAABQwAZDcAZTgAZjkAZzoAaAAAAAAABQwAZDcAZTgAZjkAZzoAaAIDAAEHAAYCAwABBwAGAwwAbTkAbjoAbwAAAAMMAG05AG46AG8CBwAGF-kCAQIHAAYX7wIBAwwAdDkAdToAdgAAAAMMAHQ5AHU6AHYCAwABBwAGAgMAAQcABgMMAHs5AHw6AH0AAAADDAB7OQB8OgB9AgOXAwEHAAYCA50DAQcABgUMAIIBNwCDATgAhAE5AIUBOgCGAQAAAAAABQwAggE3AIMBOACEATkAhQE6AIYBAgMAAQevAwYCAwABB7UDBgUMAIsBNwCMATgAjQE5AI4BOgCPAQAAAAAABQwAiwE3AIwBOACNATkAjgE6AI8BAR8AAQEfAAEDDACUATkAlQE6AJYBAAAAAwwAlAE5AJUBOgCWASUCASZiASdkAShlASlmAStoASxqFS1rFi5tAS9vFTBwFzNxATRyATVzFTt2GDx3Hj14Aj55Aj96AkB7AkF8AkJ-AkOAARVEgQEfRYMBAkaFARVHhgEgSIcBAkmIAQJKiQEVS4wBIUyNASVNjgEDTo8BA0-QAQNQkQEDUZIBA1KUAQNTlgEVVJcBJlWZAQNWmwEVV5wBJ1idAQNZngEDWp8BFVuiAShcowEsXaQBBF6lAQRfpgEEYKcBBGGoAQRiqgEEY6wBFWStAS1lsAEEZrIBFWezAS5otQEEabYBBGq3ARVrugEvbLsBM228ARNuvQETb74BE3C_ARNxwAETcsIBE3PEARV0xQE0dccBE3bJARV3ygE1eMsBE3nMARN6zQEVe9ABNnzRATx90gEGftMBBn_UAQaAAdUBBoEB1gEGggHYAQaDAdoBFYQB2wE9hQHgAQaGAeIBFYcB4wE-iAHnAQaJAegBBooB6QEViwHsAT-MAe0BRY0B7gEHjgHvAQePAfABB5AB8QEHkQHyAQeSAfQBB5MB9gEVlAH3AUaVAfkBB5YB-wEVlwH8AUeYAf0BB5kB_gEHmgH_ARWbAYICSJwBgwJOnQGEAgieAYUCCJ8BhgIIoAGHAgihAYgCCKIBigIIowGMAhWkAY0CT6UBjwIIpgGRAhWnAZICUKgBkwIIqQGUAgiqAZUCFasBmAJRrAGZAletAZoCC64BmwILrwGcAguwAZ0CC7EBngILsgGgAguzAaICFbQBowJYtQGlAgu2AacCFbcBqAJZuAGpAgu5AaoCC7oBqwIVuwGuAlq8Aa8CYL0BsQIJvgGyAgm_AbUCCcABtgIJwQG3AgnCAbkCCcMBuwIVxAG8AmHFAb4CCcYBwAIVxwHBAmLIAcICCckBwwIJygHEAhXLAccCY8wByAJpzQHJAgXOAcoCBc8BywIF0AHMAgXRAc0CBdIBzwIF0wHRAhXUAdICatUB1AIF1gHWAhXXAdcCa9gB2AIF2QHZAgXaAdoCFdsB3QJs3AHeAnDdAd8CDd4B4AIN3wHhAg3gAeICDeEB4wIN4gHlAg3jAecCFeQB6AJx5QHrAg3mAe0CFecB7gJy6AHwAg3pAfECDeoB8gIV6wH1AnPsAfYCd-0B9wIO7gH4Ag7vAfkCDvAB-gIO8QH7Ag7yAf0CDvMB_wIV9AGAA3j1AYIDDvYBhAMV9wGFA3n4AYYDDvkBhwMO-gGIAxX7AYsDevwBjAN-_QGNAw_-AY4DD_8BjwMPgAKQAw-BApEDD4ICkwMPgwKVAxWEApYDf4UCmQMPhgKbAxWHApwDgAGIAp4DD4kCnwMPigKgAxWLAqMDgQGMAqQDhwGNAqUDEI4CpgMQjwKnAxCQAqgDEJECqQMQkgKrAxCTAq0DFZQCrgOIAZUCsQMQlgKzAxWXArQDiQGYArYDEJkCtwMQmgK4AxWbArsDigGcArwDkAGdAr0DEp4CvgMSnwK_AxKgAsADEqECwQMSogLDAxKjAsUDFaQCxgORAaUCyAMSpgLKAxWnAssDkgGoAswDEqkCzQMSqgLOAxWrAtEDkwGsAtIDlwE" + strings: JSON.parse("[\"where\",\"orderBy\",\"cursor\",\"user\",\"accounts\",\"sessions\",\"verifications\",\"model\",\"latestOfModel\",\"parentOfModels\",\"modelVersion\",\"modelVersions\",\"_count\",\"tag\",\"tags\",\"taggedVersion\",\"taggedAdditionalFiles\",\"latestVersion\",\"parentModel\",\"childModels\",\"parentVersion\",\"versions\",\"authors\",\"granteeUser\",\"permissions\",\"additionalFiles\",\"likes\",\"interactions\",\"drafts\",\"parent\",\"replies\",\"modelComment\",\"comments\",\"authoredModels\",\"grantedPermissions\",\"actor\",\"events\",\"modelLikes\",\"modelInteractions\",\"modelDrafts\",\"commentLikes\",\"passkeys\",\"User.findUnique\",\"User.findUniqueOrThrow\",\"User.findFirst\",\"User.findFirstOrThrow\",\"User.findMany\",\"data\",\"User.createOne\",\"User.createMany\",\"User.createManyAndReturn\",\"User.updateOne\",\"User.updateMany\",\"User.updateManyAndReturn\",\"create\",\"update\",\"User.upsertOne\",\"User.deleteOne\",\"User.deleteMany\",\"having\",\"_avg\",\"_sum\",\"_min\",\"_max\",\"User.groupBy\",\"User.aggregate\",\"Account.findUnique\",\"Account.findUniqueOrThrow\",\"Account.findFirst\",\"Account.findFirstOrThrow\",\"Account.findMany\",\"Account.createOne\",\"Account.createMany\",\"Account.createManyAndReturn\",\"Account.updateOne\",\"Account.updateMany\",\"Account.updateManyAndReturn\",\"Account.upsertOne\",\"Account.deleteOne\",\"Account.deleteMany\",\"Account.groupBy\",\"Account.aggregate\",\"Session.findUnique\",\"Session.findUniqueOrThrow\",\"Session.findFirst\",\"Session.findFirstOrThrow\",\"Session.findMany\",\"Session.createOne\",\"Session.createMany\",\"Session.createManyAndReturn\",\"Session.updateOne\",\"Session.updateMany\",\"Session.updateManyAndReturn\",\"Session.upsertOne\",\"Session.deleteOne\",\"Session.deleteMany\",\"Session.groupBy\",\"Session.aggregate\",\"Verification.findUnique\",\"Verification.findUniqueOrThrow\",\"Verification.findFirst\",\"Verification.findFirstOrThrow\",\"Verification.findMany\",\"Verification.createOne\",\"Verification.createMany\",\"Verification.createManyAndReturn\",\"Verification.updateOne\",\"Verification.updateMany\",\"Verification.updateManyAndReturn\",\"Verification.upsertOne\",\"Verification.deleteOne\",\"Verification.deleteMany\",\"Verification.groupBy\",\"Verification.aggregate\",\"Passkey.findUnique\",\"Passkey.findUniqueOrThrow\",\"Passkey.findFirst\",\"Passkey.findFirstOrThrow\",\"Passkey.findMany\",\"Passkey.createOne\",\"Passkey.createMany\",\"Passkey.createManyAndReturn\",\"Passkey.updateOne\",\"Passkey.updateMany\",\"Passkey.updateManyAndReturn\",\"Passkey.upsertOne\",\"Passkey.deleteOne\",\"Passkey.deleteMany\",\"Passkey.groupBy\",\"Passkey.aggregate\",\"Model.findUnique\",\"Model.findUniqueOrThrow\",\"Model.findFirst\",\"Model.findFirstOrThrow\",\"Model.findMany\",\"Model.createOne\",\"Model.createMany\",\"Model.createManyAndReturn\",\"Model.updateOne\",\"Model.updateMany\",\"Model.updateManyAndReturn\",\"Model.upsertOne\",\"Model.deleteOne\",\"Model.deleteMany\",\"Model.groupBy\",\"Model.aggregate\",\"ModelVersion.findUnique\",\"ModelVersion.findUniqueOrThrow\",\"ModelVersion.findFirst\",\"ModelVersion.findFirstOrThrow\",\"ModelVersion.findMany\",\"ModelVersion.createOne\",\"ModelVersion.createMany\",\"ModelVersion.createManyAndReturn\",\"ModelVersion.updateOne\",\"ModelVersion.updateMany\",\"ModelVersion.updateManyAndReturn\",\"ModelVersion.upsertOne\",\"ModelVersion.deleteOne\",\"ModelVersion.deleteMany\",\"ModelVersion.groupBy\",\"ModelVersion.aggregate\",\"ModelVersionTag.findUnique\",\"ModelVersionTag.findUniqueOrThrow\",\"ModelVersionTag.findFirst\",\"ModelVersionTag.findFirstOrThrow\",\"ModelVersionTag.findMany\",\"ModelVersionTag.createOne\",\"ModelVersionTag.createMany\",\"ModelVersionTag.createManyAndReturn\",\"ModelVersionTag.updateOne\",\"ModelVersionTag.updateMany\",\"ModelVersionTag.updateManyAndReturn\",\"ModelVersionTag.upsertOne\",\"ModelVersionTag.deleteOne\",\"ModelVersionTag.deleteMany\",\"ModelVersionTag.groupBy\",\"ModelVersionTag.aggregate\",\"ModelAdditionalFile.findUnique\",\"ModelAdditionalFile.findUniqueOrThrow\",\"ModelAdditionalFile.findFirst\",\"ModelAdditionalFile.findFirstOrThrow\",\"ModelAdditionalFile.findMany\",\"ModelAdditionalFile.createOne\",\"ModelAdditionalFile.createMany\",\"ModelAdditionalFile.createManyAndReturn\",\"ModelAdditionalFile.updateOne\",\"ModelAdditionalFile.updateMany\",\"ModelAdditionalFile.updateManyAndReturn\",\"ModelAdditionalFile.upsertOne\",\"ModelAdditionalFile.deleteOne\",\"ModelAdditionalFile.deleteMany\",\"ModelAdditionalFile.groupBy\",\"ModelAdditionalFile.aggregate\",\"Tag.findUnique\",\"Tag.findUniqueOrThrow\",\"Tag.findFirst\",\"Tag.findFirstOrThrow\",\"Tag.findMany\",\"Tag.createOne\",\"Tag.createMany\",\"Tag.createManyAndReturn\",\"Tag.updateOne\",\"Tag.updateMany\",\"Tag.updateManyAndReturn\",\"Tag.upsertOne\",\"Tag.deleteOne\",\"Tag.deleteMany\",\"Tag.groupBy\",\"Tag.aggregate\",\"ModelAuthor.findUnique\",\"ModelAuthor.findUniqueOrThrow\",\"ModelAuthor.findFirst\",\"ModelAuthor.findFirstOrThrow\",\"ModelAuthor.findMany\",\"ModelAuthor.createOne\",\"ModelAuthor.createMany\",\"ModelAuthor.createManyAndReturn\",\"ModelAuthor.updateOne\",\"ModelAuthor.updateMany\",\"ModelAuthor.updateManyAndReturn\",\"ModelAuthor.upsertOne\",\"ModelAuthor.deleteOne\",\"ModelAuthor.deleteMany\",\"ModelAuthor.groupBy\",\"ModelAuthor.aggregate\",\"ModelPermission.findUnique\",\"ModelPermission.findUniqueOrThrow\",\"ModelPermission.findFirst\",\"ModelPermission.findFirstOrThrow\",\"ModelPermission.findMany\",\"ModelPermission.createOne\",\"ModelPermission.createMany\",\"ModelPermission.createManyAndReturn\",\"ModelPermission.updateOne\",\"ModelPermission.updateMany\",\"ModelPermission.updateManyAndReturn\",\"ModelPermission.upsertOne\",\"ModelPermission.deleteOne\",\"ModelPermission.deleteMany\",\"ModelPermission.groupBy\",\"ModelPermission.aggregate\",\"ModelLike.findUnique\",\"ModelLike.findUniqueOrThrow\",\"ModelLike.findFirst\",\"ModelLike.findFirstOrThrow\",\"ModelLike.findMany\",\"ModelLike.createOne\",\"ModelLike.createMany\",\"ModelLike.createManyAndReturn\",\"ModelLike.updateOne\",\"ModelLike.updateMany\",\"ModelLike.updateManyAndReturn\",\"ModelLike.upsertOne\",\"ModelLike.deleteOne\",\"ModelLike.deleteMany\",\"ModelLike.groupBy\",\"ModelLike.aggregate\",\"ModelInteraction.findUnique\",\"ModelInteraction.findUniqueOrThrow\",\"ModelInteraction.findFirst\",\"ModelInteraction.findFirstOrThrow\",\"ModelInteraction.findMany\",\"ModelInteraction.createOne\",\"ModelInteraction.createMany\",\"ModelInteraction.createManyAndReturn\",\"ModelInteraction.updateOne\",\"ModelInteraction.updateMany\",\"ModelInteraction.updateManyAndReturn\",\"ModelInteraction.upsertOne\",\"ModelInteraction.deleteOne\",\"ModelInteraction.deleteMany\",\"ModelInteraction.groupBy\",\"ModelInteraction.aggregate\",\"ModelDraft.findUnique\",\"ModelDraft.findUniqueOrThrow\",\"ModelDraft.findFirst\",\"ModelDraft.findFirstOrThrow\",\"ModelDraft.findMany\",\"ModelDraft.createOne\",\"ModelDraft.createMany\",\"ModelDraft.createManyAndReturn\",\"ModelDraft.updateOne\",\"ModelDraft.updateMany\",\"ModelDraft.updateManyAndReturn\",\"ModelDraft.upsertOne\",\"ModelDraft.deleteOne\",\"ModelDraft.deleteMany\",\"ModelDraft.groupBy\",\"ModelDraft.aggregate\",\"ModelComment.findUnique\",\"ModelComment.findUniqueOrThrow\",\"ModelComment.findFirst\",\"ModelComment.findFirstOrThrow\",\"ModelComment.findMany\",\"ModelComment.createOne\",\"ModelComment.createMany\",\"ModelComment.createManyAndReturn\",\"ModelComment.updateOne\",\"ModelComment.updateMany\",\"ModelComment.updateManyAndReturn\",\"ModelComment.upsertOne\",\"ModelComment.deleteOne\",\"ModelComment.deleteMany\",\"ModelComment.groupBy\",\"ModelComment.aggregate\",\"ModelCommentLike.findUnique\",\"ModelCommentLike.findUniqueOrThrow\",\"ModelCommentLike.findFirst\",\"ModelCommentLike.findFirstOrThrow\",\"ModelCommentLike.findMany\",\"ModelCommentLike.createOne\",\"ModelCommentLike.createMany\",\"ModelCommentLike.createManyAndReturn\",\"ModelCommentLike.updateOne\",\"ModelCommentLike.updateMany\",\"ModelCommentLike.updateManyAndReturn\",\"ModelCommentLike.upsertOne\",\"ModelCommentLike.deleteOne\",\"ModelCommentLike.deleteMany\",\"ModelCommentLike.groupBy\",\"ModelCommentLike.aggregate\",\"Event.findUnique\",\"Event.findUniqueOrThrow\",\"Event.findFirst\",\"Event.findFirstOrThrow\",\"Event.findMany\",\"Event.createOne\",\"Event.createMany\",\"Event.createManyAndReturn\",\"Event.updateOne\",\"Event.updateMany\",\"Event.updateManyAndReturn\",\"Event.upsertOne\",\"Event.deleteOne\",\"Event.deleteMany\",\"Event.groupBy\",\"Event.aggregate\",\"AND\",\"OR\",\"NOT\",\"id\",\"type\",\"actorId\",\"resourceType\",\"resourceId\",\"payload\",\"createdAt\",\"processedAt\",\"equals\",\"in\",\"notIn\",\"lt\",\"lte\",\"gt\",\"gte\",\"not\",\"string_contains\",\"string_starts_with\",\"string_ends_with\",\"array_starts_with\",\"array_ends_with\",\"array_contains\",\"contains\",\"startsWith\",\"endsWith\",\"modelCommentId\",\"userId\",\"legacyId\",\"parentId\",\"modelId\",\"versionNumber\",\"content\",\"likesCount\",\"updatedAt\",\"editedAt\",\"deletedAt\",\"schemaVersion\",\"ModelInteractionKind\",\"kind\",\"sessionId\",\"ipHash\",\"userAgent\",\"referer\",\"geo\",\"cookie\",\"granteeUserId\",\"PermissionLevel\",\"permissionLevel\",\"AuthorRole\",\"role\",\"name\",\"displayName\",\"every\",\"some\",\"none\",\"taggedVersionNumber\",\"fileKey\",\"ModelFileKind\",\"tagId\",\"title\",\"description\",\"changeSummary\",\"previewImageFileKey\",\"netlogoFileKey\",\"netlogoVersion\",\"infoTab\",\"finalizedAt\",\"latestVersionNumber\",\"parentModelId\",\"parentVersionNumber\",\"ModelVisibility\",\"visibility\",\"isEndorsed\",\"isLibraryModel\",\"viewCount\",\"runCount\",\"downloadCount\",\"shareCount\",\"publicKey\",\"credentialID\",\"counter\",\"deviceType\",\"backedUp\",\"transports\",\"aaguid\",\"identifier\",\"value\",\"expiresAt\",\"token\",\"ipAddress\",\"impersonatedBy\",\"accountId\",\"providerId\",\"accessToken\",\"refreshToken\",\"accessTokenExpiresAt\",\"refreshTokenExpiresAt\",\"scope\",\"idToken\",\"password\",\"email\",\"emailVerified\",\"image\",\"SystemRole\",\"systemRole\",\"UserKind\",\"userKind\",\"isProfilePublic\",\"bio\",\"country\",\"socialLinks\",\"dob\",\"affiliation\",\"banned\",\"banReason\",\"banExpires\",\"onboardedAt\",\"modelCommentId_userId\",\"modelId_userId\",\"modelId_granteeUserId\",\"modelId_versionNumber\",\"modelId_versionNumber_tagId\",\"id_latestVersionNumber\",\"is\",\"isNot\",\"connectOrCreate\",\"upsert\",\"createMany\",\"set\",\"disconnect\",\"delete\",\"connect\",\"updateMany\",\"deleteMany\",\"increment\",\"decrement\",\"multiply\",\"divide\"]"), + graph: "ygqqAaACJQQAAJUFACAFAACWBQAgBgAAlwUAICAAAO4EACAhAACJBQAgIgAAigUAICQAAJgFACAlAACLBQAgJgAAjAUAICcAAI0FACAoAADvBAAgKQAAmQUAINICAACRBQAw0wIAAA8AENQCAACRBQAw1QIBAAAAAdsCQADDBAAh8AICAAAAAfYCQADDBAAh-AJAAOMEACGGAwEAwgQAIYcDAQDCBAAhuQMBAAAAAboDIADiBAAhuwMBAMIEACG9AwAAkgW9AyK_AwAAkwW_AyLAAyAA4gQAIcEDAQDCBAAhwgMBAMIEACHDAwAA9AQAIMQDQADjBAAhxQMBAMIEACHGAyAAlAUAIccDAQDCBAAhyANAAOMEACHJA0AA4wQAIQEAAAABACARAwAA5AQAINICAACcBQAw0wIAAAMAENQCAACcBQAw1QIBAMAEACHbAkAAwwQAIe8CAQDABAAh9gJAAMMEACGwAwEAwAQAIbEDAQDABAAhsgMBAMIEACGzAwEAwgQAIbQDQADjBAAhtQNAAOMEACG2AwEAwgQAIbcDAQDCBAAhuAMBAMIEACEIAwAAnAkAILIDAACdBQAgswMAAJ0FACC0AwAAnQUAILUDAACdBQAgtgMAAJ0FACC3AwAAnQUAILgDAACdBQAgEQMAAOQEACDSAgAAnAUAMNMCAAADABDUAgAAnAUAMNUCAQAAAAHbAkAAwwQAIe8CAQDABAAh9gJAAMMEACGwAwEAwAQAIbEDAQDABAAhsgMBAMIEACGzAwEAwgQAIbQDQADjBAAhtQNAAOMEACG2AwEAwgQAIbcDAQDCBAAhuAMBAMIEACEDAAAAAwAgAQAABAAwAgAABQAgDQMAAOQEACDSAgAAmwUAMNMCAAAHABDUAgAAmwUAMNUCAQDABAAh2wJAAMMEACHvAgEAwAQAIfYCQADDBAAh_gIBAMIEACGsA0AAwwQAIa0DAQDABAAhrgMBAMIEACGvAwEAwgQAIQQDAACcCQAg_gIAAJ0FACCuAwAAnQUAIK8DAACdBQAgDQMAAOQEACDSAgAAmwUAMNMCAAAHABDUAgAAmwUAMNUCAQAAAAHbAkAAwwQAIe8CAQDABAAh9gJAAMMEACH-AgEAwgQAIawDQADDBAAhrQMBAAAAAa4DAQDCBAAhrwMBAMIEACEDAAAABwAgAQAACAAwAgAACQAgCwMAAOwEACDSAgAAmgUAMNMCAAALABDUAgAAmgUAMNUCAQDABAAh2wJAAOMEACHvAgEAwgQAIfYCQADjBAAhqgMBAMAEACGrAwEAwAQAIawDQADDBAAhBAMAAJwJACDbAgAAnQUAIO8CAACdBQAg9gIAAJ0FACALAwAA7AQAINICAACaBQAw0wIAAAsAENQCAACaBQAw1QIBAAAAAdsCQADjBAAh7wIBAMIEACH2AkAA4wQAIaoDAQDABAAhqwMBAMAEACGsA0AAwwQAIQMAAAALACABAAAMADACAAANACAlBAAAlQUAIAUAAJYFACAGAACXBQAgIAAA7gQAICEAAIkFACAiAACKBQAgJAAAmAUAICUAAIsFACAmAACMBQAgJwAAjQUAICgAAO8EACApAACZBQAg0gIAAJEFADDTAgAADwAQ1AIAAJEFADDVAgEAwAQAIdsCQADDBAAh8AICAMEEACH2AkAAwwQAIfgCQADjBAAhhgMBAMIEACGHAwEAwgQAIbkDAQDCBAAhugMgAOIEACG7AwEAwgQAIb0DAACSBb0DIr8DAACTBb8DIsADIADiBAAhwQMBAMIEACHCAwEAwgQAIcMDAAD0BAAgxANAAOMEACHFAwEAwgQAIcYDIACUBQAhxwMBAMIEACHIA0AA4wQAIckDQADjBAAhAQAAAA8AIAkDAADkBAAgBwAA6wQAINICAACPBQAw0wIAABEAENQCAACPBQAw2wJAAMMEACHvAgEAwAQAIfICAQDABAAhhgMAAJAFhgMiAgMAAJwJACAHAACeCQAgCgMAAOQEACAHAADrBAAg0gIAAI8FADDTAgAAEQAQ1AIAAI8FADDbAkAAwwQAIe8CAQDABAAh8gIBAMAEACGGAwAAkAWGAyLLAwAAjgUAIAMAAAARACABAAASADACAAATACATBwAA6wQAIAgAAPEEACAJAAD8BAAgDgAAxAQAIBAAAP0EACDSAgAA-wQAMNMCAAAVABDUAgAA-wQAMNsCQADDBAAh8gIBAMAEACHzAgIA4QQAIZADAQDABAAhkQMBAMIEACGSAwEAwgQAIZMDAQDCBAAhlAMBAMAEACGVAwEAwgQAIZYDAQDCBAAhlwNAAOMEACEBAAAAFQAgHhEAAIcFACASAADxBAAgEwAA_AQAIBQAAIcFACAVAACIBQAgFgAAiQUAIBgAAIoFACAZAAD9BAAgGgAAiwUAIBsAAIwFACAcAACNBQAgIAAA7gQAINICAACFBQAw0wIAABcAENQCAACFBQAw1QIBAMAEACHbAkAAwwQAIfACAgDBBAAh9gJAAMMEACH4AkAA4wQAIZgDAgDBBAAhmQMBAMIEACGaAwIAwQQAIZwDAACGBZwDIp0DIADiBAAhngMgAOIEACGfAwIA4QQAIaADAgDhBAAhoQMCAOEEACGiAwIA4QQAIQEAAAAXACAREQAAoQkAIBIAAJ4JACATAACfCQAgFAAAoQkAIBUAAKMJACAWAACTCQAgGAAAlAkAIBkAAKAJACAaAACWCQAgGwAAlwkAIBwAAJgJACAgAACZCQAg8AIAAJ0FACD4AgAAnQUAIJgDAACdBQAgmQMAAJ0FACCaAwAAnQUAIB8RAACHBQAgEgAA8QQAIBMAAPwEACAUAACHBQAgFQAAiAUAIBYAAIkFACAYAACKBQAgGQAA_QQAIBoAAIsFACAbAACMBQAgHAAAjQUAICAAAO4EACDSAgAAhQUAMNMCAAAXABDUAgAAhQUAMNUCAQAAAAHbAkAAwwQAIfACAgAAAAH2AkAAwwQAIfgCQADjBAAhmAMCAMEEACGZAwEAwgQAIZoDAgDBBAAhnAMAAIYFnAMinQMgAOIEACGeAyAA4gQAIZ8DAgDhBAAhoAMCAOEEACGhAwIA4QQAIaIDAgDhBAAhzwMAAIQFACADAAAAFwAgAQAAGQAwAgAAGgAgCQoAAIAFACANAACDBQAg0gIAAIIFADDTAgAAHAAQ1AIAAIIFADDbAkAAwwQAIfICAQDABAAh8wICAOEEACGPAwEAwAQAIQIKAAChCQAgDQAAogkAIAoKAACABQAgDQAAgwUAINICAACCBQAw0wIAABwAENQCAACCBQAw2wJAAMMEACHyAgEAwAQAIfMCAgDhBAAhjwMBAMAEACHOAwAAgQUAIAMAAAAcACABAAAdADACAAAeACADAAAAHAAgAQAAHQAwAgAAHgAgAQAAABwAIAsHAADrBAAgDwAAgAUAINICAAD-BAAw0wIAACIAENQCAAD-BAAw1QIBAMAEACHbAkAAwwQAIfICAQDABAAh-wIAAP8EjwMijAMCAOEEACGNAwEAwAQAIQIHAACeCQAgDwAAoQkAIAsHAADrBAAgDwAAgAUAINICAAD-BAAw0wIAACIAENQCAAD-BAAw1QIBAAAAAdsCQADDBAAh8gIBAMAEACH7AgAA_wSPAyKMAwIA4QQAIY0DAQDABAAhAwAAACIAIAEAACMAMAIAACQAIAEAAAAXACABAAAAHAAgAQAAACIAIAEAAAAXACADAAAAFwAgAQAAGQAwAgAAGgAgAQAAABUAIAsHAACeCQAgCAAAngkAIAkAAJ8JACAOAACWBgAgEAAAoAkAIJEDAACdBQAgkgMAAJ0FACCTAwAAnQUAIJUDAACdBQAglgMAAJ0FACCXAwAAnQUAIBQHAADrBAAgCAAA8QQAIAkAAPwEACAOAADEBAAgEAAA_QQAINICAAD7BAAw0wIAABUAENQCAAD7BAAw2wJAAMMEACHyAgEAwAQAIfMCAgDhBAAhkAMBAMAEACGRAwEAwgQAIZIDAQDCBAAhkwMBAMIEACGUAwEAwAQAIZUDAQDCBAAhlgMBAMIEACGXA0AA4wQAIc0DAAD6BAAgAwAAABUAIAEAACwAMAIAAC0AIAMAAAARACABAAASADACAAATACAKBwAA6wQAIBcAAOwEACDSAgAA-AQAMNMCAAAwABDUAgAA-AQAMNUCAQDABAAh2wJAAMMEACHyAgEAwAQAIYIDAQDCBAAhhAMAAPkEhAMiAwcAAJ4JACAXAACcCQAgggMAAJ0FACALBwAA6wQAIBcAAOwEACDSAgAA-AQAMNMCAAAwABDUAgAA-AQAMNUCAQAAAAHbAkAAwwQAIfICAQDABAAhggMBAMIEACGEAwAA-QSEAyLMAwAA9wQAIAMAAAAwACABAAAxADACAAAyACABAAAADwAgAwAAACIAIAEAACMAMAIAACQAIAgDAADkBAAgBwAA6wQAINICAAD2BAAw0wIAADYAENQCAAD2BAAw2wJAAMMEACHvAgEAwAQAIfICAQDABAAhAgMAAJwJACAHAACeCQAgCQMAAOQEACAHAADrBAAg0gIAAPYEADDTAgAANgAQ1AIAAPYEADDbAkAAwwQAIe8CAQDABAAh8gIBAMAEACHLAwAA9QQAIAMAAAA2ACABAAA3ADACAAA4ACARAwAA7AQAIAcAAOsEACDSAgAA8gQAMNMCAAA6ABDUAgAA8gQAMNUCAQDABAAh2wJAAMMEACHvAgEAwgQAIfICAQDABAAh8wICAMEEACH7AgAA8wT7AiL8AgEAwgQAIf0CAQDCBAAh_gIBAMIEACH_AgEAwgQAIYADAAD0BAAggQMBAMIEACEKAwAAnAkAIAcAAJ4JACDvAgAAnQUAIPMCAACdBQAg_AIAAJ0FACD9AgAAnQUAIP4CAACdBQAg_wIAAJ0FACCAAwAAnQUAIIEDAACdBQAgEQMAAOwEACAHAADrBAAg0gIAAPIEADDTAgAAOgAQ1AIAAPIEADDVAgEAAAAB2wJAAMMEACHvAgEAwgQAIfICAQDABAAh8wICAMEEACH7AgAA8wT7AiL8AgEAwgQAIf0CAQDCBAAh_gIBAMIEACH_AgEAwgQAIYADAAD0BAAggQMBAMIEACEDAAAAOgAgAQAAOwAwAgAAPAAgAQAAAA8AIAwDAADkBAAgBwAA8QQAIC8AAOYEACDSAgAA8AQAMNMCAAA_ABDUAgAA8AQAMNUCAQDABAAh2wJAAMMEACHvAgEAwAQAIfICAQDCBAAh9gJAAMMEACH5AgIA4QQAIQMDAACcCQAgBwAAngkAIPICAACdBQAgDAMAAOQEACAHAADxBAAgLwAA5gQAINICAADwBAAw0wIAAD8AENQCAADwBAAw1QIBAAAAAdsCQADDBAAh7wIBAMAEACHyAgEAwgQAIfYCQADDBAAh-QICAOEEACEDAAAAPwAgAQAAQAAwAgAAQQAgAQAAABcAIBQDAADsBAAgBwAA6wQAIBoAAO8EACAdAADtBAAgHgAA7gQAINICAADqBAAw0wIAAEQAENQCAADqBAAw1QIBAMAEACHbAkAAwwQAIe8CAQDCBAAh8AICAMEEACHxAgEAwgQAIfICAQDABAAh8wICAMEEACH0AgEAwgQAIfUCAgDhBAAh9gJAAMMEACH3AkAA4wQAIfgCQADjBAAhDAMAAJwJACAHAACeCQAgGgAAmgkAIB0AAJ0JACAeAACZCQAg7wIAAJ0FACDwAgAAnQUAIPECAACdBQAg8wIAAJ0FACD0AgAAnQUAIPcCAACdBQAg-AIAAJ0FACAUAwAA7AQAIAcAAOsEACAaAADvBAAgHQAA7QQAIB4AAO4EACDSAgAA6gQAMNMCAABEABDUAgAA6gQAMNUCAQAAAAHbAkAAwwQAIe8CAQDCBAAh8AICAAAAAfECAQDCBAAh8gIBAMAEACHzAgIAwQQAIfQCAQDCBAAh9QICAOEEACH2AkAAwwQAIfcCQADjBAAh-AJAAOMEACEDAAAARAAgAQAARQAwAgAARgAgAQAAAA8AIAEAAABEACADAAAARAAgAQAARQAwAgAARgAgCAMAAOQEACAfAADpBAAg0gIAAOgEADDTAgAASwAQ1AIAAOgEADDbAkAAwwQAIe4CAQDABAAh7wIBAMAEACECAwAAnAkAIB8AAJ0JACAJAwAA5AQAIB8AAOkEACDSAgAA6AQAMNMCAABLABDUAgAA6AQAMNsCQADDBAAh7gIBAMAEACHvAgEAwAQAIcoDAADnBAAgAwAAAEsAIAEAAEwAMAIAAE0AIAEAAABEACABAAAASwAgAQAAABcAIAEAAAAVACABAAAAEQAgAQAAADAAIAEAAAAiACABAAAANgAgAQAAADoAIAEAAAA_ACABAAAARAAgAwAAADAAIAEAADEAMAIAADIAIAwjAADkBAAg0gIAAOUEADDTAgAAWwAQ1AIAAOUEADDVAgEAwAQAIdYCAQDABAAh1wIBAMAEACHYAgEAwAQAIdkCAQDABAAh2gIAAOYEACDbAkAAwwQAIdwCQADjBAAhAiMAAJwJACDcAgAAnQUAIAwjAADkBAAg0gIAAOUEADDTAgAAWwAQ1AIAAOUEADDVAgEAAAAB1gIBAMAEACHXAgEAwAQAIdgCAQDABAAh2QIBAMAEACHaAgAA5gQAINsCQADDBAAh3AJAAOMEACEDAAAAWwAgAQAAXAAwAgAAXQAgAwAAADYAIAEAADcAMAIAADgAIAMAAAA6ACABAAA7ADACAAA8ACADAAAAPwAgAQAAQAAwAgAAQQAgAwAAAEQAIAEAAEUAMAIAAEYAIAMAAABLACABAABMADACAABNACAPAwAA5AQAINICAADgBAAw0wIAAGQAENQCAADgBAAw1QIBAMAEACHbAkAA4wQAIe8CAQDABAAhhwMBAMIEACGjAwEAwAQAIaQDAQDABAAhpQMCAOEEACGmAwEAwAQAIacDIADiBAAhqAMBAMIEACGpAwEAwgQAIQUDAACcCQAg2wIAAJ0FACCHAwAAnQUAIKgDAACdBQAgqQMAAJ0FACAPAwAA5AQAINICAADgBAAw0wIAAGQAENQCAADgBAAw1QIBAAAAAdsCQADjBAAh7wIBAMAEACGHAwEAwgQAIaMDAQDABAAhpAMBAMAEACGlAwIA4QQAIaYDAQDABAAhpwMgAOIEACGoAwEAwgQAIakDAQDCBAAhAwAAAGQAIAEAAGUAMAIAAGYAIAEAAAADACABAAAABwAgAQAAAAsAIAEAAAARACABAAAAMAAgAQAAAFsAIAEAAAA2ACABAAAAOgAgAQAAAD8AIAEAAABEACABAAAASwAgAQAAAGQAIAEAAAABACAbBAAAkAkAIAUAAJEJACAGAACSCQAgIAAAmQkAICEAAJMJACAiAACUCQAgJAAAlQkAICUAAJYJACAmAACXCQAgJwAAmAkAICgAAJoJACApAACbCQAg8AIAAJ0FACD4AgAAnQUAIIYDAACdBQAghwMAAJ0FACC5AwAAnQUAILsDAACdBQAgwQMAAJ0FACDCAwAAnQUAIMMDAACdBQAgxAMAAJ0FACDFAwAAnQUAIMYDAACdBQAgxwMAAJ0FACDIAwAAnQUAIMkDAACdBQAgAwAAAA8AIAEAAHUAMAIAAAEAIAMAAAAPACABAAB1ADACAAABACADAAAADwAgAQAAdQAwAgAAAQAgIgQAAIQJACAFAACFCQAgBgAAhgkAICAAAI0JACAhAACHCQAgIgAAiAkAICQAAIkJACAlAACKCQAgJgAAiwkAICcAAIwJACAoAACOCQAgKQAAjwkAINUCAQAAAAHbAkAAAAAB8AICAAAAAfYCQAAAAAH4AkAAAAABhgMBAAAAAYcDAQAAAAG5AwEAAAABugMgAAAAAbsDAQAAAAG9AwAAAL0DAr8DAAAAvwMCwAMgAAAAAcEDAQAAAAHCAwEAAAABwwOAAAAAAcQDQAAAAAHFAwEAAAABxgMgAAAAAccDAQAAAAHIA0AAAAAByQNAAAAAAQEvAAB5ACAW1QIBAAAAAdsCQAAAAAHwAgIAAAAB9gJAAAAAAfgCQAAAAAGGAwEAAAABhwMBAAAAAbkDAQAAAAG6AyAAAAABuwMBAAAAAb0DAAAAvQMCvwMAAAC_AwLAAyAAAAABwQMBAAAAAcIDAQAAAAHDA4AAAAABxANAAAAAAcUDAQAAAAHGAyAAAAABxwMBAAAAAcgDQAAAAAHJA0AAAAABAS8AAHsAMAEvAAB7ADAiBAAA_QcAIAUAAP4HACAGAAD_BwAgIAAAhggAICEAAIAIACAiAACBCAAgJAAAgggAICUAAIMIACAmAACECAAgJwAAhQgAICgAAIcIACApAACICAAg1QIBAKEFACHbAkAAogUAIfACAgCyBQAh9gJAAKIFACH4AkAAowUAIYYDAQCzBQAhhwMBALMFACG5AwEAswUAIboDIADSBgAhuwMBALMFACG9AwAA-ge9AyK_AwAA-we_AyLAAyAA0gYAIcEDAQCzBQAhwgMBALMFACHDA4AAAAABxANAAKMFACHFAwEAswUAIcYDIAD8BwAhxwMBALMFACHIA0AAowUAIckDQACjBQAhAgAAAAEAIC8AAH4AIBbVAgEAoQUAIdsCQACiBQAh8AICALIFACH2AkAAogUAIfgCQACjBQAhhgMBALMFACGHAwEAswUAIbkDAQCzBQAhugMgANIGACG7AwEAswUAIb0DAAD6B70DIr8DAAD7B78DIsADIADSBgAhwQMBALMFACHCAwEAswUAIcMDgAAAAAHEA0AAowUAIcUDAQCzBQAhxgMgAPwHACHHAwEAswUAIcgDQACjBQAhyQNAAKMFACECAAAADwAgLwAAgAEAIAIAAAAPACAvAACAAQAgAwAAAAEAIDYAAHkAIDcAAH4AIAEAAAABACABAAAADwAgFAwAAPUHACA8AAD2BwAgPQAA-QcAID4AAPgHACA_AAD3BwAg8AIAAJ0FACD4AgAAnQUAIIYDAACdBQAghwMAAJ0FACC5AwAAnQUAILsDAACdBQAgwQMAAJ0FACDCAwAAnQUAIMMDAACdBQAgxAMAAJ0FACDFAwAAnQUAIMYDAACdBQAgxwMAAJ0FACDIAwAAnQUAIMkDAACdBQAgGdICAADWBAAw0wIAAIcBABDUAgAA1gQAMNUCAQCWBAAh2wJAAJgEACHwAgIApQQAIfYCQACYBAAh-AJAAJkEACGGAwEApgQAIYcDAQCmBAAhuQMBAKYEACG6AyAAzQQAIbsDAQCmBAAhvQMAANcEvQMivwMAANgEvwMiwAMgAM0EACHBAwEApgQAIcIDAQCmBAAhwwMAALEEACDEA0AAmQQAIcUDAQCmBAAhxgMgANkEACHHAwEApgQAIcgDQACZBAAhyQNAAJkEACEDAAAADwAgAQAAhgEAMDsAAIcBACADAAAADwAgAQAAdQAwAgAAAQAgAQAAAAUAIAEAAAAFACADAAAAAwAgAQAABAAwAgAABQAgAwAAAAMAIAEAAAQAMAIAAAUAIAMAAAADACABAAAEADACAAAFACAOAwAA9AcAINUCAQAAAAHbAkAAAAAB7wIBAAAAAfYCQAAAAAGwAwEAAAABsQMBAAAAAbIDAQAAAAGzAwEAAAABtANAAAAAAbUDQAAAAAG2AwEAAAABtwMBAAAAAbgDAQAAAAEBLwAAjwEAIA3VAgEAAAAB2wJAAAAAAe8CAQAAAAH2AkAAAAABsAMBAAAAAbEDAQAAAAGyAwEAAAABswMBAAAAAbQDQAAAAAG1A0AAAAABtgMBAAAAAbcDAQAAAAG4AwEAAAABAS8AAJEBADABLwAAkQEAMA4DAADzBwAg1QIBAKEFACHbAkAAogUAIe8CAQChBQAh9gJAAKIFACGwAwEAoQUAIbEDAQChBQAhsgMBALMFACGzAwEAswUAIbQDQACjBQAhtQNAAKMFACG2AwEAswUAIbcDAQCzBQAhuAMBALMFACECAAAABQAgLwAAlAEAIA3VAgEAoQUAIdsCQACiBQAh7wIBAKEFACH2AkAAogUAIbADAQChBQAhsQMBAKEFACGyAwEAswUAIbMDAQCzBQAhtANAAKMFACG1A0AAowUAIbYDAQCzBQAhtwMBALMFACG4AwEAswUAIQIAAAADACAvAACWAQAgAgAAAAMAIC8AAJYBACADAAAABQAgNgAAjwEAIDcAAJQBACABAAAABQAgAQAAAAMAIAoMAADwBwAgPgAA8gcAID8AAPEHACCyAwAAnQUAILMDAACdBQAgtAMAAJ0FACC1AwAAnQUAILYDAACdBQAgtwMAAJ0FACC4AwAAnQUAIBDSAgAA1QQAMNMCAACdAQAQ1AIAANUEADDVAgEAlgQAIdsCQACYBAAh7wIBAJYEACH2AkAAmAQAI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_AADhBwAg2wIAAJ0FACCHAwAAnQUAIKgDAACdBQAgqQMAAJ0FACAO0gIAANIEADDTAgAA4QEAENQCAADSBAAw1QIBAJYEACHbAkAAmQQAIe8CAQCWBAAhhwMBAKYEACGjAwEAlgQAIaQDAQCWBAAhpQMCAKcEACGmAwEAlgQAIacDIADNBAAhqAMBAKYEACGpAwEApgQAIQMAAABkACABAADgAQAwOwAA4QEAIAMAAABkACABAABlADACAABmACABAAAAGgAgAQAAABoAIAMAAAAXACABAAAZADACAAAaACADAAAAFwAgAQAAGQAwAgAAGgAgAwAAABcAIAEAABkAMAIAABoAIBsRAADOBwAgEgAAvwcAIBMAAMAHACAUAADBBwAgFQAAwgcAIBYAAMMHACAYAADEBwAgGQAAxQcAIBoAAMYHACAbAADHBwAgHAAAyAcAICAAAMkHACDVAgEAAAAB2wJAAAAAAfACAgAAAAH2AkAAAAAB-AJAAAAAAZgDAgAAAAGZAwEAAAABmgMCAAAAAZwDAAAAnAMCnQMgAAAAAZ4DIAAAAAGfAwIAAAABoAMCAAAAAaEDAgAAAAGiAwIAAAABAS8AAOkBACAP1QIBAAAAAdsCQAAAAAHwAgIAAAAB9gJAAAAAAfgCQAAAAAGYAwIAAAABmQMBAAAAAZoDAgAAAAGcAwAAAJwDAp0DIAAAAAGeAyAAAAABnwMCAAAAAaADAgAAAAGhAwIAAAABogMCAAAAAQEvAADrAQAwAS8AAOsBADABAAAAFQAgAQAAABcAIAEAAAAVACAbEQAA1AYAIBIAANUGACATAADWBgAgFAAA1AcAIBUAANcGACAWAADYBgAgGAAA2QYAIBkAANoGACAaAADbBgAgGwAA3AYAIBwAAN0GACAgAADeBgAg1QIBAKEFACHbAkAAogUAIfACAgCyBQAh9gJAAKIFACH4AkAAowUAIZgDAgCyBQAhmQMBALMFACGaAwIAsgUAIZwDAADRBpwDIp0DIADSBgAhngMgANIGACGfAwIAtAUAIaADAgC0BQAhoQMCALQFACGiAwIAtAUAIQIAAAAaACAvAADxAQAgD9UCAQChBQAh2wJAAKIFACHwAgIAsgUAIfYCQACiBQAh-AJAAKMFACGYAwIAsgUAIZkDAQCzBQAhmgMCALIFACGcAwAA0QacAyKdAyAA0gYAIZ4DIADSBgAhnwMCALQFACGgAwIAtAUAIaEDAgC0BQAhogMCALQFACECAAAAFwAgLwAA8wEAIAIAAAAXACAvAADzAQAgAQAAABUAIAEAAAAXACABAAAAFQAgAwAAABoAIDYAAOkBACA3AADxAQAgAQAAABoAIAEAAAAXACAKDAAA2gcAIDwAANsHACA9AADeBwAgPgAA3QcAID8AANwHACDwAgAAnQUAIPgCAACdBQAgmAMAAJ0FACCZAwAAnQUAIJoDAACdBQAgEtICAADLBAAw0wIAAP0BABDUAgAAywQAMNUCAQCWBAAh2wJAAJgEACHwAgIApQQAIfYCQACYBAAh-AJAAJkEACGYAwIApQQAIZkDAQCmBAAhmgMCAKUEACGcAwAAzAScAyKdAyAAzQQAIZ4DIADNBAAhnwMCAKcEACGgAwIApwQAIaEDAgCnBAAhogMCAKcEACEDAAAAFwAgAQAA_AEAMDsAAP0BACADAAAAFwAgAQAAGQAwAgAAGgAgAQAAAC0AIAEAAAAtACADAAAAFQAgAQAALAAwAgAALQAgAwAAABUAIAEAACwAMAIAAC0AIAMAAAAVACABAAAsADACAAAtACAQBwAA2QcAIAgAALkHACAJAAC6BwAgDgAAuwcAIBAAALwHACDbAkAAAAAB8gIBAAAAAfMCAgAAAAGQAwEAAAABkQMBAAAAAZIDAQAAAAGTAwEAAAABlAMBAAAAAZUDAQAAAAGWAwEAAAABlwNAAAAAAQEvAACFAgAgC9sCQAAAAAHyAgEAAAAB8wICAAAAAZADAQAAAAGRAwEAAAABkgMBAAAAAZMDAQAAAAGUAwEAAAABlQMBAAAAAZYDAQAAAAGXA0AAAAABAS8AAIcCADABLwAAhwIAMBAHAACtBgAgCAAArgYAIAkAAK8GACAOAACwBgAgEAAAsQYAINsCQACiBQAh8gIBAKEFACHzAgIAtAUAIZADAQChBQAhkQMBALMFACGSAwEAswUAIZMDAQCzBQAhlAMBAKEFACGVAwEAswUAIZYDAQCzBQAhlwNAAKMFACECAAAALQAgLwAAigIAIAvbAkAAogUAIfICAQChBQAh8wICALQFACGQAwEAoQUAIZEDAQCzBQAhkgMBALMFACGTAwEAswUAIZQDAQChBQAhlQMBALMFACGWAwEAswUAIZcDQACjBQAhAgAAABUAIC8AAIwCACACAAAAFQAgLwAAjAIAIAMAAAAtACA2AACFAgAgNwAAigIAIAEAAAAtACABAAAAFQAgCwwAAKgGACA8AACpBgAgPQAArAYAID4AAKsGACA_AACqBgAgkQMAAJ0FACCSAwAAnQUAIJMDAACdBQAglQMAAJ0FACCWAwAAnQUAIJcDAACdBQAgDtICAADKBAAw0wIAAJMCABDUAgAAygQAMNsCQACYBAAh8gIBAJYEACHzAgIApwQAIZADAQCWBAAhkQMBAKYEACGSAwEApgQAIZMDAQCmBAAhlAMBAJYEACGVAwEApgQAIZYDAQCmBAAhlwNAAJkEACEDAAAAFQAgAQAAkgIAMDsAAJMCACADAAAAFQAgAQAALAAwAgAALQAgAQAAAB4AIAEAAAAeACADAAAAHAAgAQAAHQAwAgAAHgAgAwAAABwAIAEAAB0AMAIAAB4AIAMAAAAcACABAAAdADACAAAeACAGCgAAlAYAIA0AAKcGACDbAkAAAAAB8gIBAAAAAfMCAgAAAAGPAwEAAAABAS8AAJsCACAE2wJAAAAAAfICAQAAAAHzAgIAAAABjwMBAAAAAQEvAACdAgAwAS8AAJ0CADAGCgAAkgYAIA0AAKYGACDbAkAAogUAIfICAQChBQAh8wICALQFACGPAwEAoQUAIQIAAAAeACAvAACgAgAgBNsCQACiBQAh8gIBAKEFACHzAgIAtAUAIY8DAQChBQAhAgAAABwAIC8AAKICACACAAAAHAAgLwAAogIAIAMAAAAeACA2AACbAgAgNwAAoAIAIAEAAAAeACABAAAAHAAgBQwAAKEGACA8AACiBgAgPQAApQYAID4AAKQGACA_AACjBgAgB9ICAADJBAAw0wIAAKkCABDUAgAAyQQAMNsCQACYBAAh8gIBAJYEACHzAgIApwQAIY8DAQCWBAAhAwAAABwAIAEAAKgCADA7AACpAgAgAwAAABwAIAEAAB0AMAIAAB4AIAEAAAAkACABAAAAJAAgAwAAACIAIAEAACMAMAIAACQAIAMAAAAiACABAAAjADACAAAkACADAAAAIgAgAQAAIwAwAgAAJAAgCAcAAJ8GACAPAACgBgAg1QIBAAAAAdsCQAAAAAHyAgEAAAAB-wIAAACPAwKMAwIAAAABjQMBAAAAAQEvAACxAgAgBtUCAQAAAAHbAkAAAAAB8gIBAAAAAfsCAAAAjwMCjAMCAAAAAY0DAQAAAAEBLwAAswIAMAEvAACzAgAwCAcAAJ0GACAPAACeBgAg1QIBAKEFACHbAkAAogUAIfICAQChBQAh-wIAAJwGjwMijAMCALQFACGNAwEAoQUAIQIAAAAkACAvAAC2AgAgBtUCAQChBQAh2wJAAKIFACHyAgEAoQUAIfsCAACcBo8DIowDAgC0BQAhjQMBAKEFACECAAAAIgAgLwAAuAIAIAIAAAAiACAvAAC4AgAgAwAAACQAIDYAALECACA3AAC2AgAgAQAAACQAIAEAAAAiACAFDAAAlwYAIDwAAJgGACA9AACbBgAgPgAAmgYAID8AAJkGACAJ0gIAAMUEADDTAgAAvwIAENQCAADFBAAw1QIBAJYEACHbAkAAmAQAIfICAQCWBAAh-wIAAMYEjwMijAMCAKcEACGNAwEAlgQAIQMAAAAiACABAAC-AgAwOwAAvwIAIAMAAAAiACABAAAjADACAAAkACAJCwAAxAQAINICAAC_BAAw0wIAAMUCABDUAgAAvwQAMNUCAQAAAAHbAkAAwwQAIfACAgAAAAGHAwEAAAABiAMBAMIEACEBAAAAwgIAIAEAAADCAgAgCQsAAMQEACDSAgAAvwQAMNMCAADFAgAQ1AIAAL8EADDVAgEAwAQAIdsCQADDBAAh8AICAMEEACGHAwEAwAQAIYgDAQDCBAAhAwsAAJYGACDwAgAAnQUAIIgDAACdBQAgAwAAAMUCACABAADGAgAwAgAAwgIAIAMAAADFAgAgAQAAxgIAMAIAAMICACADAAAAxQIAIAEAAMYCADACAADCAgAgBgsAAJUGACDVAgEAAAAB2wJAAAAAAfACAgAAAAGHAwEAAAABiAMBAAAAAQEvAADKAgAgBdUCAQAAAAHbAkAAAAAB8AICAAAAAYcDAQAAAAGIAwEAAAABAS8AAMwCADABLwAAzAIAMAYLAACGBgAg1QIBAKEFACHbAkAAogUAIfACAgCyBQAhhwMBAKEFACGIAwEAswUAIQIAAADCAgAgLwAAzwIAIAXVAgEAoQUAIdsCQACiBQAh8AICALIFACGHAwEAoQUAIYgDAQCzBQAhAgAAAMUCACAvAADRAgAgAgAAAMUCACAvAADRAgAgAwAAAMICACA2AADKAgAgNwAAzwIAIAEAAADCAgAgAQAAAMUCACAHDAAAgQYAIDwAAIIGACA9AACFBgAgPgAAhAYAID8AAIMGACDwAgAAnQUAIIgDAACdBQAgCNICAAC-BAAw0wIAANgCABDUAgAAvgQAMNUCAQCWBAAh2wJAAJgEACHwAgIApQQAIYcDAQCWBAAhiAMBAKYEACEDAAAAxQIAIAEAANcCADA7AADYAgAgAwAAAMUCACABAADGAgAwAgAAwgIAIAEAAAATACABAAAAEwAgAwAAABEAIAEAABIAMAIAABMAIAMAAAARACABAAASADACAAATACADAAAAEQAgAQAAEgAwAgAAEwAgBgMAAIAGACAHAAD_BQAg2wJAAAAAAe8CAQAAAAHyAgEAAAABhgMAAACGAwIBLwAA4AIAIATbAkAAAAAB7wIBAAAAAfICAQAAAAGGAwAAAIYDAgEvAADiAgAwAS8AAOICADAGAwAA_gUAIAcAAP0FACDbAkAAogUAIe8CAQChBQAh8gIBAKEFACGGAwAA_AWGAyICAAAAEwAgLwAA5QIAIATbAkAAogUAIe8CAQChBQAh8gIBAKEFACGGAwAA_AWGAyICAAAAEQAgLwAA5wIAIAIAAAARACAvAADnAgAgAwAAABMAIDYAAOACACA3AADlAgAgAQAAABMAIAEAAAARACADDAAA-QUAID4AAPsFACA_AAD6BQAgB9ICAAC6BAAw0wIAAO4CABDUAgAAugQAMNsCQACYBAAh7wIBAJYEACHyAgEAlgQAIYYDAAC7BIYDIgMAAAARACABAADtAgAwOwAA7gIAIAMAAAARACABAAASADACAAATACABAAAAMgAgAQAAADIAIAMAAAAwACABAAAxADACAAAyACADAAAAMAAgAQAAMQAwAgAAMgAgAwAAADAAIAEAADEAMAIAADIAIAcHAAD3BQAgFwAA-AUAINUCAQAAAAHbAkAAAAAB8gIBAAAAAYIDAQAAAAGEAwAAAIQDAgEvAAD2AgAgBdUCAQAAAAHbAkAAAAAB8gIBAAAAAYIDAQAAAAGEAwAAAIQDAgEvAAD4AgAwAS8AAPgCADABAAAADwAgBwcAAPUFACAXAAD2BQAg1QIBAKEFACHbAkAAogUAIfICAQChBQAhggMBALMFACGEAwAA9AWEAyICAAAAMgAgLwAA_AIAIAXVAgEAoQUAIdsCQACiBQAh8gIBAKEFACGCAwEAswUAIYQDAAD0BYQDIgIAAAAwACAvAAD-AgAgAgAAADAAIC8AAP4CACABAAAADwAgAwAAADIAIDYAAPYCACA3AAD8AgAgAQAAADIAIAEAAAAwACAEDAAA8QUAID4AAPMFACA_AADyBQAgggMAAJ0FACAI0gIAALYEADDTAgAAhgMAENQCAAC2BAAw1QIBAJYEACHbAkAAmAQAIfICAQCWBAAhggMBAKYEACGEAwAAtwSEAyIDAAAAMAAgAQAAhQMAMDsAAIYDACADAAAAMAAgAQAAMQAwAgAAMgAgAQAAADgAIAEAAAA4ACADAAAANgAgAQAANwAwAgAAOAAgAwAAADYAIAEAADcAMAIAADgAIAMAAAA2ACABAAA3ADACAAA4ACAFAwAA8AUAIAcAAO8FACDbAkAAAAAB7wIBAAAAAfICAQAAAAEBLwAAjgMAIAPbAkAAAAAB7wIBAAAAAfICAQAAAAEBLwAAkAMAMAEvAACQAwAwBQMAAO4FACAHAADtBQAg2wJAAKIFACHvAgEAoQUAIfICAQChBQAhAgAAADgAIC8AAJMDACAD2wJAAKIFACHvAgEAoQUAIfICAQChBQAhAgAAADYAIC8AAJUDACACAAAANgAgLwAAlQMAIAMAAAA4ACA2AACOAwAgNwAAkwMAIAEAAAA4ACABAAAANgAgAwwAAOoFACA-AADsBQAgPwAA6wUAIAbSAgAAtQQAMNMCAACcAwAQ1AIAALUEADDbAkAAmAQAIe8CAQCWBAAh8gIBAJYEACEDAAAANgAgAQAAmwMAMDsAAJwDACADAAAANgAgAQAANwAwAgAAOAAgAQAAADwAIAEAAAA8ACADAAAAOgAgAQAAOwAwAgAAPAAgAwAAADoAIAEAADsAMAIAADwAIAMAAAA6ACABAAA7ADACAAA8ACAOAwAA6QUAIAcAAOgFACDVAgEAAAAB2wJAAAAAAe8CAQAAAAHyAgEAAAAB8wICAAAAAfsCAAAA-wIC_AIBAAAAAf0CAQAAAAH-AgEAAAAB_wIBAAAAAYADgAAAAAGBAwEAAAABAS8AAKQDACAM1QIBAAAAAdsCQAAAAAHvAgEAAAAB8gIBAAAAAfMCAgAAAAH7AgAAAPsCAvwCAQAAAAH9AgEAAAAB_gIBAAAAAf8CAQAAAAGAA4AAAAABgQMBAAAAAQEvAACmAwAwAS8AAKYDADABAAAADwAgDgMAAOcFACAHAADmBQAg1QIBAKEFACHbAkAAogUAIe8CAQCzBQAh8gIBAKEFACHzAgIAsgUAIfsCAADlBfsCIvwCAQCzBQAh_QIBALMFACH-AgEAswUAIf8CAQCzBQAhgAOAAAAAAYEDAQCzBQAhAgAAADwAIC8AAKoDACAM1QIBAKEFACHbAkAAogUAIe8CAQCzBQAh8gIBAKEFACHzAgIAsgUAIfsCAADlBfsCIvwCAQCzBQAh_QIBALMFACH-AgEAswUAIf8CAQCzBQAhgAOAAAAAAYEDAQCzBQAhAgAAADoAIC8AAKwDACACAAAAOgAgLwAArAMAIAEAAAAPACADAAAAPAAgNgAApAMAIDcAAKoDACABAAAAPAAgAQAAADoAIA0MAADgBQAgPAAA4QUAID0AAOQFACA-AADjBQAgPwAA4gUAIO8CAACdBQAg8wIAAJ0FACD8AgAAnQUAIP0CAACdBQAg_gIAAJ0FACD_AgAAnQUAIIADAACdBQAggQMAAJ0FACAP0gIAAK8EADDTAgAAtAMAENQCAACvBAAw1QIBAJYEACHbAkAAmAQAIe8CAQCmBAAh8gIBAJYEACHzAgIApQQAIfsCAACwBPsCIvwCAQCmBAAh_QIBAKYEACH-AgEApgQAIf8CAQCmBAAhgAMAALEEACCBAwEApgQAIQMAAAA6ACABAACzAwAwOwAAtAMAIAMAAAA6ACABAAA7ADACAAA8ACABAAAAQQAgAQAAAEEAIAMAAAA_ACABAABAADACAABBACADAAAAPwAgAQAAQAAwAgAAQQAgAwAAAD8AIAEAAEAAMAIAAEEAIAkDAADeBQAgBwAA3wUAIC-AAAAAAdUCAQAAAAHbAkAAAAAB7wIBAAAAAfICAQAAAAH2AkAAAAAB-QICAAAAAQEvAAC8AwAgBy-AAAAAAdUCAQAAAAHbAkAAAAAB7wIBAAAAAfICAQAAAAH2AkAAAAAB-QICAAAAAQEvAAC-AwAwAS8AAL4DADABAAAAFwAgCQMAANwFACAHAADdBQAgL4AAAAAB1QIBAKEFACHbAkAAogUAIe8CAQChBQAh8gIBALMFACH2AkAAogUAIfkCAgC0BQAhAgAAAEEAIC8AAMIDACAHL4AAAAAB1QIBAKEFACHbAkAAogUAIe8CAQChBQAh8gIBALMFACH2AkAAogUAIfkCAgC0BQAhAgAAAD8AIC8AAMQDACACAAAAPwAgLwAAxAMAIAEAAAAXACADAAAAQQAgNgAAvAMAIDcAAMIDACABAAAAQQAgAQAAAD8AIAYMAADXBQAgPAAA2AUAID0AANsFACA-AADaBQAgPwAA2QUAIPICAACdBQAgCi8AAJcEACDSAgAArgQAMNMCAADMAwAQ1AIAAK4EADDVAgEAlgQAIdsCQACYBAAh7wIBAJYEACHyAgEApgQAIfYCQACYBAAh-QICAKcEACEDAAAAPwAgAQAAywMAMDsAAMwDACADAAAAPwAgAQAAQAAwAgAAQQAgAQAAAEYAIAEAAABGACADAAAARAAgAQAARQAwAgAARgAgAwAAAEQAIAEAAEUAMAIAAEYAIAMAAABEACABAABFADACAABGACARAwAA0wUAIAcAANIFACAaAADVBQAgHQAA1gUAIB4AANQFACDVAgEAAAAB2wJAAAAAAe8CAQAAAAHwAgIAAAAB8QIBAAAAAfICAQAAAAHzAgIAAAAB9AIBAAAAAfUCAgAAAAH2AkAAAAAB9wJAAAAAAfgCQAAAAAEBLwAA1AMAIAzVAgEAAAAB2wJAAAAAAe8CAQAAAAHwAgIAAAAB8QIBAAAAAfICAQAAAAHzAgIAAAAB9AIBAAAAAfUCAgAAAAH2AkAAAAAB9wJAAAAAAfgCQAAAAAEBLwAA1gMAMAEvAADWAwAwAQAAAA8AIAEAAABEACARAwAAtgUAIAcAALUFACAaAAC5BQAgHQAAtwUAIB4AALgFACDVAgEAoQUAIdsCQACiBQAh7wIBALMFACHwAgIAsgUAIfECAQCzBQAh8gIBAKEFACHzAgIAsgUAIfQCAQCzBQAh9QICALQFACH2AkAAogUAIfcCQACjBQAh-AJAAKMFACECAAAARgAgLwAA2wMAIAzVAgEAoQUAIdsCQACiBQAh7wIBALMFACHwAgIAsgUAIfECAQCzBQAh8gIBAKEFACHzAgIAsgUAIfQCAQCzBQAh9QICALQFACH2AkAAogUAIfcCQACjBQAh-AJAAKMFACECAAAARAAgLwAA3QMAIAIAAABEACAvAADdAwAgAQAAAA8AIAEAAABEACADAAAARgAgNgAA1AMAIDcAANsDACABAAAARgAgAQAAAEQAIAwMAACtBQAgPAAArgUAID0AALEFACA-AACwBQAgPwAArwUAIO8CAACdBQAg8AIAAJ0FACDxAgAAnQUAIPMCAACdBQAg9AIAAJ0FACD3AgAAnQUAIPgCAACdBQAgD9ICAACkBAAw0wIAAOYDABDUAgAApAQAMNUCAQCWBAAh2wJAAJgEACHvAgEApgQAIfACAgClBAAh8QIBAKYEACHyAgEAlgQAIfMCAgClBAAh9AIBAKYEACH1AgIApwQAIfYCQACYBAAh9wJAAJkEACH4AkAAmQQAIQMAAABEACABAADlAwAwOwAA5gMAIAMAAABEACABAABFADACAABGACABAAAATQAgAQAAAE0AIAMAAABLACABAABMADACAABNACADAAAASwAgAQAATAAwAgAATQAgAwAAAEsAIAEAAEwAMAIAAE0AIAUDAACsBQAgHwAAqwUAINsCQAAAAAHuAgEAAAAB7wIBAAAAAQEvAADuAwAgA9sCQAAAAAHuAgEAAAAB7wIBAAAAAQEvAADwAwAwAS8AAPADADAFAwAAqgUAIB8AAKkFACDbAkAAogUAIe4CAQChBQAh7wIBAKEFACECAAAATQAgLwAA8wMAIAPbAkAAogUAIe4CAQChBQAh7wIBAKEFACECAAAASwAgLwAA9QMAIAIAAABLACAvAAD1AwAgAwAAAE0AIDYAAO4DACA3AADzAwAgAQAAAE0AIAEAAABLACADDAAApgUAID4AAKgFACA_AACnBQAgBtICAACjBAAw0wIAAPwDABDUAgAAowQAMNsCQACYBAAh7gIBAJYEACHvAgEAlgQAIQMAAABLACABAAD7AwAwOwAA_AMAIAMAAABLACABAABMADACAABNACABAAAAXQAgAQAAAF0AIAMAAABbACABAABcADACAABdACADAAAAWwAgAQAAXAAwAgAAXQAgAwAAAFsAIAEAAFwAMAIAAF0AIAkjAAClBQAg1QIBAAAAAdYCAQAAAAHXAgEAAAAB2AIBAAAAAdkCAQAAAAHaAoAAAAAB2wJAAAAAAdwCQAAAAAEBLwAAhAQAIAjVAgEAAAAB1gIBAAAAAdcCAQAAAAHYAgEAAAAB2QIBAAAAAdoCgAAAAAHbAkAAAAAB3AJAAAAAAQEvAACGBAAwAS8AAIYEADAJIwAApAUAINUCAQChBQAh1gIBAKEFACHXAgEAoQUAIdgCAQChBQAh2QIBAKEFACHaAoAAAAAB2wJAAKIFACHcAkAAowUAIQIAAABdACAvAACJBAAgCNUCAQChBQAh1gIBAKEFACHXAgEAoQUAIdgCAQChBQAh2QIBAKEFACHaAoAAAAAB2wJAAKIFACHcAkAAowUAIQIAAABbACAvAACLBAAgAgAAAFsAIC8AAIsEACADAAAAXQAgNgAAhAQAIDcAAIkEACABAAAAXQAgAQAAAFsAIAQMAACeBQAgPgAAoAUAID8AAJ8FACDcAgAAnQUAIAvSAgAAlQQAMNMCAACSBAAQ1AIAAJUEADDVAgEAlgQAIdYCAQCWBAAh1wIBAJYEACHYAgEAlgQAIdkCAQCWBAAh2gIAAJcEACDbAkAAmAQAIdwCQACZBAAhAwAAAFsAIAEAAJEEADA7AACSBAAgAwAAAFsAIAEAAFwAMAIAAF0AIAvSAgAAlQQAMNMCAACSBAAQ1AIAAJUEADDVAgEAlgQAIdYCAQCWBAAh1wIBAJYEACHYAgEAlgQAIdkCAQCWBAAh2gIAAJcEACDbAkAAmAQAIdwCQACZBAAhDgwAAJ4EACA-AACiBAAgPwAAogQAIN0CAQAAAAHeAgEAAAAE3wIBAAAABOACAQAAAAHhAgEAAAAB4gIBAAAAAeMCAQAAAAHkAgEAoQQAIesCAQAAAAHsAgEAAAAB7QIBAAAAAQ8MAACeBAAgPgAAoAQAID8AAKAEACDdAoAAAAAB4AKAAAAAAeECgAAAAAHiAoAAAAAB4wKAAAAAAeQCgAAAAAHlAgEAAAAB5gIBAAAAAecCAQAAAAHoAoAAAAAB6QKAAAAAAeoCgAAAAAELDAAAngQAID4AAJ8EACA_AACfBAAg3QJAAAAAAd4CQAAAAATfAkAAAAAE4AJAAAAAAeECQAAAAAHiAkAAAAAB4wJAAAAAAeQCQACdBAAhCwwAAJsEACA-AACcBAAgPwAAnAQAIN0CQAAAAAHeAkAAAAAF3wJAAAAABeACQAAAAAHhAkAAAAAB4gJAAAAAAeMCQAAAAAHkAkAAmgQAIQsMAACbBAAgPgAAnAQAID8AAJwEACDdAkAAAAAB3gJAAAAABd8CQAAAAAXgAkAAAAAB4QJAAAAAAeICQAAAAAHjAkAAAAAB5AJAAJoEACEI3QICAAAAAd4CAgAAAAXfAgIAAAAF4AICAAAAAeECAgAAAAHiAgIAAAAB4wICAAAAAeQCAgCbBAAhCN0CQAAAAAHeAkAAAAAF3wJAAAAABeACQAAAAAHhAkAAAAAB4gJAAAAAAeMCQAAAAAHkAkAAnAQAIQsMAACeBAAgPgAAnwQAID8AAJ8EACDdAkAAAAAB3gJAAAAABN8CQAAAAATgAkAAAAAB4QJAAAAAAeICQAAAAAHjAkAAAAAB5AJAAJ0EACEI3QICAAAAAd4CAgAAAATfAgIAAAAE4AICAAAAAeECAgAAAAHiAgIAAAAB4wICAAAAAeQCAgCeBAAhCN0CQAAAAAHeAkAAAAAE3wJAAAAABOACQAAAAAHhAkAAAAAB4gJAAAAAAeMCQAAAAAHkAkAAnwQAIQzdAoAAAAAB4AKAAAAAAeECgAAAAAHiAoAAAAAB4wKAAAAAAeQCgAAAAAHlAgEAAAAB5gIBAAAAAecCAQAAAAHoAoAAAAAB6QKAAAAAAeoCgAAAAAEODAAAngQAID4AAKIEACA_AACiBAAg3QIBAAAAAd4CAQAAAATfAgEAAAAE4AIBAAAAAeECAQAAAAHiAgEAAAAB4wIBAAAAAeQCAQChBAAh6wIBAAAAAewCAQAAAAHtAgEAAAABC90CAQAAAAHeAgEAAAAE3wIBAAAABOACAQAAAAHhAgEAAAAB4gIBAAAAAeMCAQAAAAHkAgEAogQAIesCAQAAAAHsAgEAAAAB7QIBAAAAAQbSAgAAowQAMNMCAAD8AwAQ1AIAAKMEADDbAkAAmAQAIe4CAQCWBAAh7wIBAJYEACEP0gIAAKQEADDTAgAA5gMAENQCAACkBAAw1QIBAJYEACHbAkAAmAQAIe8CAQCmBAAh8AICAKUEACHxAgEApgQAIfICAQCWBAAh8wICAKUEACH0AgEApgQAIfUCAgCnBAAh9gJAAJgEACH3AkAAmQQAIfgCQACZBAAhDQwAAJsEACA8AACtBAAgPQAAmwQAID4AAJsEACA_AACbBAAg3QICAAAAAd4CAgAAAAXfAgIAAAAF4AICAAAAAeECAgAAAAHiAgIAAAAB4wICAAAAAeQCAgCsBAAhDgwAAJsEACA-AACrBAAgPwAAqwQAIN0CAQAAAAHeAgEAAAAF3wIBAAAABeACAQAAAAHhAgEAAAAB4gIBAAAAAeMCAQAAAAHkAgEAqgQAIesCAQAAAAHsAgEAAAAB7QIBAAAAAQ0MAACeBAAgPAAAqQQAID0AAJ4EACA-AACeBAAgPwAAngQAIN0CAgAAAAHeAgIAAAAE3wICAAAABOACAgAAAAHhAgIAAAAB4gICAAAAAeMCAgAAAAHkAgIAqAQAIQ0MAACeBAAgPAAAqQQAID0AAJ4EACA-AACeBAAgPwAAngQAIN0CAgAAAAHeAgIAAAAE3wICAAAABOACAgAAAAHhAgIAAAAB4gICAAAAAeMCAgAAAAHkAgIAqAQAIQjdAggAAAAB3gIIAAAABN8CCAAAAATgAggAAAAB4QIIAAAAAeICCAAAAAHjAggAAAAB5AIIAKkEACEODAAAmwQAID4AAKsEACA_AACrBAAg3QIBAAAAAd4CAQAAAAXfAgEAAAAF4AIBAAAAAeECAQAAAAHiAgEAAAAB4wIBAAAAAeQCAQCqBAAh6wIBAAAAAewCAQAAAAHtAgEAAAABC90CAQAAAAHeAgEAAAAF3wIBAAAABeACAQAAAAHhAgEAAAAB4gIBAAAAAeMCAQAAAAHkAgEAqwQAIesCAQAAAAHsAgEAAAAB7QIBAAAAAQ0MAACbBAAgPAAArQQAID0AAJsEACA-AACbBAAgPwAAmwQAIN0CAgAAAAHeAgIAAAAF3wICAAAABeACAgAAAAHhAgIAAAAB4gICAAAAAeMCAgAAAAHkAgIArAQAIQjdAggAAAAB3gIIAAAABd8CCAAAAAXgAggAAAAB4QIIAAAAAeICCAAAAAHjAggAAAAB5AIIAK0EACEKLwAAlwQAINICAACuBAAw0wIAAMwDABDUAgAArgQAMNUCAQCWBAAh2wJAAJgEACHvAgEAlgQAIfICAQCmBAAh9gJAAJgEACH5AgIApwQAIQ_SAgAArwQAMNMCAAC0AwAQ1AIAAK8EADDVAgEAlgQAIdsCQACYBAAh7wIBAKYEACHyAgEAlgQAIfMCAgClBAAh-wIAALAE-wIi_AIBAKYEACH9AgEApgQAIf4CAQCmBAAh_wIBAKYEACGAAwAAsQQAIIEDAQCmBAAhBwwAAJ4EACA-AAC0BAAgPwAAtAQAIN0CAAAA-wIC3gIAAAD7AgjfAgAAAPsCCOQCAACzBPsCIg8MAACbBAAgPgAAsgQAID8AALIEACDdAoAAAAAB4AKAAAAAAeECgAAAAAHiAoAAAAAB4wKAAAAAAeQCgAAAAAHlAgEAAAAB5gIBAAAAAecCAQAAAAHoAoAAAAAB6QKAAAAAAeoCgAAAAAEM3QKAAAAAAeACgAAAAAHhAoAAAAAB4gKAAAAAAeMCgAAAAAHkAoAAAAAB5QIBAAAAAeYCAQAAAAHnAgEAAAAB6AKAAAAAAekCgAAAAAHqAoAAAAABBwwAAJ4EACA-AAC0BAAgPwAAtAQAIN0CAAAA-wIC3gIAAAD7AgjfAgAAAPsCCOQCAACzBPsCIgTdAgAAAPsCAt4CAAAA-wII3wIAAAD7AgjkAgAAtAT7AiIG0gIAALUEADDTAgAAnAMAENQCAAC1BAAw2wJAAJgEACHvAgEAlgQAIfICAQCWBAAhCNICAAC2BAAw0wIAAIYDABDUAgAAtgQAMNUCAQCWBAAh2wJAAJgEACHyAgEAlgQAIYIDAQCmBAAhhAMAALcEhAMiBwwAAJ4EACA-AAC5BAAgPwAAuQQAIN0CAAAAhAMC3gIAAACEAwjfAgAAAIQDCOQCAAC4BIQDIgcMAACeBAAgPgAAuQQAID8AALkEACDdAgAAAIQDAt4CAAAAhAMI3wIAAACEAwjkAgAAuASEAyIE3QIAAACEAwLeAgAAAIQDCN8CAAAAhAMI5AIAALkEhAMiB9ICAAC6BAAw0wIAAO4CABDUAgAAugQAMNsCQACYBAAh7wIBAJYEACHyAgEAlgQAIYYDAAC7BIYDIgcMAACeBAAgPgAAvQQAID8AAL0EACDdAgAAAIYDAt4CAAAAhgMI3wIAAACGAwjkAgAAvASGAyIHDAAAngQAID4AAL0EACA_AAC9BAAg3QIAAACGAwLeAgAAAIYDCN8CAAAAhgMI5AIAALwEhgMiBN0CAAAAhgMC3gIAAACGAwjfAgAAAIYDCOQCAAC9BIYDIgjSAgAAvgQAMNMCAADYAgAQ1AIAAL4EADDVAgEAlgQAIdsCQACYBAAh8AICAKUEACGHAwEAlgQAIYgDAQCmBAAhCQsAAMQEACDSAgAAvwQAMNMCAADFAgAQ1AIAAL8EADDVAgEAwAQAIdsCQADDBAAh8AICAMEEACGHAwEAwAQAIYgDAQDCBAAhC90CAQAAAAHeAgEAAAAE3wIBAAAABOACAQAAAAHhAgEAAAAB4gIBAAAAAeMCAQAAAAHkAgEAogQAIesCAQAAAAHsAgEAAAAB7QIBAAAAAQjdAgIAAAAB3gICAAAABd8CAgAAAAXgAgIAAAAB4QICAAAAAeICAgAAAAHjAgIAAAAB5AICAJsEACEL3QIBAAAAAd4CAQAAAAXfAgEAAAAF4AIBAAAAAeECAQAAAAHiAgEAAAAB4wIBAAAAAeQCAQCrBAAh6wIBAAAAAewCAQAAAAHtAgEAAAABCN0CQAAAAAHeAkAAAAAE3wJAAAAABOACQAAAAAHhAkAAAAAB4gJAAAAAAeMCQAAAAAHkAkAAnwQAIQOJAwAAHAAgigMAABwAIIsDAAAcACAJ0gIAAMUEADDTAgAAvwIAENQCAADFBAAw1QIBAJYEACHbAkAAmAQAIfICAQCWBAAh-wIAAMYEjwMijAMCAKcEACGNAwEAlgQAIQcMAACeBAAgPgAAyAQAID8AAMgEACDdAgAAAI8DAt4CAAAAjwMI3wIAAACPAwjkAgAAxwSPAyIHDAAAngQAID4AAMgEACA_AADIBAAg3QIAAACPAwLeAgAAAI8DCN8CAAAAjwMI5AIAAMcEjwMiBN0CAAAAjwMC3gIAAACPAwjfAgAAAI8DCOQCAADIBI8DIgfSAgAAyQQAMNMCAACpAgAQ1AIAAMkEADDbAkAAmAQAIfICAQCWBAAh8wICAKcEACGPAwEAlgQAIQ7SAgAAygQAMNMCAACTAgAQ1AIAAMoEADDbAkAAmAQAIfICAQCWBAAh8wICAKcEACGQAwEAlgQAIZEDAQCmBAAhkgMBAKYEACGTAwEApgQAIZQDAQCWBAAhlQMBAKYEACGWAwEApgQAIZcDQACZBAAhEtICAADLBAAw0wIAAP0BABDUAgAAywQAMNUCAQCWBAAh2wJAAJgEACHwAgIApQQAIfYCQACYBAAh-AJAAJkEACGYAwIApQQAIZkDAQCmBAAhmgMCAKUEACGcAwAAzAScAyKdAyAAzQQAIZ4DIADNBAAhnwMCAKcEACGgAwIApwQAIaEDAgCnBAAhogMCAKcEACEHDAAAngQAID4AANEEACA_AADRBAAg3QIAAACcAwLeAgAAAJwDCN8CAAAAnAMI5AIAANAEnAMiBQwAAJ4EACA-AADPBAAgPwAAzwQAIN0CIAAAAAHkAiAAzgQAIQUMAACeBAAgPgAAzwQAID8AAM8EACDdAiAAAAAB5AIgAM4EACEC3QIgAAAAAeQCIADPBAAhBwwAAJ4EACA-AADRBAAgPwAA0QQAIN0CAAAAnAMC3gIAAACcAwjfAgAAAJwDCOQCAADQBJwDIgTdAgAAAJwDAt4CAAAAnAMI3wIAAACcAwjkAgAA0QScAyIO0gIAANIEADDTAgAA4QEAENQCAADSBAAw1QIBAJYEACHbAkAAmQQAIe8CAQCWBAAhhwMBAKYEACGjAwEAlgQAIaQDAQCWBAAhpQMCAKcEACGmAwEAlgQAIacDIADNBAAhqAMBAKYEACGpAwEApgQAIQrSAgAA0wQAMNMCAADLAQAQ1AIAANMEADDVAgEAlgQAIdsCQACZBAAh7wIBAKYEACH2AkAAmQQAIaoDAQCWBAAhqwMBAJYEACGsA0AAmAQAIQzSAgAA1AQAMNMCAACzAQAQ1AIAANQEADDVAgEAlgQAIdsCQACYBAAh7wIBAJYEACH2AkAAmAQAIf4CAQCmBAAhrANAAJgEACGtAwEAlgQAIa4DAQCmBAAhrwMBAKYEACEQ0gIAANUEADDTAgAAnQEAENQCAADVBAAw1QIBAJYEACHbAkAAmAQAIe8CAQCWBAAh9gJAAJgEACGwAwEAlgQAIbEDAQCWBAAhsgMBAKYEACGzAwEApgQAIbQDQACZBAAhtQNAAJkEACG2AwEApgQAIbcDAQCmBAAhuAMBAKYEACEZ0gIAANYEADDTAgAAhwEAENQCAADWBAAw1QIBAJYEACHbAkAAmAQAIfACAgClBAAh9gJAAJgEACH4AkAAmQQAIYYDAQCmBAAhhwMBAKYEACG5AwEApgQAIboDIADNBAAhuwMBAKYEACG9AwAA1wS9AyK_AwAA2AS_AyLAAyAAzQQAIcEDAQCmBAAhwgMBAKYEACHDAwAAsQQAIMQDQACZBAAhxQMBAKYEACHGAyAA2QQAIccDAQCmBAAhyANAAJkEACHJA0AAmQQAIQcMAACeBAAgPgAA3wQAID8AAN8EACDdAgAAAL0DAt4CAAAAvQMI3wIAAAC9AwjkAgAA3gS9AyIHDAAAngQAID4AAN0EACA_AADdBAAg3QIAAAC_AwLeAgAAAL8DCN8CAAAAvwMI5AIAANwEvwMiBQwAAJsEACA-AADbBAAgPwAA2wQAIN0CIAAAAAHkAiAA2gQAIQUMAACbBAAgPgAA2wQAID8AANsEACDdAiAAAAAB5AIgANoEACEC3QIgAAAAAeQCIADbBAAhBwwAAJ4EACA-AADdBAAgPwAA3QQAIN0CAAAAvwMC3gIAAAC_AwjfAgAAAL8DCOQCAADcBL8DIgTdAgAAAL8DAt4CAAAAvwMI3wIAAAC_AwjkAgAA3QS_AyIHDAAAngQAID4AAN8EACA_AADfBAAg3QIAAAC9AwLeAgAAAL0DCN8CAAAAvQMI5AIAAN4EvQMiBN0CAAAAvQMC3gIAAAC9AwjfAgAAAL0DCOQCAADfBL0DIg8DAADkBAAg0gIAAOAEADDTAgAAZAAQ1AIAAOAEADDVAgEAwAQAIdsCQADjBAAh7wIBAMAEACGHAwEAwgQAIaMDAQDABAAhpAMBAMAEACGlAwIA4QQAIaYDAQDABAAhpwMgAOIEACGoAwEAwgQAIakDAQDCBAAhCN0CAgAAAAHeAgIAAAAE3wICAAAABOACAgAAAAHhAgIAAAAB4gICAAAAAeMCAgAAAAHkAgIAngQAIQLdAiAAAAAB5AIgAM8EACEI3QJAAAAAAd4CQAAAAAXfAkAAAAAF4AJAAAAAAeECQAAAAAHiAkAAAAAB4wJAAAAAAeQCQACcBAAhJwQAAJUFACAFAACWBQAgBgAAlwUAICAAAO4EACAhAACJBQAgIgAAigUAICQAAJgFACAlAACLBQAgJgAAjAUAICcAAI0FACAoAADvBAAgKQAAmQUAINICAACRBQAw0wIAAA8AENQCAACRBQAw1QIBAMAEACHbAkAAwwQAIfACAgDBBAAh9gJAAMMEACH4AkAA4wQAIYYDAQDCBAAhhwMBAMIEACG5AwEAwgQAIboDIADiBAAhuwMBAMIEACG9AwAAkgW9AyK_AwAAkwW_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_gIBAMIEACH_AgEAwgQAIYADAAD0BAAggQMBAMIEACEE3QIAAAD7AgLeAgAAAPsCCN8CAAAA-wII5AIAALQE-wIiDN0CgAAAAAHgAoAAAAAB4QKAAAAAAeICgAAAAAHjAoAAAAAB5AKAAAAAAeUCAQAAAAHmAgEAAAAB5wIBAAAAAegCgAAAAAHpAoAAAAAB6gKAAAAAAQLvAgEAAAAB8gIBAAAAAQgDAADkBAAgBwAA6wQAINICAAD2BAAw0wIAADYAENQCAAD2BAAw2wJAAMMEACHvAgEAwAQAIfICAQDABAAhAvICAQAAAAGCAwEAAAABCgcAAOsEACAXAADsBAAg0gIAAPgEADDTAgAAMAAQ1AIAAPgEADDVAgEAwAQAIdsCQADDBAAh8gIBAMAEACGCAwEAwgQAIYQDAAD5BIQDIgTdAgAAAIQDAt4CAAAAhAMI3wIAAACEAwjkAgAAuQSEAyIC8gIBAAAAAfMCAgAAAAETBwAA6wQAIAgAAPEEACAJAAD8BAAgDgAAxAQAIBAAAP0EACDSAgAA-wQAMNMCAAAVABDUAgAA-wQAMNsCQADDBAAh8gIBAMAEACHzAgIA4QQAIZADAQDABAAhkQMBAMIEACGSAwEAwgQAIZMDAQDCBAAhlAMBAMAEACGVAwEAwgQAIZYDAQDCBAAhlwNAAOMEACEDiQMAABcAIIoDAAAXACCLAwAAFwAgA4kDAAAiACCKAwAAIgAgiwMAACIAIAsHAADrBAAgDwAAgAUAINICAAD-BAAw0wIAACIAENQCAAD-BAAw1QIBAMAEACHbAkAAwwQAIfICAQDABAAh-wIAAP8EjwMijAMCAOEEACGNAwEAwAQAIQTdAgAAAI8DAt4CAAAAjwMI3wIAAACPAwjkAgAAyASPAyIVBwAA6wQAIAgAAPEEACAJAAD8BAAgDgAAxAQAIBAAAP0EACDSAgAA-wQAMNMCAAAVABDUAgAA-wQAMNsCQADDBAAh8gIBAMAEACHzAgIA4QQAIZADAQDABAAhkQMBAMIEACGSAwEAwgQAIZMDAQDCBAAhlAMBAMAEACGVAwEAwgQAIZYDAQDCBAAhlwNAAOMEACHQAwAAFQAg0QMAABUAIAPyAgEAAAAB8wICAAAAAY8DAQAAAAEJCgAAgAUAIA0AAIMFACDSAgAAggUAMNMCAAAcABDUAgAAggUAMNsCQADDBAAh8gIBAMAEACHzAgIA4QQAIY8DAQDABAAhCwsAAMQEACDSAgAAvwQAMNMCAADFAgAQ1AIAAL8EADDVAgEAwAQAIdsCQADDBAAh8AICAMEEACGHAwEAwAQAIYgDAQDCBAAh0AMAAMUCACDRAwAAxQIAIALVAgEAAAABmAMCAAAAAR4RAACHBQAgEgAA8QQAIBMAAPwEACAUAACHBQAgFQAAiAUAIBYAAIkFACAYAACKBQAgGQAA_QQAIBoAAIsFACAbAACMBQAgHAAAjQUAICAAAO4EACDSAgAAhQUAMNMCAAAXABDUAgAAhQUAMNUCAQDABAAh2wJAAMMEACHwAgIAwQQAIfYCQADDBAAh-AJAAOMEACGYAwIAwQQAIZkDAQDCBAAhmgMCAMEEACGcAwAAhgWcAyKdAyAA4gQAIZ4DIADiBAAhnwMCAOEEACGgAwIA4QQAIaEDAgDhBAAhogMCAOEEACEE3QIAAACcAwLeAgAAAJwDCN8CAAAAnAMI5AIAANEEnAMiFQcAAOsEACAIAADxBAAgCQAA_AQAIA4AAMQEACAQAAD9BAAg0gIAAPsEADDTAgAAFQAQ1AIAAPsEADDbAkAAwwQAIfICAQDABAAh8wICAOEEACGQAwEAwAQAIZEDAQDCBAAhkgMBAMIEACGTAwEAwgQAIZQDAQDABAAhlQMBAMIEACGWAwEAwgQAIZcDQADjBAAh0AMAABUAINEDAAAVACADiQMAABUAIIoDAAAVACCLAwAAFQAgA4kDAAARACCKAwAAEQAgiwMAABEAIAOJAwAAMAAgigMAADAAIIsDAAAwACADiQMAADYAIIoDAAA2ACCLAwAANgAgA4kDAAA6ACCKAwAAOgAgiwMAADoAIAOJAwAAPwAgigMAAD8AIIsDAAA_ACAC7wIBAAAAAfICAQAAAAEJAwAA5AQAIAcAAOsEACDSAgAAjwUAMNMCAAARABDUAgAAjwUAMNsCQADDBAAh7wIBAMAEACHyAgEAwAQAIYYDAACQBYYDIgTdAgAAAIYDAt4CAAAAhgMI3wIAAACGAwjkAgAAvQSGAyIlBAAAlQUAIAUAAJYFACAGAACXBQAgIAAA7gQAICEAAIkFACAiAACKBQAgJAAAmAUAICUAAIsFACAmAACMBQAgJwAAjQUAICgAAO8EACApAACZBQAg0gIAAJEFADDTAgAADwAQ1AIAAJEFADDVAgEAwAQAIdsCQADDBAAh8AICAMEEACH2AkAAwwQAIfgCQADjBAAhhgMBAMIEACGHAwEAwgQAIbkDAQDCBAAhugMgAOIEACG7AwEAwgQAIb0DAACSBb0DIr8DAACTBb8DIsADIADiBAAhwQMBAMIEACHCAwEAwgQAIcMDAAD0BAAgxANAAOMEACHFAwEAwgQAIcYDIACUBQAhxwMBAMIEACHIA0AA4wQAIckDQADjBAAhBN0CAAAAvQMC3gIAAAC9AwjfAgAAAL0DCOQCAADfBL0DIgTdAgAAAL8DAt4CAAAAvwMI3wIAAAC_AwjkAgAA3QS_AyIC3QIgAAAAAeQCIADbBAAhA4kDAAADACCKAwAAAwAgiwMAAAMAIAOJAwAABwAgigMAAAcAIIsDAAAHACADiQMAAAsAIIoDAAALACCLAwAACwAgA4kDAABbACCKAwAAWwAgiwMAAFsAIAOJAwAAZAAgigMAAGQAIIsDAABkACALAwAA7AQAINICAACaBQAw0wIAAAsAENQCAACaBQAw1QIBAMAEACHbAkAA4wQAIe8CAQDCBAAh9gJAAOMEACGqAwEAwAQAIasDAQDABAAhrANAAMMEACENAwAA5AQAINICAACbBQAw0wIAAAcAENQCAACbBQAw1QIBAMAEACHbAkAAwwQAIe8CAQDABAAh9gJAAMMEACH-AgEAwgQAIawDQADDBAAhrQMBAMAEACGuAwEAwgQAIa8DAQDCBAAhEQMAAOQEACDSAgAAnAUAMNMCAAADABDUAgAAnAUAMNUCAQDABAAh2wJAAMMEACHvAgEAwAQAIfYCQADDBAAhsAMBAMAEACGxAwEAwAQAIbIDAQDCBAAhswMBAMIEACG0A0AA4wQAIbUDQADjBAAhtgMBAMIEACG3AwEAwgQAIbgDAQDCBAAhAAAAAAHVAwEAAAABAdUDQAAAAAEB1QNAAAAAAQU2AADGCgAgNwAAyQoAINIDAADHCgAg0wMAAMgKACDYAwAAAQAgAzYAAMYKACDSAwAAxwoAINgDAAABACAAAAAFNgAAvgoAIDcAAMQKACDSAwAAvwoAINMDAADDCgAg2AMAAEYAIAU2AAC8CgAgNwAAwQoAINIDAAC9CgAg0wMAAMAKACDYAwAAAQAgAzYAAL4KACDSAwAAvwoAINgDAABGACADNgAAvAoAINIDAAC9CgAg2AMAAAEAIAAAAAAABdUDAgAAAAHbAwIAAAAB3AMCAAAAAd0DAgAAAAHeAwIAAAABAdUDAQAAAAEF1QMCAAAAAdsDAgAAAAHcAwIAAAAB3QMCAAAAAd4DAgAAAAEFNgAArwoAIDcAALoKACDSAwAAsAoAINMDAAC5CgAg2AMAABoAIAc2AACtCgAgNwAAtwoAINIDAACuCgAg0wMAALYKACDWAwAADwAg1wMAAA8AINgDAAABACAHNgAAqwoAIDcAALQKACDSAwAArAoAINMDAACzCgAg1gMAAEQAINcDAABEACDYAwAARgAgCzYAAMYFADA3AADLBQAw0gMAAMcFADDTAwAAyAUAMNQDAADJBQAg1QMAAMoFADDWAwAAygUAMNcDAADKBQAw2AMAAMoFADDZAwAAzAUAMNoDAADNBQAwCzYAALoFADA3AAC_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-wkAIDcAAIEKACDSAwAA_AkAINMDAACACgAg2AMAABoAIAU2AAD5CQAgNwAA_gkAINIDAAD6CQAg0wMAAP0JACDYAwAAAQAgAzYAAPsJACDSAwAA_AkAINgDAAAaACADNgAA-QkAINIDAAD6CQAg2AMAAAEAIAAAAAAACzYAAIcGADA3AACMBgAw0gMAAIgGADDTAwAAiQYAMNQDAACKBgAg1QMAAIsGADDWAwAAiwYAMNcDAACLBgAw2AMAAIsGADDZAwAAjQYAMNoDAACOBgAwBAoAAJQGACDbAkAAAAAB8gIBAAAAAfMCAgAAAAECAAAAHgAgNgAAkwYAIAMAAAAeACA2AACTBgAgNwAAkQYAIAEvAAD4CQAwCgoAAIAFACANAACDBQAg0gIAAIIFADDTAgAAHAAQ1AIAAIIFADDbAkAAwwQAIfICAQDABAAh8wICAOEEACGPAwEAwAQAIc4DAACBBQAgAgAAAB4AIC8AAJEGACACAAAAjwYAIC8AAJAGACAH0gIAAI4GADDTAgAAjwYAENQCAACOBgAw2wJAAMMEACHyAgEAwAQAIfMCAgDhBAAhjwMBAMAEACEH0gIAAI4GADDTAgAAjwYAENQCAACOBgAw2wJAAMMEACHyAgEAwAQAIfMCAgDhBAAhjwMBAMAEACED2wJAAKIFACHyAgEAoQUAIfMCAgC0BQAhBAoAAJIGACDbAkAAogUAIfICAQChBQAh8wICALQFACEFNgAA8wkAIDcAAPYJACDSAwAA9AkAINMDAAD1CQAg2AMAAC0AIAQKAACUBgAg2wJAAAAAAfICAQAAAAHzAgIAAAABAzYAAPMJACDSAwAA9AkAINgDAAAtACAENgAAhwYAMNIDAACIBgAw1AMAAIoGACDYAwAAiwYAMAAAAAAAAAHVAwAAAI8DAgU2AADrCQAgNwAA8QkAINIDAADsCQAg0wMAAPAJACDYAwAAGgAgBTYAAOkJACA3AADuCQAg0gMAAOoJACDTAwAA7QkAINgDAAAtACADNgAA6wkAINIDAADsCQAg2AMAABoAIAM2AADpCQAg0gMAAOoJACDYAwAALQAgAAAAAAAFNgAA5AkAIDcAAOcJACDSAwAA5QkAINMDAADmCQAg2AMAAMICACADNgAA5AkAINIDAADlCQAg2AMAAMICACAAAAAAAAU2AADECQAgNwAA4gkAINIDAADFCQAg0wMAAOEJACDYAwAAGgAgBzYAAL0HACA3AADXBwAg0gMAAL4HACDTAwAA1gcAINYDAAAXACDXAwAAFwAg2AMAABoAIAs2AADHBgAwNwAAzAYAMNIDAADIBgAw0wMAAMkGADDUAwAAygYAINUDAADLBgAw1gMAAMsGADDXAwAAywYAMNgDAADLBgAw2QMAAM0GADDaAwAAzgYAMAs2AAC-BgAwNwAAwgYAMNIDAAC_BgAw0wMAAMAGADDUAwAAwQYAINUDAACLBgAw1gMAAIsGADDXAwAAiwYAMNgDAACLBgAw2QMAAMMGADDaAwAAjgYAMAs2AACyBgAwNwAAtwYAMNIDAACzBgAw0wMAALQGADDUAwAAtQYAINUDAAC2BgAw1gMAALYGADDXAwAAtgYAMNgDAAC2BgAw2QMAALgGADDaAwAAuQYAMAUHAACfBgAg1QIBAAAAAdsCQAAAAAH7AgAAAI8DAo0DAQAAAAECAAAAJAAgNgAAvQYAIAMAAAAkACA2AAC9BgAgNwAAvAYAIAEvAADgCQAwCwcAAOsEACAPAACABQAg0gIAAP4EADDTAgAAIgAQ1AIAAP4EADDVAgEAAAAB2wJAAMMEACHyAgEAwAQAIfsCAAD_BI8DIowDAgDhBAAhjQMBAMAEACECAAAAJAAgLwAAvAYAIAIAAAC6BgAgLwAAuwYAIAnSAgAAuQYAMNMCAAC6BgAQ1AIAALkGADDVAgEAwAQAIdsCQADDBAAh8gIBAMAEACH7AgAA_wSPAyKMAwIA4QQAIY0DAQDABAAhCdICAAC5BgAw0wIAALoGABDUAgAAuQYAMNUCAQDABAAh2wJAAMMEACHyAgEAwAQAIfsCAAD_BI8DIowDAgDhBAAhjQMBAMAEACEE1QIBAKEFACHbAkAAogUAIfsCAACcBo8DIo0DAQChBQAhBQcAAJ0GACDVAgEAoQUAIdsCQACiBQAh-wIAAJwGjwMijQMBAKEFACEFBwAAnwYAINUCAQAAAAHbAkAAAAAB-wIAAACPAwKNAwEAAAABAw0AAKcGACDbAkAAAAABjwMBAAAAAQIAAAAeACA2AADGBgAgAwAAAB4AIDYAAMYGACA3AADFBgAgAS8AAN8JADACAAAAHgAgLwAAxQYAIAIAAACPBgAgLwAAxAYAIALbAkAAogUAIY8DAQChBQAhAw0AAKYGACDbAkAAogUAIY8DAQChBQAhAw0AAKcGACDbAkAAAAABjwMBAAAAARgRAADOBwAgEgAAvwcAIBMAAMAHACAVAADCBwAgFgAAwwcAIBgAAMQHACAZAADFBwAgGgAAxgcAIBsAAMcHACAcAADIBwAgIAAAyQcAINUCAQAAAAHbAkAAAAAB8AICAAAAAfYCQAAAAAH4AkAAAAABmAMCAAAAAZwDAAAAnAMCnQMgAAAAAZ4DIAAAAAGfAwIAAAABoAMCAAAAAaEDAgAAAAGiAwIAAAABAgAAABoAIDYAANUHACADAAAAGgAgNgAA1QcAIDcAANMGACABLwAA3gkAMB8RAACHBQAgEgAA8QQAIBMAAPwEACAUAACHBQAgFQAAiAUAIBYAAIkFACAYAACKBQAgGQAA_QQAIBoAAIsFACAbAACMBQAgHAAAjQUAICAAAO4EACDSAgAAhQUAMNMCAAAXABDUAgAAhQUAMNUCAQAAAAHbAkAAwwQAIfACAgAAAAH2AkAAwwQAIfgCQADjBAAhmAMCAMEEACGZAwEAwgQAIZoDAgDBBAAhnAMAAIYFnAMinQMgAOIEACGeAyAA4gQAIZ8DAgDhBAAhoAMCAOEEACGhAwIA4QQAIaIDAgDhBAAhzwMAAIQFACACAAAAGgAgLwAA0wYAIAIAAADPBgAgLwAA0AYAIBLSAgAAzgYAMNMCAADPBgAQ1AIAAM4GADDVAgEAwAQAIdsCQADDBAAh8AICAMEEACH2AkAAwwQAIfgCQADjBAAhmAMCAMEEACGZAwEAwgQAIZoDAgDBBAAhnAMAAIYFnAMinQMgAOIEACGeAyAA4gQAIZ8DAgDhBAAhoAMCAOEEACGhAwIA4QQAIaIDAgDhBAAhEtICAADOBgAw0wIAAM8GABDUAgAAzgYAMNUCAQDABAAh2wJAAMMEACHwAgIAwQQAIfYCQADDBAAh-AJAAOMEACGYAwIAwQQAIZkDAQDCBAAhmgMCAMEEACGcAwAAhgWcAyKdAyAA4gQAIZ4DIADiBAAhnwMCAOEEACGgAwIA4QQAIaEDAgDhBAAhogMCAOEEACEN1QIBAKEFACHbAkAAogUAIfACAgCyBQAh9gJAAKIFACH4AkAAowUAIZgDAgCyBQAhnAMAANEGnAMinQMgANIGACGeAyAA0gYAIZ8DAgC0BQAhoAMCALQFACGhAwIAtAUAIaIDAgC0BQAhAdUDAAAAnAMCAdUDIAAAAAEYEQAA1AYAIBIAANUGACATAADWBgAgFQAA1wYAIBYAANgGACAYAADZBgAgGQAA2gYAIBoAANsGACAbAADcBgAgHAAA3QYAICAAAN4GACDVAgEAoQUAIdsCQACiBQAh8AICALIFACH2AkAAogUAIfgCQACjBQAhmAMCALIFACGcAwAA0QacAyKdAyAA0gYAIZ4DIADSBgAhnwMCALQFACGgAwIAtAUAIaEDAgC0BQAhogMCALQFACEHNgAAywkAIDcAANwJACDSAwAAzAkAINMDAADbCQAg1gMAABUAINcDAAAVACDYAwAALQAgBzYAAM4JACA3AADZCQAg0gMAAM8JACDTAwAA2AkAINYDAAAXACDXAwAAFwAg2AMAABoAIAs2AADKBwAwNwAA0AcAMNIDAADLBwAw0wMAAM8HADDUAwAAzAcAINUDAADLBgAw1gMAAMsGADDXAwAAywYAMNgDAADLBgAw2QMAANEHADDaAwAAzgYAMAs2AACtBwAwNwAAsgcAMNIDAACuBwAw0wMAAK8HADDUAwAAsAcAINUDAACxBwAw1gMAALEHADDXAwAAsQcAMNgDAACxBwAw2QMAALMHADDaAwAAtAcAMAs2AAChBwAwNwAApgcAMNIDAACiBwAw0wMAAKMHADDUAwAApAcAINUDAAClBwAw1gMAAKUHADDXAwAApQcAMNgDAAClBwAw2QMAAKcHADDaAwAAqAcAMAs2AACVBwAwNwAAmgcAMNIDAACWBwAw0wMAAJcHADDUAwAAmAcAINUDAACZBwAw1gMAAJkHADDXAwAAmQcAMNgDAACZBwAw2QMAAJsHADDaAwAAnAcAMAs2AACMBwAwNwAAkAcAMNIDAACNBwAw0wMAAI4HADDUAwAAjwcAINUDAAC2BgAw1gMAALYGADDXAwAAtgYAMNgDAAC2BgAw2QMAAJEHADDaAwAAuQYAMAs2AACABwAwNwAAhQcAMNIDAACBBwAw0wMAAIIHADDUAwAAgwcAINUDAACEBwAw1gMAAIQHADDXAwAAhAcAMNgDAACEBwAw2QMAAIYHADDaAwAAhwcAMAs2AAD0BgAwNwAA-QYAMNIDAAD1BgAw0wMAAPYGADDUAwAA9wYAINUDAAD4BgAw1gMAAPgGADDXAwAA-AYAMNgDAAD4BgAw2QMAAPoGADDaAwAA-wYAMAs2AADoBgAwNwAA7QYAMNIDAADpBgAw0wMAAOoGADDUAwAA6wYAINUDAADsBgAw1gMAAOwGADDXAwAA7AYAMNgDAADsBgAw2QMAAO4GADDaAwAA7wYAMAs2AADfBgAwNwAA4wYAMNIDAADgBgAw0wMAAOEGADDUAwAA4gYAINUDAADKBQAw1gMAAMoFADDXAwAAygUAMNgDAADKBQAw2QMAAOQGADDaAwAAzQUAMA8DAADTBQAgGgAA1QUAIB0AANYFACAeAADUBQAg1QIBAAAAAdsCQAAAAAHvAgEAAAAB8AICAAAAAfECAQAAAAHzAgIAAAAB9AIBAAAAAfUCAgAAAAH2AkAAAAAB9wJAAAAAAfgCQAAAAAECAAAARgAgNgAA5wYAIAMAAABGACA2AADnBgAgNwAA5gYAIAEvAADXCQAwAgAAAEYAIC8AAOYGACACAAAAzgUAIC8AAOUGACAL1QIBAKEFACHbAkAAogUAIe8CAQCzBQAh8AICALIFACHxAgEAswUAIfMCAgCyBQAh9AIBALMFACH1AgIAtAUAIfYCQACiBQAh9wJAAKMFACH4AkAAowUAIQ8DAAC2BQAgGgAAuQUAIB0AALcFACAeAAC4BQAg1QIBAKEFACHbAkAAogUAIe8CAQCzBQAh8AICALIFACHxAgEAswUAIfMCAgCyBQAh9AIBALMFACH1AgIAtAUAIfYCQACiBQAh9wJAAKMFACH4AkAAowUAIQ8DAADTBQAgGgAA1QUAIB0AANYFACAeAADUBQAg1QIBAAAAAdsCQAAAAAHvAgEAAAAB8AICAAAAAfECAQAAAAHzAgIAAAAB9AIBAAAAAfUCAgAAAAH2AkAAAAAB9wJAAAAAAfgCQAAAAAEHAwAA3gUAIC-AAAAAAdUCAQAAAAHbAkAAAAAB7wIBAAAAAfYCQAAAAAH5AgIAAAABAgAAAEEAIDYAAPMGACADAAAAQQAgNgAA8wYAIDcAAPIGACABLwAA1gkAMAwDAADkBAAgBwAA8QQAIC8AAOYEACDSAgAA8AQAMNMCAAA_ABDUAgAA8AQAMNUCAQAAAAHbAkAAwwQAIe8CAQDABAAh8gIBAMIEACH2AkAAwwQAIfkCAgDhBAAhAgAAAEEAIC8AAPIGACACAAAA8AYAIC8AAPEGACAKLwAA5gQAINICAADvBgAw0wIAAPAGABDUAgAA7wYAMNUCAQDABAAh2wJAAMMEACHvAgEAwAQAIfICAQDCBAAh9gJAAMMEACH5AgIA4QQAIQovAADmBAAg0gIAAO8GADDTAgAA8AYAENQCAADvBgAw1QIBAMAEACHbAkAAwwQAIe8CAQDABAAh8gIBAMIEACH2AkAAwwQAIfkCAgDhBAAhBi-AAAAAAdUCAQChBQAh2wJAAKIFACHvAgEAoQUAIfYCQACiBQAh-QICALQFACEHAwAA3AUAIC-AAAAAAdUCAQChBQAh2wJAAKIFACHvAgEAoQUAIfYCQACiBQAh-QICALQFACEHAwAA3gUAIC-AAAAAAdUCAQAAAAHbAkAAAAAB7wIBAAAAAfYCQAAAAAH5AgIAAAABDAMAAOkFACDVAgEAAAAB2wJAAAAAAe8CAQAAAAHzAgIAAAAB-wIAAAD7AgL8AgEAAAAB_QIBAAAAAf4CAQAAAAH_AgEAAAABgAOAAAAAAYEDAQAAAAECAAAAPAAgNgAA_wYAIAMAAAA8ACA2AAD_BgAgNwAA_gYAIAEvAADVCQAwEQMAAOwEACAHAADrBAAg0gIAAPIEADDTAgAAOgAQ1AIAAPIEADDVAgEAAAAB2wJAAMMEACHvAgEAwgQAIfICAQDABAAh8wICAMEEACH7AgAA8wT7AiL8AgEAwgQAIf0CAQDCBAAh_gIBAMIEACH_AgEAwgQAIYADAAD0BAAggQMBAMIEACECAAAAPAAgLwAA_gYAIAIAAAD8BgAgLwAA_QYAIA_SAgAA-wYAMNMCAAD8BgAQ1AIAAPsGADDVAgEAwAQAIdsCQADDBAAh7wIBAMIEACHyAgEAwAQAIfMCAgDBBAAh-wIAAPME-wIi_AIBAMIEACH9AgEAwgQAIf4CAQDCBAAh_wIBAMIEACGAAwAA9AQAIIEDAQDCBAAhD9ICAAD7BgAw0wIAAPwGABDUAgAA-wYAMNUCAQDABAAh2wJAAMMEACHvAgEAwgQAIfICAQDABAAh8wICAMEEACH7AgAA8wT7AiL8AgEAwgQAIf0CAQDCBAAh_gIBAMIEACH_AgEAwgQAIYADAAD0BAAggQMBAMIEACEL1QIBAKEFACHbAkAAogUAIe8CAQCzBQAh8wICALIFACH7AgAA5QX7AiL8AgEAswUAIf0CAQCzBQAh_gIBALMFACH_AgEAswUAIYADgAAAAAGBAwEAswUAIQwDAADnBQAg1QIBAKEFACHbAkAAogUAIe8CAQCzBQAh8wICALIFACH7AgAA5QX7AiL8AgEAswUAIf0CAQCzBQAh_gIBALMFACH_AgEAswUAIYADgAAAAAGBAwEAswUAIQwDAADpBQAg1QIBAAAAAdsCQAAAAAHvAgEAAAAB8wICAAAAAfsCAAAA-wIC_AIBAAAAAf0CAQAAAAH-AgEAAAAB_wIBAAAAAYADgAAAAAGBAwEAAAABAwMAAPAFACDbAkAAAAAB7wIBAAAAAQIAAAA4ACA2AACLBwAgAwAAADgAIDYAAIsHACA3AACKBwAgAS8AANQJADAJAwAA5AQAIAcAAOsEACDSAgAA9gQAMNMCAAA2ABDUAgAA9gQAMNsCQADDBAAh7wIBAMAEACHyAgEAwAQAIcsDAAD1BAAgAgAAADgAIC8AAIoHACACAAAAiAcAIC8AAIkHACAG0gIAAIcHADDTAgAAiAcAENQCAACHBwAw2wJAAMMEACHvAgEAwAQAIfICAQDABAAhBtICAACHBwAw0wIAAIgHABDUAgAAhwcAMNsCQADDBAAh7wIBAMAEACHyAgEAwAQAIQLbAkAAogUAIe8CAQChBQAhAwMAAO4FACDbAkAAogUAIe8CAQChBQAhAwMAAPAFACDbAkAAAAAB7wIBAAAAAQYPAACgBgAg1QIBAAAAAdsCQAAAAAH7AgAAAI8DAowDAgAAAAGNAwEAAAABAgAAACQAIDYAAJQHACADAAAAJAAgNgAAlAcAIDcAAJMHACABLwAA0wkAMAIAAAAkACAvAACTBwAgAgAAALoGACAvAACSBwAgBdUCAQChBQAh2wJAAKIFACH7AgAAnAaPAyKMAwIAtAUAIY0DAQChBQAhBg8AAJ4GACDVAgEAoQUAIdsCQACiBQAh-wIAAJwGjwMijAMCALQFACGNAwEAoQUAIQYPAACgBgAg1QIBAAAAAdsCQAAAAAH7AgAAAI8DAowDAgAAAAGNAwEAAAABBRcAAPgFACDVAgEAAAAB2wJAAAAAAYIDAQAAAAGEAwAAAIQDAgIAAAAyACA2AACgBwAgAwAAADIAIDYAAKAHACA3AACfBwAgAS8AANIJADALBwAA6wQAIBcAAOwEACDSAgAA-AQAMNMCAAAwABDUAgAA-AQAMNUCAQAAAAHbAkAAwwQAIfICAQDABAAhggMBAMIEACGEAwAA-QSEAyLMAwAA9wQAIAIAAAAyACAvAACfBwAgAgAAAJ0HACAvAACeBwAgCNICAACcBwAw0wIAAJ0HABDUAgAAnAcAMNUCAQDABAAh2wJAAMMEACHyAgEAwAQAIYIDAQDCBAAhhAMAAPkEhAMiCNICAACcBwAw0wIAAJ0HABDUAgAAnAcAMNUCAQDABAAh2wJAAMMEACHyAgEAwAQAIYIDAQDCBAAhhAMAAPkEhAMiBNUCAQChBQAh2wJAAKIFACGCAwEAswUAIYQDAAD0BYQDIgUXAAD2BQAg1QIBAKEFACHbAkAAogUAIYIDAQCzBQAhhAMAAPQFhAMiBRcAAPgFACDVAgEAAAAB2wJAAAAAAYIDAQAAAAGEAwAAAIQDAgQDAACABgAg2wJAAAAAAe8CAQAAAAGGAwAAAIYDAgIAAAATACA2AACsBwAgAwAAABMAIDYAAKwHACA3AACrBwAgAS8AANEJADAKAwAA5AQAIAcAAOsEACDSAgAAjwUAMNMCAAARABDUAgAAjwUAMNsCQADDBAAh7wIBAMAEACHyAgEAwAQAIYYDAACQBYYDIssDAACOBQAgAgAAABMAIC8AAKsHACACAAAAqQcAIC8AAKoHACAH0gIAAKgHADDTAgAAqQcAENQCAACoBwAw2wJAAMMEACHvAgEAwAQAIfICAQDABAAhhgMAAJAFhgMiB9ICAACoBwAw0wIAAKkHABDUAgAAqAcAMNsCQADDBAAh7wIBAMAEACHyAgEAwAQAIYYDAACQBYYDIgPbAkAAogUAIe8CAQChBQAhhgMAAPwFhgMiBAMAAP4FACDbAkAAogUAIe8CAQChBQAhhgMAAPwFhgMiBAMAAIAGACDbAkAAAAAB7wIBAAAAAYYDAAAAhgMCDggAALkHACAJAAC6BwAgDgAAuwcAIBAAALwHACDbAkAAAAAB8wICAAAAAZADAQAAAAGRAwEAAAABkgMBAAAAAZMDAQAAAAGUAwEAAAABlQMBAAAAAZYDAQAAAAGXA0AAAAABAgAAAC0AIDYAALgHACADAAAALQAgNgAAuAcAIDcAALcHACABLwAA0AkAMBQHAADrBAAgCAAA8QQAIAkAAPwEACAOAADEBAAgEAAA_QQAINICAAD7BAAw0wIAABUAENQCAAD7BAAw2wJAAMMEACHyAgEAwAQAIfMCAgDhBAAhkAMBAMAEACGRAwEAwgQAIZIDAQDCBAAhkwMBAMIEACGUAwEAwAQAIZUDAQDCBAAhlgMBAMIEACGXA0AA4wQAIc0DAAD6BAAgAgAAAC0AIC8AALcHACACAAAAtQcAIC8AALYHACAO0gIAALQHADDTAgAAtQcAENQCAAC0BwAw2wJAAMMEACHyAgEAwAQAIfMCAgDhBAAhkAMBAMAEACGRAwEAwgQAIZIDAQDCBAAhkwMBAMIEACGUAwEAwAQAIZUDAQDCBAAhlgMBAMIEACGXA0AA4wQAIQ7SAgAAtAcAMNMCAAC1BwAQ1AIAALQHADDbAkAAwwQAIfICAQDABAAh8wICAOEEACGQAwEAwAQAIZEDAQDCBAAhkgMBAMIEACGTAwEAwgQAIZQDAQDABAAhlQMBAMIEACGWAwEAwgQAIZcDQADjBAAhCtsCQACiBQAh8wICALQFACGQAwEAoQUAIZEDAQCzBQAhkgMBALMFACGTAwEAswUAIZQDAQChBQAhlQMBALMFACGWAwEAswUAIZcDQACjBQAhDggAAK4GACAJAACvBgAgDgAAsAYAIBAAALEGACDbAkAAogUAIfMCAgC0BQAhkAMBAKEFACGRAwEAswUAIZIDAQCzBQAhkwMBALMFACGUAwEAoQUAIZUDAQCzBQAhlgMBALMFACGXA0AAowUAIQ4IAAC5BwAgCQAAugcAIA4AALsHACAQAAC8BwAg2wJAAAAAAfMCAgAAAAGQAwEAAAABkQMBAAAAAZIDAQAAAAGTAwEAAAABlAMBAAAAAZUDAQAAAAGWAwEAAAABlwNAAAAAAQM2AAC9BwAg0gMAAL4HACDYAwAAGgAgBDYAAMcGADDSAwAAyAYAMNQDAADKBgAg2AMAAMsGADAENgAAvgYAMNIDAAC_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_CQAgNwAAwgkAINIDAADACQAg0wMAAMEJACDYAwAAAQAgAzYAAL8JACDSAwAAwAkAINgDAAABACAAAAAHNgAAugkAIDcAAL0JACDSAwAAuwkAINMDAAC8CQAg1gMAAA8AINcDAAAPACDYAwAAAQAgAzYAALoJACDSAwAAuwkAINgDAAABACAAAAAFNgAAtQkAIDcAALgJACDSAwAAtgkAINMDAAC3CQAg2AMAAAEAIAM2AAC1CQAg0gMAALYJACDYAwAAAQAgAAAABTYAALAJACA3AACzCQAg0gMAALEJACDTAwAAsgkAINgDAAABACADNgAAsAkAINIDAACxCQAg2AMAAAEAIAAAAAAAAdUDAAAAvQMCAdUDAAAAvwMCAdUDIAAAAAELNgAA-AgAMDcAAP0IADDSAwAA-QgAMNMDAAD6CAAw1AMAAPsIACDVAwAA_AgAMNYDAAD8CAAw1wMAAPwIADDYAwAA_AgAMNkDAAD-CAAw2gMAAP8IADALNgAA7AgAMDcAAPEIADDSAwAA7QgAMNMDAADuCAAw1AMAAO8IACDVAwAA8AgAMNYDAADwCAAw1wMAAPAIADDYAwAA8AgAMNkDAADyCAAw2gMAAPMIADALNgAA4AgAMDcAAOUIADDSAwAA4QgAMNMDAADiCAAw1AMAAOMIACDVAwAA5AgAMNYDAADkCAAw1wMAAOQIADDYAwAA5AgAMNkDAADmCAAw2gMAAOcIADALNgAA1wgAMDcAANsIADDSAwAA2AgAMNMDAADZCAAw1AMAANoIACDVAwAApQcAMNYDAAClBwAw1wMAAKUHADDYAwAApQcAMNkDAADcCAAw2gMAAKgHADALNgAAzggAMDcAANIIADDSAwAAzwgAMNMDAADQCAAw1AMAANEIACDVAwAAmQcAMNYDAACZBwAw1wMAAJkHADDYAwAAmQcAMNkDAADTCAAw2gMAAJwHADALNgAAwggAMDcAAMcIADDSAwAAwwgAMNMDAADECAAw1AMAAMUIACDVAwAAxggAMNYDAADGCAAw1wMAAMYIADDYAwAAxggAMNkDAADICAAw2gMAAMkIADALNgAAuQgAMDcAAL0IADDSAwAAuggAMNMDAAC7CAAw1AMAALwIACDVAwAAhAcAMNYDAACEBwAw1wMAAIQHADDYAwAAhAcAMNkDAAC-CAAw2gMAAIcHADALNgAAsAgAMDcAALQIADDSAwAAsQgAMNMDAACyCAAw1AMAALMIACDVAwAA-AYAMNYDAAD4BgAw1wMAAPgGADDYAwAA-AYAMNkDAAC1CAAw2gMAAPsGADALNgAApwgAMDcAAKsIADDSAwAAqAgAMNMDAACpCAAw1AMAAKoIACDVAwAA7AYAMNYDAADsBgAw1wMAAOwGADDYAwAA7AYAMNkDAACsCAAw2gMAAO8GADALNgAAnggAMDcAAKIIADDSAwAAnwgAMNMDAACgCAAw1AMAAKEIACDVAwAAygUAMNYDAADKBQAw1wMAAMoFADDYAwAAygUAMNkDAACjCAAw2gMAAM0FADALNgAAlQgAMDcAAJkIADDSAwAAlggAMNMDAACXCAAw1AMAAJgIACDVAwAAvgUAMNYDAAC-BQAw1wMAAL4FADDYAwAAvgUAMNkDAACaCAAw2gMAAMEFADALNgAAiQgAMDcAAI4IADDSAwAAiggAMNMDAACLCAAw1AMAAIwIACDVAwAAjQgAMNYDAACNCAAw1wMAAI0IADDYAwAAjQgAMNkDAACPCAAw2gMAAJAIADAK1QIBAAAAAdsCQAAAAAGHAwEAAAABowMBAAAAAaQDAQAAAAGlAwIAAAABpgMBAAAAAacDIAAAAAGoAwEAAAABqQMBAAAAAQIAAABmACA2AACUCAAgAwAAAGYAIDYAAJQIACA3AACTCAAgAS8AAK8JADAPAwAA5AQAINICAADgBAAw0wIAAGQAENQCAADgBAAw1QIBAAAAAdsCQADjBAAh7wIBAMAEACGHAwEAwgQAIaMDAQDABAAhpAMBAMAEACGlAwIA4QQAIaYDAQDABAAhpwMgAOIEACGoAwEAwgQAIakDAQDCBAAhAgAAAGYAIC8AAJMIACACAAAAkQgAIC8AAJIIACAO0gIAAJAIADDTAgAAkQgAENQCAACQCAAw1QIBAMAEACHbAkAA4wQAIe8CAQDABAAhhwMBAMIEACGjAwEAwAQAIaQDAQDABAAhpQMCAOEEACGmAwEAwAQAIacDIADiBAAhqAMBAMIEACGpAwEAwgQAIQ7SAgAAkAgAMNMCAACRCAAQ1AIAAJAIADDVAgEAwAQAIdsCQADjBAAh7wIBAMAEACGHAwEAwgQAIaMDAQDABAAhpAMBAMAEACGlAwIA4QQAIaYDAQDABAAhpwMgAOIEACGoAwEAwgQAIakDAQDCBAAhCtUCAQChBQAh2wJAAKMFACGHAwEAswUAIaMDAQChBQAhpAMBAKEFACGlAwIAtAUAIaYDAQChBQAhpwMgANIGACGoAwEAswUAIakDAQCzBQAhCtUCAQChBQAh2wJAAKMFACGHAwEAswUAIaMDAQChBQAhpAMBAKEFACGlAwIAtAUAIaYDAQChBQAhpwMgANIGACGoAwEAswUAIakDAQCzBQAhCtUCAQAAAAHbAkAAAAABhwMBAAAAAaMDAQAAAAGkAwEAAAABpQMCAAAAAaYDAQAAAAGnAyAAAAABqAMBAAAAAakDAQAAAAEDHwAAqwUAINsCQAAAAAHuAgEAAAABAgAAAE0AIDYAAJ0IACADAAAATQAgNgAAnQgAIDcAAJwIACABLwAArgkAMAIAAABNACAvAACcCAAgAgAAAMIFACAvAACbCAAgAtsCQACiBQAh7gIBAKEFACEDHwAAqQUAINsCQACiBQAh7gIBAKEFACEDHwAAqwUAINsCQAAAAAHuAgEAAAABDwcAANIFACAaAADVBQAgHQAA1gUAIB4AANQFACDVAgEAAAAB2wJAAAAAAfACAgAAAAHxAgEAAAAB8gIBAAAAAfMCAgAAAAH0AgEAAAAB9QICAAAAAfYCQAAAAAH3AkAAAAAB-AJAAAAAAQIAAABGACA2AACmCAAgAwAAAEYAIDYAAKYIACA3AAClCAAgAS8AAK0JADACAAAARgAgLwAApQgAIAIAAADOBQAgLwAApAgAIAvVAgEAoQUAIdsCQACiBQAh8AICALIFACHxAgEAswUAIfICAQChBQAh8wICALIFACH0AgEAswUAIfUCAgC0BQAh9gJAAKIFACH3AkAAowUAIfgCQACjBQAhDwcAALUFACAaAAC5BQAgHQAAtwUAIB4AALgFACDVAgEAoQUAIdsCQACiBQAh8AICALIFACHxAgEAswUAIfICAQChBQAh8wICALIFACH0AgEAswUAIfUCAgC0BQAh9gJAAKIFACH3AkAAowUAIfgCQACjBQAhDwcAANIFACAaAADVBQAgHQAA1gUAIB4AANQFACDVAgEAAAAB2wJAAAAAAfACAgAAAAHxAgEAAAAB8gIBAAAAAfMCAgAAAAH0AgEAAAAB9QICAAAAAfYCQAAAAAH3AkAAAAAB-AJAAAAAAQcHAADfBQAgL4AAAAAB1QIBAAAAAdsCQAAAAAHyAgEAAAAB9gJAAAAAAfkCAgAAAAECAAAAQQAgNgAArwgAIAMAAABBACA2AACvCAAgNwAArggAIAEvAACsCQAwAgAAAEEAIC8AAK4IACACAAAA8AYAIC8AAK0IACAGL4AAAAAB1QIBAKEFACHbAkAAogUAIfICAQCzBQAh9gJAAKIFACH5AgIAtAUAIQcHAADdBQAgL4AAAAAB1QIBAKEFACHbAkAAogUAIfICAQCzBQAh9gJAAKIFACH5AgIAtAUAIQcHAADfBQAgL4AAAAAB1QIBAAAAAdsCQAAAAAHyAgEAAAAB9gJAAAAAAfkCAgAAAAEMBwAA6AUAINUCAQAAAAHbAkAAAAAB8gIBAAAAAfMCAgAAAAH7AgAAAPsCAvwCAQAAAAH9AgEAAAAB_gIBAAAAAf8CAQAAAAGAA4AAAAABgQMBAAAAAQIAAAA8ACA2AAC4CAAgAwAAADwAIDYAALgIACA3AAC3CAAgAS8AAKsJADACAAAAPAAgLwAAtwgAIAIAAAD8BgAgLwAAtggAIAvVAgEAoQUAIdsCQACiBQAh8gIBAKEFACHzAgIAsgUAIfsCAADlBfsCIvwCAQCzBQAh_QIBALMFACH-AgEAswUAIf8CAQCzBQAhgAOAAAAAAYEDAQCzBQAhDAcAAOYFACDVAgEAoQUAIdsCQACiBQAh8gIBAKEFACHzAgIAsgUAIfsCAADlBfsCIvwCAQCzBQAh_QIBALMFACH-AgEAswUAIf8CAQCzBQAhgAOAAAAAAYEDAQCzBQAhDAcAAOgFACDVAgEAAAAB2wJAAAAAAfICAQAAAAHzAgIAAAAB-wIAAAD7AgL8AgEAAAAB_QIBAAAAAf4CAQAAAAH_AgEAAAABgAOAAAAAAYEDAQAAAAEDBwAA7wUAINsCQAAAAAHyAgEAAAABAgAAADgAIDYAAMEIACADAAAAOAAgNgAAwQgAIDcAAMAIACABLwAAqgkAMAIAAAA4ACAvAADACAAgAgAAAIgHACAvAAC_CAAgAtsCQACiBQAh8gIBAKEFACEDBwAA7QUAINsCQACiBQAh8gIBAKEFACEDBwAA7wUAINsCQAAAAAHyAgEAAAABB9UCAQAAAAHWAgEAAAAB2AIBAAAAAdkCAQAAAAHaAoAAAAAB2wJAAAAAAdwCQAAAAAECAAAAXQAgNgAAzQgAIAMAAABdACA2AADNCAAgNwAAzAgAIAEvAACpCQAwDCMAAOQEACDSAgAA5QQAMNMCAABbABDUAgAA5QQAMNUCAQAAAAHWAgEAwAQAIdcCAQDABAAh2AIBAMAEACHZAgEAwAQAIdoCAADmBAAg2wJAAMMEACHcAkAA4wQAIQIAAABdACAvAADMCAAgAgAAAMoIACAvAADLCAAgC9ICAADJCAAw0wIAAMoIABDUAgAAyQgAMNUCAQDABAAh1gIBAMAEACHXAgEAwAQAIdgCAQDABAAh2QIBAMAEACHaAgAA5gQAINsCQADDBAAh3AJAAOMEACEL0gIAAMkIADDTAgAAyggAENQCAADJCAAw1QIBAMAEACHWAgEAwAQAIdcCAQDABAAh2AIBAMAEACHZAgEAwAQAIdoCAADmBAAg2wJAAMMEACHcAkAA4wQAIQfVAgEAoQUAIdYCAQChBQAh2AIBAKEFACHZAgEAoQUAIdoCgAAAAAHbAkAAogUAIdwCQACjBQAhB9UCAQChBQAh1gIBAKEFACHYAgEAoQUAIdkCAQChBQAh2gKAAAAAAdsCQACiBQAh3AJAAKMFACEH1QIBAAAAAdYCAQAAAAHYAgEAAAAB2QIBAAAAAdoCgAAAAAHbAkAAAAAB3AJAAAAAAQUHAAD3BQAg1QIBAAAAAdsCQAAAAAHyAgEAAAABhAMAAACEAwICAAAAMgAgNgAA1ggAIAMAAAAyACA2AADWCAAgNwAA1QgAIAEvAACoCQAwAgAAADIAIC8AANUIACACAAAAnQcAIC8AANQIACAE1QIBAKEFACHbAkAAogUAIfICAQChBQAhhAMAAPQFhAMiBQcAAPUFACDVAgEAoQUAIdsCQACiBQAh8gIBAKEFACGEAwAA9AWEAyIFBwAA9wUAINUCAQAAAAHbAkAAAAAB8gIBAAAAAYQDAAAAhAMCBAcAAP8FACDbAkAAAAAB8gIBAAAAAYYDAAAAhgMCAgAAABMAIDYAAN8IACADAAAAEwAgNgAA3wgAIDcAAN4IACABLwAApwkAMAIAAAATACAvAADeCAAgAgAAAKkHACAvAADdCAAgA9sCQACiBQAh8gIBAKEFACGGAwAA_AWGAyIEBwAA_QUAINsCQACiBQAh8gIBAKEFACGGAwAA_AWGAyIEBwAA_wUAINsCQAAAAAHyAgEAAAABhgMAAACGAwIG1QIBAAAAAdsCQAAAAAH2AkAAAAABqgMBAAAAAasDAQAAAAGsA0AAAAABAgAAAA0AIDYAAOsIACADAAAADQAgNgAA6wgAIDcAAOoIACABLwAApgkAMAsDAADsBAAg0gIAAJoFADDTAgAACwAQ1AIAAJoFADDVAgEAAAAB2wJAAOMEACHvAgEAwgQAIfYCQADjBAAhqgMBAMAEACGrAwEAwAQAIawDQADDBAAhAgAAAA0AIC8AAOoIACACAAAA6AgAIC8AAOkIACAK0gIAAOcIADDTAgAA6AgAENQCAADnCAAw1QIBAMAEACHbAkAA4wQAIe8CAQDCBAAh9gJAAOMEACGqAwEAwAQAIasDAQDABAAhrANAAMMEACEK0gIAAOcIADDTAgAA6AgAENQCAADnCAAw1QIBAMAEACHbAkAA4wQAIe8CAQDCBAAh9gJAAOMEACGqAwEAwAQAIasDAQDABAAhrANAAMMEACEG1QIBAKEFACHbAkAAowUAIfYCQACjBQAhqgMBAKEFACGrAwEAoQUAIawDQACiBQAhBtUCAQChBQAh2wJAAKMFACH2AkAAowUAIaoDAQChBQAhqwMBAKEFACGsA0AAogUAIQbVAgEAAAAB2wJAAAAAAfYCQAAAAAGqAwEAAAABqwMBAAAAAawDQAAAAAEI1QIBAAAAAdsCQAAAAAH2AkAAAAAB_gIBAAAAAawDQAAAAAGtAwEAAAABrgMBAAAAAa8DAQAAAAECAAAACQAgNgAA9wgAIAMAAAAJACA2AAD3CAAgNwAA9ggAIAEvAAClCQAwDQMAAOQEACDSAgAAmwUAMNMCAAAHABDUAgAAmwUAMNUCAQAAAAHbAkAAwwQAIe8CAQDABAAh9gJAAMMEACH-AgEAwgQAIawDQADDBAAhrQMBAAAAAa4DAQDCBAAhrwMBAMIEACECAAAACQAgLwAA9ggAIAIAAAD0CAAgLwAA9QgAIAzSAgAA8wgAMNMCAAD0CAAQ1AIAAPMIADDVAgEAwAQAIdsCQADDBAAh7wIBAMAEACH2AkAAwwQAIf4CAQDCBAAhrANAAMMEACGtAwEAwAQAIa4DAQDCBAAhrwMBAMIEACEM0gIAAPMIADDTAgAA9AgAENQCAADzCAAw1QIBAMAEACHbAkAAwwQAIe8CAQDABAAh9gJAAMMEACH-AgEAwgQAIawDQADDBAAhrQMBAMAEACGuAwEAwgQAIa8DAQDCBAAhCNUCAQChBQAh2wJAAKIFACH2AkAAogUAIf4CAQCzBQAhrANAAKIFACGtAwEAoQUAIa4DAQCzBQAhrwMBALMFACEI1QIBAKEFACHbAkAAogUAIfYCQACiBQAh_gIBALMFACGsA0AAogUAIa0DAQChBQAhrgMBALMFACGvAwEAswUAIQjVAgEAAAAB2wJAAAAAAfYCQAAAAAH-AgEAAAABrANAAAAAAa0DAQAAAAGuAwEAAAABrwMBAAAAAQzVAgEAAAAB2wJAAAAAAfYCQAAAAAGwAwEAAAABsQMBAAAAAbIDAQAAAAGzAwEAAAABtANAAAAAAbUDQAAAAAG2AwEAAAABtwMBAAAAAbgDAQAAAAECAAAABQAgNgAAgwkAIAMAAAAFACA2AACDCQAgNwAAggkAIAEvAACkCQAwEQMAAOQEACDSAgAAnAUAMNMCAAADABDUAgAAnAUAMNUCAQAAAAHbAkAAwwQAIe8CAQDABAAh9gJAAMMEACGwAwEAwAQAIbEDAQDABAAhsgMBAMIEACGzAwEAwgQAIbQDQADjBAAhtQNAAOMEACG2AwEAwgQAIbcDAQDCBAAhuAMBAMIEACECAAAABQAgLwAAggkAIAIAAACACQAgLwAAgQkAIBDSAgAA_wgAMNMCAACACQAQ1AIAAP8IADDVAgEAwAQAIdsCQADDBAAh7wIBAMAEACH2AkAAwwQAIbADAQDABAAhsQMBAMAEACGyAwEAwgQAIbMDAQDCBAAhtANAAOMEACG1A0AA4wQAIbYDAQDCBAAhtwMBAMIEACG4AwEAwgQAIRDSAgAA_wgAMNMCAACACQAQ1AIAAP8IADDVAgEAwAQAIdsCQADDBAAh7wIBAMAEACH2AkAAwwQAIbADAQDABAAhsQMBAMAEACGyAwEAwgQAIbMDAQDCBAAhtANAAOMEACG1A0AA4wQAIbYDAQDCBAAhtwMBAMIEACG4AwEAwgQAIQzVAgEAoQUAIdsCQACiBQAh9gJAAKIFACGwAwEAoQUAIbEDAQChBQAhsgMBALMFACGzAwEAswUAIbQDQACjBQAhtQNAAKMFACG2AwEAswUAIbcDAQCzBQAhuAMBALMFACEM1QIBAKEFACHbAkAAogUAIfYCQACiBQAhsAMBAKEFACGxAwEAoQUAIbIDAQCzBQAhswMBALMFACG0A0AAowUAIbUDQACjBQAhtgMBALMFACG3AwEAswUAIbgDAQCzBQAhDNUCAQAAAAHbAkAAAAAB9gJAAAAAAbADAQAAAAGxAwEAAAABsgMBAAAAAbMDAQAAAAG0A0AAAAABtQNAAAAAAbYDAQAAAAG3AwEAAAABuAMBAAAAAQQ2AAD4CAAw0gMAAPkIADDUAwAA-wgAINgDAAD8CAAwBDYAAOwIADDSAwAA7QgAMNQDAADvCAAg2AMAAPAIADAENgAA4AgAMNIDAADhCAAw1AMAAOMIACDYAwAA5AgAMAQ2AADXCAAw0gMAANgIADDUAwAA2ggAINgDAAClBwAwBDYAAM4IADDSAwAAzwgAMNQDAADRCAAg2AMAAJkHADAENgAAwggAMNIDAADDCAAw1AMAAMUIACDYAwAAxggAMAQ2AAC5CAAw0gMAALoIADDUAwAAvAgAINgDAACEBwAwBDYAALAIADDSAwAAsQgAMNQDAACzCAAg2AMAAPgGADAENgAApwgAMNIDAACoCAAw1AMAAKoIACDYAwAA7AYAMAQ2AACeCAAw0gMAAJ8IADDUAwAAoQgAINgDAADKBQAwBDYAAJUIADDSAwAAlggAMNQDAACYCAAg2AMAAL4FADAENgAAiQgAMNIDAACKCAAw1AMAAIwIACDYAwAAjQgAMAAAAAAAAAAAAAAAABsEAACQCQAgBQAAkQkAIAYAAJIJACAgAACZCQAgIQAAkwkAICIAAJQJACAkAACVCQAgJQAAlgkAICYAAJcJACAnAACYCQAgKAAAmgkAICkAAJsJACDwAgAAnQUAIPgCAACdBQAghgMAAJ0FACCHAwAAnQUAILkDAACdBQAguwMAAJ0FACDBAwAAnQUAIMIDAACdBQAgwwMAAJ0FACDEAwAAnQUAIMUDAACdBQAgxgMAAJ0FACDHAwAAnQUAIMgDAACdBQAgyQMAAJ0FACAMAwAAnAkAIAcAAJ4JACAaAACaCQAgHQAAnQkAIB4AAJkJACDvAgAAnQUAIPACAACdBQAg8QIAAJ0FACDzAgAAnQUAIPQCAACdBQAg9wIAAJ0FACD4AgAAnQUAIBERAAChCQAgEgAAngkAIBMAAJ8JACAUAAChCQAgFQAAowkAIBYAAJMJACAYAACUCQAgGQAAoAkAIBoAAJYJACAbAACXCQAgHAAAmAkAICAAAJkJACDwAgAAnQUAIPgCAACdBQAgmAMAAJ0FACCZAwAAnQUAIJoDAACdBQAgAAALBwAAngkAIAgAAJ4JACAJAACfCQAgDgAAlgYAIBAAAKAJACCRAwAAnQUAIJIDAACdBQAgkwMAAJ0FACCVAwAAnQUAIJYDAACdBQAglwMAAJ0FACADCwAAlgYAIPACAACdBQAgiAMAAJ0FACAADNUCAQAAAAHbAkAAAAAB9gJAAAAAAbADAQAAAAGxAwEAAAABsgMBAAAAAbMDAQAAAAG0A0AAAAABtQNAAAAAAbYDAQAAAAG3AwEAAAABuAMBAAAAAQjVAgEAAAAB2wJAAAAAAfYCQAAAAAH-AgEAAAABrANAAAAAAa0DAQAAAAGuAwEAAAABrwMBAAAAAQbVAgEAAAAB2wJAAAAAAfYCQAAAAAGqAwEAAAABqwMBAAAAAawDQAAAAAED2wJAAAAAAfICAQAAAAGGAwAAAIYDAgTVAgEAAAAB2wJAAAAAAfICAQAAAAGEAwAAAIQDAgfVAgEAAAAB1gIBAAAAAdgCAQAAAAHZAgEAAAAB2gKAAAAAAdsCQAAAAAHcAkAAAAABAtsCQAAAAAHyAgEAAAABC9UCAQAAAAHbAkAAAAAB8gIBAAAAAfMCAgAAAAH7AgAAAPsCAvwCAQAAAAH9AgEAAAAB_gIBAAAAAf8CAQAAAAGAA4AAAAABgQMBAAAAAQYvgAAAAAHVAgEAAAAB2wJAAAAAAfICAQAAAAH2AkAAAAAB-QICAAAAAQvVAgEAAAAB2wJAAAAAAfACAgAAAAHxAgEAAAAB8gIBAAAAAfMCAgAAAAH0AgEAAAAB9QICAAAAAfYCQAAAAAH3AkAAAAAB-AJAAAAAAQLbAkAAAAAB7gIBAAAAAQrVAgEAAAAB2wJAAAAAAYcDAQAAAAGjAwEAAAABpAMBAAAAAaUDAgAAAAGmAwEAAAABpwMgAAAAAagDAQAAAAGpAwEAAAABIQUAAIUJACAGAACGCQAgIAAAjQkAICEAAIcJACAiAACICQAgJAAAiQkAICUAAIoJACAmAACLCQAgJwAAjAkAICgAAI4JACApAACPCQAg1QIBAAAAAdsCQAAAAAHwAgIAAAAB9gJAAAAAAfgCQAAAAAGGAwEAAAABhwMBAAAAAbkDAQAAAAG6AyAAAAABuwMBAAAAAb0DAAAAvQMCvwMAAAC_AwLAAyAAAAABwQMBAAAAAcIDAQAAAAHDA4AAAAABxANAAAAAAcUDAQAAAAHGAyAAAAABxwMBAAAAAcgDQAAAAAHJA0AAAAABAgAAAAEAIDYAALAJACADAAAADwAgNgAAsAkAIDcAALQJACAjAAAADwAgBQAA_gcAIAYAAP8HACAgAACGCAAgIQAAgAgAICIAAIEIACAkAACCCAAgJQAAgwgAICYAAIQIACAnAACFCAAgKAAAhwgAICkAAIgIACAvAAC0CQAg1QIBAKEFACHbAkAAogUAIfACAgCyBQAh9gJAAKIFACH4AkAAowUAIYYDAQCzBQAhhwMBALMFACG5AwEAswUAIboDIADSBgAhuwMBALMFACG9AwAA-ge9AyK_AwAA-we_AyLAAyAA0gYAIcEDAQCzBQAhwgMBALMFACHDA4AAAAABxANAAKMFACHFAwEAswUAIcYDIAD8BwAhxwMBALMFACHIA0AAowUAIckDQACjBQAhIQUAAP4HACAGAAD_BwAgIAAAhggAICEAAIAIACAiAACBCAAgJAAAgggAICUAAIMIACAmAACECAAgJwAAhQgAICgAAIcIACApAACICAAg1QIBAKEFACHbAkAAogUAIfACAgCyBQAh9gJAAKIFACH4AkAAowUAIYYDAQCzBQAhhwMBALMFACG5AwEAswUAIboDIADSBgAhuwMBALMFACG9AwAA-ge9AyK_AwAA-we_AyLAAyAA0gYAIcEDAQCzBQAhwgMBALMFACHDA4AAAAABxANAAKMFACHFAwEAswUAIcYDIAD8BwAhxwMBALMFACHIA0AAowUAIckDQACjBQAhIQQAAIQJACAGAACGCQAgIAAAjQkAICEAAIcJACAiAACICQAgJAAAiQkAICUAAIoJACAmAACLCQAgJwAAjAkAICgAAI4JACApAACPCQAg1QIBAAAAAdsCQAAAAAHwAgIAAAAB9gJAAAAAAfgCQAAAAAGGAwEAAAABhwMBAAAAAbkDAQAAAAG6AyAAAAABuwMBAAAAAb0DAAAAvQMCvwMAAAC_AwLAAyAAAAABwQMBAAAAAcIDAQAAAAHDA4AAAAABxANAAAAAAcUDAQAAAAHGAyAAAAABxwMBAAAAAcgDQAAAAAHJA0AAAAABAgAAAAEAIDYAALUJACADAAAADwAgNgAAtQkAIDcAALkJACAjAAAADwAgBAAA_QcAIAYAAP8HACAgAACGCAAgIQAAgAgAICIAAIEIACAkAACCCAAgJQAAgwgAICYAAIQIACAnAACFCAAgKAAAhwgAICkAAIgIACAvAAC5CQAg1QIBAKEFACHbAkAAogUAIfACAgCyBQAh9gJAAKIFACH4AkAAowUAIYYDAQCzBQAhhwMBALMFACG5AwEAswUAIboDIADSBgAhuwMBALMFACG9AwAA-ge9AyK_AwAA-we_AyLAAyAA0gYAIcEDAQCzBQAhwgMBALMFACHDA4AAAAABxANAAKMFACHFAwEAswUAIcYDIAD8BwAhxwMBALMFACHIA0AAowUAIckDQACjBQAhIQQAAP0HACAGAAD_BwAgIAAAhggAICEAAIAIACAiAACBCAAgJAAAgggAICUAAIMIACAmAACECAAgJwAAhQgAICgAAIcIACApAACICAAg1QIBAKEFACHbAkAAogUAIfACAgCyBQAh9gJAAKIFACH4AkAAowUAIYYDAQCzBQAhhwMBALMFACG5AwEAswUAIboDIADSBgAhuwMBALMFACG9AwAA-ge9AyK_AwAA-we_AyLAAyAA0gYAIcEDAQCzBQAhwgMBALMFACHDA4AAAAABxANAAKMFACHFAwEAswUAIcYDIAD8BwAhxwMBALMFACHIA0AAowUAIckDQACjBQAhIQQAAIQJACAFAACFCQAgIAAAjQkAICEAAIcJACAiAACICQAgJAAAiQkAICUAAIoJACAmAACLCQAgJwAAjAkAICgAAI4JACApAACPCQAg1QIBAAAAAdsCQAAAAAHwAgIAAAAB9gJAAAAAAfgCQAAAAAGGAwEAAAABhwMBAAAAAbkDAQAAAAG6AyAAAAABuwMBAAAAAb0DAAAAvQMCvwMAAAC_AwLAAyAAAAABwQMBAAAAAcIDAQAAAAHDA4AAAAABxANAAAAAAcUDAQAAAAHGAyAAAAABxwMBAAAAAcgDQAAAAAHJA0AAAAABAgAAAAEAIDYAALoJACADAAAADwAgNgAAugkAIDcAAL4JACAjAAAADwAgBAAA_QcAIAUAAP4HACAgAACGCAAgIQAAgAgAICIAAIEIACAkAACCCAAgJQAAgwgAICYAAIQIACAnAACFCAAgKAAAhwgAICkAAIgIACAvAAC-CQAg1QIBAKEFACHbAkAAogUAIfACAgCyBQAh9gJAAKIFACH4AkAAowUAIYYDAQCzBQAhhwMBALMFACG5AwEAswUAIboDIADSBgAhuwMBALMFACG9AwAA-ge9AyK_AwAA-we_AyLAAyAA0gYAIcEDAQCzBQAhwgMBALMFACHDA4AAAAABxANAAKMFACHFAwEAswUAIcYDIAD8BwAhxwMBALMFACHIA0AAowUAIckDQACjBQAhIQQAAP0HACAFAAD-BwAgIAAAhggAICEAAIAIACAiAACBCAAgJAAAgggAICUAAIMIACAmAACECAAgJwAAhQgAICgAAIcIACApAACICAAg1QIBAKEFACHbAkAAogUAIfACAgCyBQAh9gJAAKIFACH4AkAAowUAIYYDAQCzBQAhhwMBALMFACG5AwEAswUAIboDIADSBgAhuwMBALMFACG9AwAA-ge9AyK_AwAA-we_AyLAAyAA0gYAIcEDAQCzBQAhwgMBALMFACHDA4AAAAABxANAAKMFACHFAwEAswUAIcYDIAD8BwAhxwMBALMFACHIA0AAowUAIckDQACjBQAhIQQAAIQJACAFAACFCQAgBgAAhgkAICAAAI0JACAhAACHCQAgIgAAiAkAICQAAIkJACAlAACKCQAgJgAAiwkAICcAAIwJACAoAACOCQAg1QIBAAAAAdsCQAAAAAHwAgIAAAAB9gJAAAAAAfgCQAAAAAGGAwEAAAABhwMBAAAAAbkDAQAAAAG6AyAAAAABuwMBAAAAAb0DAAAAvQMCvwMAAAC_AwLAAyAAAAABwQMBAAAAAcIDAQAAAAHDA4AAAAABxANAAAAAAcUDAQAAAAHGAyAAAAABxwMBAAAAAcgDQAAAAAHJA0AAAAABAgAAAAEAIDYAAL8JACADAAAADwAgNgAAvwkAIDcAAMMJACAjAAAADwAgBAAA_QcAIAUAAP4HACAGAAD_BwAgIAAAhggAICEAAIAIACAiAACBCAAgJAAAgggAICUAAIMIACAmAACECAAgJwAAhQgAICgAAIcIACAvAADDCQAg1QIBAKEFACHbAkAAogUAIfACAgCyBQAh9gJAAKIFACH4AkAAowUAIYYDAQCzBQAhhwMBALMFACG5AwEAswUAIboDIADSBgAhuwMBALMFACG9AwAA-ge9AyK_AwAA-we_AyLAAyAA0gYAIcEDAQCzBQAhwgMBALMFACHDA4AAAAABxANAAKMFACHFAwEAswUAIcYDIAD8BwAhxwMBALMFACHIA0AAowUAIckDQACjBQAhIQQAAP0HACAFAAD-BwAgBgAA_wcAICAAAIYIACAhAACACAAgIgAAgQgAICQAAIIIACAlAACDCAAgJgAAhAgAICcAAIUIACAoAACHCAAg1QIBAKEFACHbAkAAogUAIfACAgCyBQAh9gJAAKIFACH4AkAAowUAIYYDAQCzBQAhhwMBALMFACG5AwEAswUAIboDIADSBgAhuwMBALMFACG9AwAA-ge9AyK_AwAA-we_AyLAAyAA0gYAIcEDAQCzBQAhwgMBALMFACHDA4AAAAABxANAAKMFACHFAwEAswUAIcYDIAD8BwAhxwMBALMFACHIA0AAowUAIckDQACjBQAhGhEAAM4HACASAAC_BwAgEwAAwAcAIBQAAMEHACAWAADDBwAgGAAAxAcAIBkAAMUHACAaAADGBwAgGwAAxwcAIBwAAMgHACAgAADJBwAg1QIBAAAAAdsCQAAAAAHwAgIAAAAB9gJAAAAAAfgCQAAAAAGYAwIAAAABmQMBAAAAAZoDAgAAAAGcAwAAAJwDAp0DIAAAAAGeAyAAAAABnwMCAAAAAaADAgAAAAGhAwIAAAABogMCAAAAAQIAAAAaACA2AADECQAgDwcAANkHACAIAAC5BwAgDgAAuwcAIBAAALwHACDbAkAAAAAB8gIBAAAAAfMCAgAAAAGQAwEAAAABkQMBAAAAAZIDAQAAAAGTAwEAAAABlAMBAAAAAZUDAQAAAAGWAwEAAAABlwNAAAAAAQIAAAAtACA2AADGCQAgAwAAABUAIDYAAMYJACA3AADKCQAgEQAAABUAIAcAAK0GACAIAACuBgAgDgAAsAYAIBAAALEGACAvAADKCQAg2wJAAKIFACHyAgEAoQUAIfMCAgC0BQAhkAMBAKEFACGRAwEAswUAIZIDAQCzBQAhkwMBALMFACGUAwEAoQUAIZUDAQCzBQAhlgMBALMFACGXA0AAowUAIQ8HAACtBgAgCAAArgYAIA4AALAGACAQAACxBgAg2wJAAKIFACHyAgEAoQUAIfMCAgC0BQAhkAMBAKEFACGRAwEAswUAIZIDAQCzBQAhkwMBALMFACGUAwEAoQUAIZUDAQCzBQAhlgMBALMFACGXA0AAowUAIQ8HAADZBwAgCQAAugcAIA4AALsHACAQAAC8BwAg2wJAAAAAAfICAQAAAAHzAgIAAAABkAMBAAAAAZEDAQAAAAGSAwEAAAABkwMBAAAAAZQDAQAAAAGVAwEAAAABlgMBAAAAAZcDQAAAAAECAAAALQAgNgAAywkAIA7VAgEAAAAB2wJAAAAAAfACAgAAAAH2AkAAAAAB-AJAAAAAAZgDAgAAAAGaAwIAAAABnAMAAACcAwKdAyAAAAABngMgAAAAAZ8DAgAAAAGgAwIAAAABoQMCAAAAAaIDAgAAAAEaEQAAzgcAIBIAAL8HACAUAADBBwAgFQAAwgcAIBYAAMMHACAYAADEBwAgGQAAxQcAIBoAAMYHACAbAADHBwAgHAAAyAcAICAAAMkHACDVAgEAAAAB2wJAAAAAAfACAgAAAAH2AkAAAAAB-AJAAAAAAZgDAgAAAAGZAwEAAAABmgMCAAAAAZwDAAAAnAMCnQMgAAAAAZ4DIAAAAAGfAwIAAAABoAMCAAAAAaEDAgAAAAGiAwIAAAABAgAAABoAIDYAAM4JACAK2wJAAAAAAfMCAgAAAAGQAwEAAAABkQMBAAAAAZIDAQAAAAGTAwEAAAABlAMBAAAAAZUDAQAAAAGWAwEAAAABlwNAAAAAAQPbAkAAAAAB7wIBAAAAAYYDAAAAhgMCBNUCAQAAAAHbAkAAAAABggMBAAAAAYQDAAAAhAMCBdUCAQAAAAHbAkAAAAAB-wIAAACPAwKMAwIAAAABjQMBAAAAAQLbAkAAAAAB7wIBAAAAAQvVAgEAAAAB2wJAAAAAAe8CAQAAAAHzAgIAAAAB-wIAAAD7AgL8AgEAAAAB_QIBAAAAAf4CAQAAAAH_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_BwAgEwAAwAcAIBQAAMEHACAVAADCBwAgGAAAxAcAIBkAAMUHACAaAADGBwAgGwAAxwcAIBwAAMgHACAgAADJBwAg1QIBAAAAAdsCQAAAAAHwAgIAAAAB9gJAAAAAAfgCQAAAAAGYAwIAAAABmQMBAAAAAZoDAgAAAAGcAwAAAJwDAp0DIAAAAAGeAyAAAAABnwMCAAAAAaADAgAAAAGhAwIAAAABogMCAAAAAQIAAAAaACA2AAD7CQAgAwAAAA8AIDYAAPkJACA3AAD_CQAgIwAAAA8AIAQAAP0HACAFAAD-BwAgBgAA_wcAICAAAIYIACAiAACBCAAgJAAAgggAICUAAIMIACAmAACECAAgJwAAhQgAICgAAIcIACApAACICAAgLwAA_wkAINUCAQChBQAh2wJAAKIFACHwAgIAsgUAIfYCQACiBQAh-AJAAKMFACGGAwEAswUAIYcDAQCzBQAhuQMBALMFACG6AyAA0gYAIbsDAQCzBQAhvQMAAPoHvQMivwMAAPsHvwMiwAMgANIGACHBAwEAswUAIcIDAQCzBQAhwwOAAAAAAcQDQACjBQAhxQMBALMFACHGAyAA_AcAIccDAQCzBQAhyANAAKMFACHJA0AAowUAISEEAAD9BwAgBQAA_gcAIAYAAP8HACAgAACGCAAgIgAAgQgAICQAAIIIACAlAACDCAAgJgAAhAgAICcAAIUIACAoAACHCAAgKQAAiAgAINUCAQChBQAh2wJAAKIFACHwAgIAsgUAIfYCQACiBQAh-AJAAKMFACGGAwEAswUAIYcDAQCzBQAhuQMBALMFACG6AyAA0gYAIbsDAQCzBQAhvQMAAPoHvQMivwMAAPsHvwMiwAMgANIGACHBAwEAswUAIcIDAQCzBQAhwwOAAAAAAcQDQACjBQAhxQMBALMFACHGAyAA_AcAIccDAQCzBQAhyANAAKMFACHJA0AAowUAIQMAAAAXACA2AAD7CQAgNwAAggoAIBwAAAAXACARAADUBgAgEgAA1QYAIBMAANYGACAUAADUBwAgFQAA1wYAIBgAANkGACAZAADaBgAgGgAA2wYAIBsAANwGACAcAADdBgAgIAAA3gYAIC8AAIIKACDVAgEAoQUAIdsCQACiBQAh8AICALIFACH2AkAAogUAIfgCQACjBQAhmAMCALIFACGZAwEAswUAIZoDAgCyBQAhnAMAANEGnAMinQMgANIGACGeAyAA0gYAIZ8DAgC0BQAhoAMCALQFACGhAwIAtAUAIaIDAgC0BQAhGhEAANQGACASAADVBgAgEwAA1gYAIBQAANQHACAVAADXBgAgGAAA2QYAIBkAANoGACAaAADbBgAgGwAA3AYAIBwAAN0GACAgAADeBgAg1QIBAKEFACHbAkAAogUAIfACAgCyBQAh9gJAAKIFACH4AkAAowUAIZgDAgCyBQAhmQMBALMFACGaAwIAsgUAIZwDAADRBpwDIp0DIADSBgAhngMgANIGACGfAwIAtAUAIaADAgC0BQAhoQMCALQFACGiAwIAtAUAISEEAACECQAgBQAAhQkAIAYAAIYJACAgAACNCQAgIQAAhwkAICQAAIkJACAlAACKCQAgJgAAiwkAICcAAIwJACAoAACOCQAgKQAAjwkAINUCAQAAAAHbAkAAAAAB8AICAAAAAfYCQAAAAAH4AkAAAAABhgMBAAAAAYcDAQAAAAG5AwEAAAABugMgAAAAAbsDAQAAAAG9AwAAAL0DAr8DAAAAvwMCwAMgAAAAAcEDAQAAAAHCAwEAAAABwwOAAAAAAcQDQAAAAAHFAwEAAAABxgMgAAAAAccDAQAAAAHIA0AAAAAByQNAAAAAAQIAAAABACA2AACDCgAgGhEAAM4HACASAAC_BwAgEwAAwAcAIBQAAMEHACAVAADCBwAgFgAAwwcAIBkAAMUHACAaAADGBwAgGwAAxwcAIBwAAMgHACAgAADJBwAg1QIBAAAAAdsCQAAAAAHwAgIAAAAB9gJAAAAAAfgCQAAAAAGYAwIAAAABmQMBAAAAAZoDAgAAAAGcAwAAAJwDAp0DIAAAAAGeAyAAAAABnwMCAAAAAaADAgAAAAGhAwIAAAABogMCAAAAAQIAAAAaACA2AACFCgAgAwAAAA8AIDYAAIMKACA3AACJCgAgIwAAAA8AIAQAAP0HACAFAAD-BwAgBgAA_wcAICAAAIYIACAhAACACAAgJAAAgggAICUAAIMIACAmAACECAAgJwAAhQgAICgAAIcIACApAACICAAgLwAAiQoAINUCAQChBQAh2wJAAKIFACHwAgIAsgUAIfYCQACiBQAh-AJAAKMFACGGAwEAswUAIYcDAQCzBQAhuQMBALMFACG6AyAA0gYAIbsDAQCzBQAhvQMAAPoHvQMivwMAAPsHvwMiwAMgANIGACHBAwEAswUAIcIDAQCzBQAhwwOAAAAAAcQDQACjBQAhxQMBALMFACHGAyAA_AcAIccDAQCzBQAhyANAAKMFACHJA0AAowUAISEEAAD9BwAgBQAA_gcAIAYAAP8HACAgAACGCAAgIQAAgAgAICQAAIIIACAlAACDCAAgJgAAhAgAICcAAIUIACAoAACHCAAgKQAAiAgAINUCAQChBQAh2wJAAKIFACHwAgIAsgUAIfYCQACiBQAh-AJAAKMFACGGAwEAswUAIYcDAQCzBQAhuQMBALMFACG6AyAA0gYAIbsDAQCzBQAhvQMAAPoHvQMivwMAAPsHvwMiwAMgANIGACHBAwEAswUAIcIDAQCzBQAhwwOAAAAAAcQDQACjBQAhxQMBALMFACHGAyAA_AcAIccDAQCzBQAhyANAAKMFACHJA0AAowUAIQMAAAAXACA2AACFCgAgNwAAjAoAIBwAAAAXACARAADUBgAgEgAA1QYAIBMAANYGACAUAADUBwAgFQAA1wYAIBYAANgGACAZAADaBgAgGgAA2wYAIBsAANwGACAcAADdBgAgIAAA3gYAIC8AAIwKACDVAgEAoQUAIdsCQACiBQAh8AICALIFACH2AkAAogUAIfgCQACjBQAhmAMCALIFACGZAwEAswUAIZoDAgCyBQAhnAMAANEGnAMinQMgANIGACGeAyAA0gYAIZ8DAgC0BQAhoAMCALQFACGhAwIAtAUAIaIDAgC0BQAhGhEAANQGACASAADVBgAgEwAA1gYAIBQAANQHACAVAADXBgAgFgAA2AYAIBkAANoGACAaAADbBgAgGwAA3AYAIBwAAN0GACAgAADeBgAg1QIBAKEFACHbAkAAogUAIfACAgCyBQAh9gJAAKIFACH4AkAAowUAIZgDAgCyBQAhmQMBALMFACGaAwIAsgUAIZwDAADRBpwDIp0DIADSBgAhngMgANIGACGfAwIAtAUAIaADAgC0BQAhoQMCALQFACGiAwIAtAUAISEEAACECQAgBQAAhQkAIAYAAIYJACAgAACNCQAgIQAAhwkAICIAAIgJACAkAACJCQAgJgAAiwkAICcAAIwJACAoAACOCQAgKQAAjwkAINUCAQAAAAHbAkAAAAAB8AICAAAAAfYCQAAAAAH4AkAAAAABhgMBAAAAAYcDAQAAAAG5AwEAAAABugMgAAAAAbsDAQAAAAG9AwAAAL0DAr8DAAAAvwMCwAMgAAAAAcEDAQAAAAHCAwEAAAABwwOAAAAAAcQDQAAAAAHFAwEAAAABxgMgAAAAAccDAQAAAAHIA0AAAAAByQNAAAAAAQIAAAABACA2AACNCgAgGhEAAM4HACASAAC_BwAgEwAAwAcAIBQAAMEHACAVAADCBwAgFgAAwwcAIBgAAMQHACAZAADFBwAgGwAAxwcAIBwAAMgHACAgAADJBwAg1QIBAAAAAdsCQAAAAAHwAgIAAAAB9gJAAAAAAfgCQAAAAAGYAwIAAAABmQMBAAAAAZoDAgAAAAGcAwAAAJwDAp0DIAAAAAGeAyAAAAABnwMCAAAAAaADAgAAAAGhAwIAAAABogMCAAAAAQIAAAAaACA2AACPCgAgAwAAAA8AIDYAAI0KACA3AACTCgAgIwAAAA8AIAQAAP0HACAFAAD-BwAgBgAA_wcAICAAAIYIACAhAACACAAgIgAAgQgAICQAAIIIACAmAACECAAgJwAAhQgAICgAAIcIACApAACICAAgLwAAkwoAINUCAQChBQAh2wJAAKIFACHwAgIAsgUAIfYCQACiBQAh-AJAAKMFACGGAwEAswUAIYcDAQCzBQAhuQMBALMFACG6AyAA0gYAIbsDAQCzBQAhvQMAAPoHvQMivwMAAPsHvwMiwAMgANIGACHBAwEAswUAIcIDAQCzBQAhwwOAAAAAAcQDQACjBQAhxQMBALMFACHGAyAA_AcAIccDAQCzBQAhyANAAKMFACHJA0AAowUAISEEAAD9BwAgBQAA_gcAIAYAAP8HACAgAACGCAAgIQAAgAgAICIAAIEIACAkAACCCAAgJgAAhAgAICcAAIUIACAoAACHCAAgKQAAiAgAINUCAQChBQAh2wJAAKIFACHwAgIAsgUAIfYCQACiBQAh-AJAAKMFACGGAwEAswUAIYcDAQCzBQAhuQMBALMFACG6AyAA0gYAIbsDAQCzBQAhvQMAAPoHvQMivwMAAPsHvwMiwAMgANIGACHBAwEAswUAIcIDAQCzBQAhwwOAAAAAAcQDQACjBQAhxQMBALMFACHGAyAA_AcAIccDAQCzBQAhyANAAKMFACHJA0AAowUAIQMAAAAXACA2AACPCgAgNwAAlgoAIBwAAAAXACARAADUBgAgEgAA1QYAIBMAANYGACAUAADUBwAgFQAA1wYAIBYAANgGACAYAADZBgAgGQAA2gYAIBsAANwGACAcAADdBgAgIAAA3gYAIC8AAJYKACDVAgEAoQUAIdsCQACiBQAh8AICALIFACH2AkAAogUAIfgCQACjBQAhmAMCALIFACGZAwEAswUAIZoDAgCyBQAhnAMAANEGnAMinQMgANIGACGeAyAA0gYAIZ8DAgC0BQAhoAMCALQFACGhAwIAtAUAIaIDAgC0BQAhGhEAANQGACASAADVBgAgEwAA1gYAIBQAANQHACAVAADXBgAgFgAA2AYAIBgAANkGACAZAADaBgAgGwAA3AYAIBwAAN0GACAgAADeBgAg1QIBAKEFACHbAkAAogUAIfACAgCyBQAh9gJAAKIFACH4AkAAowUAIZgDAgCyBQAhmQMBALMFACGaAwIAsgUAIZwDAADRBpwDIp0DIADSBgAhngMgANIGACGfAwIAtAUAIaADAgC0BQAhoQMCALQFACGiAwIAtAUAISEEAACECQAgBQAAhQkAIAYAAIYJACAgAACNCQAgIQAAhwkAICIAAIgJACAkAACJCQAgJQAAigkAICcAAIwJACAoAACOCQAgKQAAjwkAINUCAQAAAAHbAkAAAAAB8AICAAAAAfYCQAAAAAH4AkAAAAABhgMBAAAAAYcDAQAAAAG5AwEAAAABugMgAAAAAbsDAQAAAAG9AwAAAL0DAr8DAAAAvwMCwAMgAAAAAcEDAQAAAAHCAwEAAAABwwOAAAAAAcQDQAAAAAHFAwEAAAABxgMgAAAAAccDAQAAAAHIA0AAAAAByQNAAAAAAQIAAAABACA2AACXCgAgGhEAAM4HACASAAC_BwAgEwAAwAcAIBQAAMEHACAVAADCBwAgFgAAwwcAIBgAAMQHACAZAADFBwAgGgAAxgcAIBwAAMgHACAgAADJBwAg1QIBAAAAAdsCQAAAAAHwAgIAAAAB9gJAAAAAAfgCQAAAAAGYAwIAAAABmQMBAAAAAZoDAgAAAAGcAwAAAJwDAp0DIAAAAAGeAyAAAAABnwMCAAAAAaADAgAAAAGhAwIAAAABogMCAAAAAQIAAAAaACA2AACZCgAgAwAAAA8AIDYAAJcKACA3AACdCgAgIwAAAA8AIAQAAP0HACAFAAD-BwAgBgAA_wcAICAAAIYIACAhAACACAAgIgAAgQgAICQAAIIIACAlAACDCAAgJwAAhQgAICgAAIcIACApAACICAAgLwAAnQoAINUCAQChBQAh2wJAAKIFACHwAgIAsgUAIfYCQACiBQAh-AJAAKMFACGGAwEAswUAIYcDAQCzBQAhuQMBALMFACG6AyAA0gYAIbsDAQCzBQAhvQMAAPoHvQMivwMAAPsHvwMiwAMgANIGACHBAwEAswUAIcIDAQCzBQAhwwOAAAAAAcQDQACjBQAhxQMBALMFACHGAyAA_AcAIccDAQCzBQAhyANAAKMFACHJA0AAowUAISEEAAD9BwAgBQAA_gcAIAYAAP8HACAgAACGCAAgIQAAgAgAICIAAIEIACAkAACCCAAgJQAAgwgAICcAAIUIACAoAACHCAAgKQAAiAgAINUCAQChBQAh2wJAAKIFACHwAgIAsgUAIfYCQACiBQAh-AJAAKMFACGGAwEAswUAIYcDAQCzBQAhuQMBALMFACG6AyAA0gYAIbsDAQCzBQAhvQMAAPoHvQMivwMAAPsHvwMiwAMgANIGACHBAwEAswUAIcIDAQCzBQAhwwOAAAAAAcQDQACjBQAhxQMBALMFACHGAyAA_AcAIccDAQCzBQAhyANAAKMFACHJA0AAowUAIQMAAAAXACA2AACZCgAgNwAAoAoAIBwAAAAXACARAADUBgAgEgAA1QYAIBMAANYGACAUAADUBwAgFQAA1wYAIBYAANgGACAYAADZBgAgGQAA2gYAIBoAANsGACAcAADdBgAgIAAA3gYAIC8AAKAKACDVAgEAoQUAIdsCQACiBQAh8AICALIFACH2AkAAogUAIfgCQACjBQAhmAMCALIFACGZAwEAswUAIZoDAgCyBQAhnAMAANEGnAMinQMgANIGACGeAyAA0gYAIZ8DAgC0BQAhoAMCALQFACGhAwIAtAUAIaIDAgC0BQAhGhEAANQGACASAADVBgAgEwAA1gYAIBQAANQHACAVAADXBgAgFgAA2AYAIBgAANkGACAZAADaBgAgGgAA2wYAIBwAAN0GACAgAADeBgAg1QIBAKEFACHbAkAAogUAIfACAgCyBQAh9gJAAKIFACH4AkAAowUAIZgDAgCyBQAhmQMBALMFACGaAwIAsgUAIZwDAADRBpwDIp0DIADSBgAhngMgANIGACGfAwIAtAUAIaADAgC0BQAhoQMCALQFACGiAwIAtAUAIRoRAADOBwAgEgAAvwcAIBMAAMAHACAUAADBBwAgFQAAwgcAIBYAAMMHACAYAADEBwAgGQAAxQcAIBoAAMYHACAbAADHBwAgIAAAyQcAINUCAQAAAAHbAkAAAAAB8AICAAAAAfYCQAAAAAH4AkAAAAABmAMCAAAAAZkDAQAAAAGaAwIAAAABnAMAAACcAwKdAyAAAAABngMgAAAAAZ8DAgAAAAGgAwIAAAABoQMCAAAAAaIDAgAAAAECAAAAGgAgNgAAoQoAICEEAACECQAgBQAAhQkAIAYAAIYJACAgAACNCQAgIQAAhwkAICIAAIgJACAkAACJCQAgJQAAigkAICYAAIsJACAoAACOCQAgKQAAjwkAINUCAQAAAAHbAkAAAAAB8AICAAAAAfYCQAAAAAH4AkAAAAABhgMBAAAAAYcDAQAAAAG5AwEAAAABugMgAAAAAbsDAQAAAAG9AwAAAL0DAr8DAAAAvwMCwAMgAAAAAcEDAQAAAAHCAwEAAAABwwOAAAAAAcQDQAAAAAHFAwEAAAABxgMgAAAAAccDAQAAAAHIA0AAAAAByQNAAAAAAQIAAAABACA2AACjCgAgAwAAABcAIDYAAKEKACA3AACnCgAgHAAAABcAIBEAANQGACASAADVBgAgEwAA1gYAIBQAANQHACAVAADXBgAgFgAA2AYAIBgAANkGACAZAADaBgAgGgAA2wYAIBsAANwGACAgAADeBgAgLwAApwoAINUCAQChBQAh2wJAAKIFACHwAgIAsgUAIfYCQACiBQAh-AJAAKMFACGYAwIAsgUAIZkDAQCzBQAhmgMCALIFACGcAwAA0QacAyKdAyAA0gYAIZ4DIADSBgAhnwMCALQFACGgAwIAtAUAIaEDAgC0BQAhogMCALQFACEaEQAA1AYAIBIAANUGACATAADWBgAgFAAA1AcAIBUAANcGACAWAADYBgAgGAAA2QYAIBkAANoGACAaAADbBgAgGwAA3AYAICAAAN4GACDVAgEAoQUAIdsCQACiBQAh8AICALIFACH2AkAAogUAIfgCQACjBQAhmAMCALIFACGZAwEAswUAIZoDAgCyBQAhnAMAANEGnAMinQMgANIGACGeAyAA0gYAIZ8DAgC0BQAhoAMCALQFACGhAwIAtAUAIaIDAgC0BQAhAwAAAA8AIDYAAKMKACA3AACqCgAgIwAAAA8AIAQAAP0HACAFAAD-BwAgBgAA_wcAICAAAIYIACAhAACACAAgIgAAgQgAICQAAIIIACAlAACDCAAgJgAAhAgAICgAAIcIACApAACICAAgLwAAqgoAINUCAQChBQAh2wJAAKIFACHwAgIAsgUAIfYCQACiBQAh-AJAAKMFACGGAwEAswUAIYcDAQCzBQAhuQMBALMFACG6AyAA0gYAIbsDAQCzBQAhvQMAAPoHvQMivwMAAPsHvwMiwAMgANIGACHBAwEAswUAIcIDAQCzBQAhwwOAAAAAAcQDQACjBQAhxQMBALMFACHGAyAA_AcAIccDAQCzBQAhyANAAKMFACHJA0AAowUAISEEAAD9BwAgBQAA_gcAIAYAAP8HACAgAACGCAAgIQAAgAgAICIAAIEIACAkAACCCAAgJQAAgwgAICYAAIQIACAoAACHCAAgKQAAiAgAINUCAQChBQAh2wJAAKIFACHwAgIAsgUAIfYCQACiBQAh-AJAAKMFACGGAwEAswUAIYcDAQCzBQAhuQMBALMFACG6AyAA0gYAIbsDAQCzBQAhvQMAAPoHvQMivwMAAPsHvwMiwAMgANIGACHBAwEAswUAIcIDAQCzBQAhwwOAAAAAAcQDQACjBQAhxQMBALMFACHGAyAA_AcAIccDAQCzBQAhyANAAKMFACHJA0AAowUAIRADAADTBQAgBwAA0gUAIBoAANUFACAdAADWBQAg1QIBAAAAAdsCQAAAAAHvAgEAAAAB8AICAAAAAfECAQAAAAHyAgEAAAAB8wICAAAAAfQCAQAAAAH1AgIAAAAB9gJAAAAAAfcCQAAAAAH4AkAAAAABAgAAAEYAIDYAAKsKACAhBAAAhAkAIAUAAIUJACAGAACGCQAgIQAAhwkAICIAAIgJACAkAACJCQAgJQAAigkAICYAAIsJACAnAACMCQAgKAAAjgkAICkAAI8JACDVAgEAAAAB2wJAAAAAAfACAgAAAAH2AkAAAAAB-AJAAAAAAYYDAQAAAAGHAwEAAAABuQMBAAAAAboDIAAAAAG7AwEAAAABvQMAAAC9AwK_AwAAAL8DAsADIAAAAAHBAwEAAAABwgMBAAAAAcMDgAAAAAHEA0AAAAABxQMBAAAAAcYDIAAAAAHHAwEAAAAByANAAAAAAckDQAAAAAECAAAAAQAgNgAArQoAIBoRAADOBwAgEgAAvwcAIBMAAMAHACAUAADBBwAgFQAAwgcAIBYAAMMHACAYAADEBwAgGQAAxQcAIBoAAMYHACAbAADHBwAgHAAAyAcAINUCAQAAAAHbAkAAAAAB8AICAAAAAfYCQAAAAAH4AkAAAAABmAMCAAAAAZkDAQAAAAGaAwIAAAABnAMAAACcAwKdAyAAAAABngMgAAAAAZ8DAgAAAAGgAwIAAAABoQMCAAAAAaIDAgAAAAECAAAAGgAgNgAArwoAIAvVAgEAAAAB2wJAAAAAAe8CAQAAAAHwAgIAAAAB8gIBAAAAAfMCAgAAAAH0AgEAAAAB9QICAAAAAfYCQAAAAAH3AkAAAAAB-AJAAAAAAQLbAkAAAAAB7wIBAAAAAQMAAABEACA2AACrCgAgNwAAtQoAIBIAAABEACADAAC2BQAgBwAAtQUAIBoAALkFACAdAAC3BQAgLwAAtQoAINUCAQChBQAh2wJAAKIFACHvAgEAswUAIfACAgCyBQAh8QIBALMFACHyAgEAoQUAIfMCAgCyBQAh9AIBALMFACH1AgIAtAUAIfYCQACiBQAh9wJAAKMFACH4AkAAowUAIRADAAC2BQAgBwAAtQUAIBoAALkFACAdAAC3BQAg1QIBAKEFACHbAkAAogUAIe8CAQCzBQAh8AICALIFACHxAgEAswUAIfICAQChBQAh8wICALIFACH0AgEAswUAIfUCAgC0BQAh9gJAAKIFACH3AkAAowUAIfgCQACjBQAhAwAAAA8AIDYAAK0KACA3AAC4CgAgIwAAAA8AIAQAAP0HACAFAAD-BwAgBgAA_wcAICEAAIAIACAiAACBCAAgJAAAgggAICUAAIMIACAmAACECAAgJwAAhQgAICgAAIcIACApAACICAAgLwAAuAoAINUCAQChBQAh2wJAAKIFACHwAgIAsgUAIfYCQACiBQAh-AJAAKMFACGGAwEAswUAIYcDAQCzBQAhuQMBALMFACG6AyAA0gYAIbsDAQCzBQAhvQMAAPoHvQMivwMAAPsHvwMiwAMgANIGACHBAwEAswUAIcIDAQCzBQAhwwOAAAAAAcQDQACjBQAhxQMBALMFACHGAyAA_AcAIccDAQCzBQAhyANAAKMFACHJA0AAowUAISEEAAD9BwAgBQAA_gcAIAYAAP8HACAhAACACAAgIgAAgQgAICQAAIIIACAlAACDCAAgJgAAhAgAICcAAIUIACAoAACHCAAgKQAAiAgAINUCAQChBQAh2wJAAKIFACHwAgIAsgUAIfYCQACiBQAh-AJAAKMFACGGAwEAswUAIYcDAQCzBQAhuQMBALMFACG6AyAA0gYAIbsDAQCzBQAhvQMAAPoHvQMivwMAAPsHvwMiwAMgANIGACHBAwEAswUAIcIDAQCzBQAhwwOAAAAAAcQDQACjBQAhxQMBALMFACHGAyAA_AcAIccDAQCzBQAhyANAAKMFACHJA0AAowUAIQMAAAAXACA2AACvCgAgNwAAuwoAIBwAAAAXACARAADUBgAgEgAA1QYAIBMAANYGACAUAADUBwAgFQAA1wYAIBYAANgGACAYAADZBgAgGQAA2gYAIBoAANsGACAbAADcBgAgHAAA3QYAIC8AALsKACDVAgEAoQUAIdsCQACiBQAh8AICALIFACH2AkAAogUAIfgCQACjBQAhmAMCALIFACGZAwEAswUAIZoDAgCyBQAhnAMAANEGnAMinQMgANIGACGeAyAA0gYAIZ8DAgC0BQAhoAMCALQFACGhAwIAtAUAIaIDAgC0BQAhGhEAANQGACASAADVBgAgEwAA1gYAIBQAANQHACAVAADXBgAgFgAA2AYAIBgAANkGACAZAADaBgAgGgAA2wYAIBsAANwGACAcAADdBgAg1QIBAKEFACHbAkAAogUAIfACAgCyBQAh9gJAAKIFACH4AkAAowUAIZgDAgCyBQAhmQMBALMFACGaAwIAsgUAIZwDAADRBpwDIp0DIADSBgAhngMgANIGACGfAwIAtAUAIaADAgC0BQAhoQMCALQFACGiAwIAtAUAISEEAACECQAgBQAAhQkAIAYAAIYJACAgAACNCQAgIQAAhwkAICIAAIgJACAkAACJCQAgJQAAigkAICYAAIsJACAnAACMCQAgKQAAjwkAINUCAQAAAAHbAkAAAAAB8AICAAAAAfYCQAAAAAH4AkAAAAABhgMBAAAAAYcDAQAAAAG5AwEAAAABugMgAAAAAbsDAQAAAAG9AwAAAL0DAr8DAAAAvwMCwAMgAAAAAcEDAQAAAAHCAwEAAAABwwOAAAAAAcQDQAAAAAHFAwEAAAABxgMgAAAAAccDAQAAAAHIA0AAAAAByQNAAAAAAQIAAAABACA2AAC8CgAgEAMAANMFACAHAADSBQAgHQAA1gUAIB4AANQFACDVAgEAAAAB2wJAAAAAAe8CAQAAAAHwAgIAAAAB8QIBAAAAAfICAQAAAAHzAgIAAAAB9AIBAAAAAfUCAgAAAAH2AkAAAAAB9wJAAAAAAfgCQAAAAAECAAAARgAgNgAAvgoAIAMAAAAPACA2AAC8CgAgNwAAwgoAICMAAAAPACAEAAD9BwAgBQAA_gcAIAYAAP8HACAgAACGCAAgIQAAgAgAICIAAIEIACAkAACCCAAgJQAAgwgAICYAAIQIACAnAACFCAAgKQAAiAgAIC8AAMIKACDVAgEAoQUAIdsCQACiBQAh8AICALIFACH2AkAAogUAIfgCQACjBQAhhgMBALMFACGHAwEAswUAIbkDAQCzBQAhugMgANIGACG7AwEAswUAIb0DAAD6B70DIr8DAAD7B78DIsADIADSBgAhwQMBALMFACHCAwEAswUAIcMDgAAAAAHEA0AAowUAIcUDAQCzBQAhxgMgAPwHACHHAwEAswUAIcgDQACjBQAhyQNAAKMFACEhBAAA_QcAIAUAAP4HACAGAAD_BwAgIAAAhggAICEAAIAIACAiAACBCAAgJAAAgggAICUAAIMIACAmAACECAAgJwAAhQgAICkAAIgIACDVAgEAoQUAIdsCQACiBQAh8AICALIFACH2AkAAogUAIfgCQACjBQAhhgMBALMFACGHAwEAswUAIbkDAQCzBQAhugMgANIGACG7AwEAswUAIb0DAAD6B70DIr8DAAD7B78DIsADIADSBgAhwQMBALMFACHCAwEAswUAIcMDgAAAAAHEA0AAowUAIcUDAQCzBQAhxgMgAPwHACHHAwEAswUAIcgDQACjBQAhyQNAAKMFACEDAAAARAAgNgAAvgoAIDcAAMUKACASAAAARAAgAwAAtgUAIAcAALUFACAdAAC3BQAgHgAAuAUAIC8AAMUKACDVAgEAoQUAIdsCQACiBQAh7wIBALMFACHwAgIAsgUAIfECAQCzBQAh8gIBAKEFACHzAgIAsgUAIfQCAQCzBQAh9QICALQFACH2AkAAogUAIfcCQACjBQAh-AJAAKMFACEQAwAAtgUAIAcAALUFACAdAAC3BQAgHgAAuAUAINUCAQChBQAh2wJAAKIFACHvAgEAswUAIfACAgCyBQAh8QIBALMFACHyAgEAoQUAIfMCAgCyBQAh9AIBALMFACH1AgIAtAUAIfYCQACiBQAh9wJAAKMFACH4AkAAowUAISEEAACECQAgBQAAhQkAIAYAAIYJACAgAACNCQAgIQAAhwkAICIAAIgJACAlAACKCQAgJgAAiwkAICcAAIwJACAoAACOCQAgKQAAjwkAINUCAQAAAAHbAkAAAAAB8AICAAAAAfYCQAAAAAH4AkAAAAABhgMBAAAAAYcDAQAAAAG5AwEAAAABugMgAAAAAbsDAQAAAAG9AwAAAL0DAr8DAAAAvwMCwAMgAAAAAcEDAQAAAAHCAwEAAAABwwOAAAAAAcQDQAAAAAHFAwEAAAABxgMgAAAAAccDAQAAAAHIA0AAAAAByQNAAAAAAQIAAAABACA2AADGCgAgAwAAAA8AIDYAAMYKACA3AADKCgAgIwAAAA8AIAQAAP0HACAFAAD-BwAgBgAA_wcAICAAAIYIACAhAACACAAgIgAAgQgAICUAAIMIACAmAACECAAgJwAAhQgAICgAAIcIACApAACICAAgLwAAygoAINUCAQChBQAh2wJAAKIFACHwAgIAsgUAIfYCQACiBQAh-AJAAKMFACGGAwEAswUAIYcDAQCzBQAhuQMBALMFACG6AyAA0gYAIbsDAQCzBQAhvQMAAPoHvQMivwMAAPsHvwMiwAMgANIGACHBAwEAswUAIcIDAQCzBQAhwwOAAAAAAcQDQACjBQAhxQMBALMFACHGAyAA_AcAIccDAQCzBQAhyANAAKMFACHJA0AAowUAISEEAAD9BwAgBQAA_gcAIAYAAP8HACAgAACGCAAgIQAAgAgAICIAAIEIACAlAACDCAAgJgAAhAgAICcAAIUIACAoAACHCAAgKQAAiAgAINUCAQChBQAh2wJAAKIFACHwAgIAsgUAIfYCQACiBQAh-AJAAKMFACGGAwEAswUAIYcDAQCzBQAhuQMBALMFACG6AyAA0gYAIbsDAQCzBQAhvQMAAPoHvQMivwMAAPsHvwMiwAMgANIGACHBAwEAswUAIcIDAQCzBQAhwwOAAAAAAcQDQACjBQAhxQMBALMFACHGAyAA_AcAIccDAQCzBQAhyANAAKMFACHJA0AAowUAIQ0EBgIFCgMGDgQMABcgYhEhFAUiWg0kXhUlXw4mYA8nYRAoYxIpZxYBAwABAQMAAQEDEAECAwABBwAGDQwAFBEWBxIpBhMqBhQrBxUuBxYvBRgzDRk1Cxo5Dhs9DxxCECBHEQYHAAYIGAYJGwYMAAwOHwgQJQsCCgAHDQAJAgsgCAwACgELIQACBwAGDwAHAwkmAA4nABAoAAIHAAYXNAECAwABBwAGAgM-AQcABgIDAAEHQwYGA0gBBwAGDAATGk4SHUkRHkoRAgMAAR8AEQIaUAAeTwAJE1EAFVIAFlMAGFQAGVUAGlYAG1cAHFgAIFkAASMAAQEDAAEMBGgABWkABmoAIHEAIWsAImwAJG0AJW4AJm8AJ3AAKHIAKXMAAAAABQwAHDwAHT0AHj4AHz8AIAAAAAAABQwAHDwAHT0AHj4AHz8AIAEDAAEBAwABAwwAJT4AJj8AJwAAAAMMACU-ACY_ACcBAwABAQMAAQMMACw-AC0_AC4AAAADDAAsPgAtPwAuAQPAAQEBA8YBAQMMADM-ADQ_ADUAAAADDAAzPgA0PwA1AQMAAQEDAAEFDAA6PAA7PQA8PgA9PwA-AAAAAAAFDAA6PAA7PQA8PgA9PwA-AxHuAQcS7wEGFPABBwMR9gEHEvcBBhT4AQcFDABDPABEPQBFPgBGPwBHAAAAAAAFDABDPABEPQBFPgBGPwBHAQcABgEHAAYFDABMPABNPQBOPgBPPwBQAAAAAAAFDABMPABNPQBOPgBPPwBQAgoABw0ACQIKAAcNAAkFDABVPABWPQBXPgBYPwBZAAAAAAAFDABVPABWPQBXPgBYPwBZAgcABg8ABwIHAAYPAAcFDABePABfPQBgPgBhPwBiAAAAAAAFDABePABfPQBgPgBhPwBiAAAFDABnPABoPQBpPgBqPwBrAAAAAAAFDABnPABoPQBpPgBqPwBrAgMAAQcABgIDAAEHAAYDDABwPgBxPwByAAAAAwwAcD4AcT8AcgIHAAYX-wIBAgcABheBAwEDDAB3PgB4PwB5AAAAAwwAdz4AeD8AeQIDAAEHAAYCAwABBwAGAwwAfj4Afz8AgAEAAAADDAB-PgB_PwCAAQIDqQMBBwAGAgOvAwEHAAYFDACFATwAhgE9AIcBPgCIAT8AiQEAAAAAAAUMAIUBPACGAT0AhwE-AIgBPwCJAQIDAAEHwQMGAgMAAQfHAwYFDACOATwAjwE9AJABPgCRAT8AkgEAAAAAAAUMAI4BPACPAT0AkAE-AJEBPwCSAQMD2QMBBwAGHdoDEQMD4AMBBwAGHeEDEQUMAJcBPACYAT0AmQE-AJoBPwCbAQAAAAAABQwAlwE8AJgBPQCZAT4AmgE_AJsBAgMAAR8AEQIDAAEfABEDDACgAT4AoQE_AKIBAAAAAwwAoAE-AKEBPwCiAQEjAAEBIwABAwwApwE-AKgBPwCpAQAAAAMMAKcBPgCoAT8AqQEqAgErdAEsdgEtdwEueAEwegExfBgyfRkzfwE0gQEYNYIBGjiDAQE5hAEBOoUBGECIARtBiQEhQooBAkOLAQJEjAECRY0BAkaOAQJHkAECSJIBGEmTASJKlQECS5cBGEyYASNNmQECTpoBAk-bARhQngEkUZ8BKFKgAQNToQEDVKIBA1WjAQNWpAEDV6YBA1ioARhZqQEpWqsBA1utARhcrgEqXa8BA16wAQNfsQEYYLQBK2G1AS9itgEEY7cBBGS4AQRluQEEZroBBGe8AQRovgEYab8BMGrCAQRrxAEYbMUBMW3HAQRuyAEEb8kBGHDMATJxzQE2cs4BFnPPARZ00AEWddEBFnbSARZ31AEWeNYBGHnXATd62QEWe9sBGHzcATh93QEWft4BFn_fARiAAeIBOYEB4wE_ggHkAQaDAeUBBoQB5gEGhQHnAQaGAegBBocB6gEGiAHsARiJAe0BQIoB8gEGiwH0ARiMAfUBQY0B-QEGjgH6AQaPAfsBGJAB_gFCkQH_AUiSAYACB5MBgQIHlAGCAgeVAYMCB5YBhAIHlwGGAgeYAYgCGJkBiQJJmgGLAgebAY0CGJwBjgJKnQGPAgeeAZACB58BkQIYoAGUAkuhAZUCUaIBlgIIowGXAgikAZgCCKUBmQIIpgGaAginAZwCCKgBngIYqQGfAlKqAaECCKsBowIYrAGkAlOtAaUCCK4BpgIIrwGnAhiwAaoCVLEBqwJasgGsAguzAa0CC7QBrgILtQGvAgu2AbACC7cBsgILuAG0Ahi5AbUCW7oBtwILuwG5Ahi8AboCXL0BuwILvgG8Agu_Ab0CGMABwAJdwQHBAmPCAcMCCcMBxAIJxAHHAgnFAcgCCcYByQIJxwHLAgnIAc0CGMkBzgJkygHQAgnLAdICGMwB0wJlzQHUAgnOAdUCCc8B1gIY0AHZAmbRAdoCbNIB2wIF0wHcAgXUAd0CBdUB3gIF1gHfAgXXAeECBdgB4wIY2QHkAm3aAeYCBdsB6AIY3AHpAm7dAeoCBd4B6wIF3wHsAhjgAe8Cb-EB8AJz4gHxAg3jAfICDeQB8wIN5QH0Ag3mAfUCDecB9wIN6AH5AhjpAfoCdOoB_QIN6wH_AhjsAYADde0BggMN7gGDAw3vAYQDGPABhwN28QGIA3ryAYkDDvMBigMO9AGLAw71AYwDDvYBjQMO9wGPAw74AZEDGPkBkgN7-gGUAw77AZYDGPwBlwN8_QGYAw7-AZkDDv8BmgMYgAKdA32BAp4DgQGCAp8DD4MCoAMPhAKhAw-FAqIDD4YCowMPhwKlAw-IAqcDGIkCqAOCAYoCqwMPiwKtAxiMAq4DgwGNArADD44CsQMPjwKyAxiQArUDhAGRArYDigGSArcDEJMCuAMQlAK5AxCVAroDEJYCuwMQlwK9AxCYAr8DGJkCwAOLAZoCwwMQmwLFAxicAsYDjAGdAsgDEJ4CyQMQnwLKAxigAs0DjQGhAs4DkwGiAs8DEaMC0AMRpALRAxGlAtIDEaYC0wMRpwLVAxGoAtcDGKkC2AOUAaoC3AMRqwLeAxisAt8DlQGtAuIDEa4C4wMRrwLkAxiwAucDlgGxAugDnAGyAukDErMC6gMStALrAxK1AuwDErYC7QMStwLvAxK4AvEDGLkC8gOdAboC9AMSuwL2Axi8AvcDngG9AvgDEr4C-QMSvwL6AxjAAv0DnwHBAv4DowHCAv8DFcMCgAQVxAKBBBXFAoIEFcYCgwQVxwKFBBXIAocEGMkCiASkAcoCigQVywKMBBjMAo0EpQHNAo4EFc4CjwQVzwKQBBjQApMEpgHRApQEqgE" } config.compilerWasm = { getRuntime: async () => require('./query_compiler_fast_bg.js'), diff --git a/apps/modeling-commons-backend/generated/prisma/package.json b/apps/modeling-commons-backend/generated/prisma/package.json index 10a469b5..3a7d7918 100644 --- a/apps/modeling-commons-backend/generated/prisma/package.json +++ b/apps/modeling-commons-backend/generated/prisma/package.json @@ -1,5 +1,5 @@ { - "name": "prisma-client-fcaac490b84f33f683f0601ea8fa99fcb04047fea616e2c73bc77a003e98770f", + "name": "prisma-client-273e5a3fcac0dca877bb4bb677e273e919cec59c8543681ceb779e6bd9ce1cc5", "main": "index.js", "types": "index.d.ts", "browser": "default.js", diff --git a/apps/modeling-commons-backend/generated/prisma/schema.prisma b/apps/modeling-commons-backend/generated/prisma/schema.prisma index 492dcbd1..46da80f2 100644 --- a/apps/modeling-commons-backend/generated/prisma/schema.prisma +++ b/apps/modeling-commons-backend/generated/prisma/schema.prisma @@ -87,6 +87,8 @@ model User { modelLikes ModelLike[] modelInteractions ModelInteraction[] modelDrafts ModelDraft[] + comments ModelComment[] + commentLikes ModelCommentLike[] // Better Auth Admin plugin role String? @@ -201,6 +203,7 @@ model Model { likes ModelLike[] interactions ModelInteraction[] drafts ModelDraft[] + comments ModelComment[] @@unique([id, latestVersionNumber]) @@index([parentModelId]) @@ -357,6 +360,48 @@ model ModelDraft { @@index([modelId]) } +model ModelComment { + id String @id @default(uuid()) + legacyId Int? @unique + + parentId String? + userId String? + + modelId String + versionNumber Int? + + content String? @db.Text + likesCount Int @default(0) + + createdAt DateTime @default(now()) @db.Timestamptz(3) + updatedAt DateTime @updatedAt @db.Timestamptz(3) + editedAt DateTime? @db.Timestamptz(3) + deletedAt DateTime? @db.Timestamptz(3) + + model Model @relation(fields: [modelId], references: [id], onDelete: Cascade) + user User? @relation(fields: [userId], references: [id], onDelete: SetNull) + parent ModelComment? @relation("CommentReplies", fields: [parentId], references: [id], onDelete: Cascade) + replies ModelComment[] @relation("CommentReplies") + + likes ModelCommentLike[] + + @@index([modelId, parentId, createdAt]) + @@index([parentId]) + @@index([userId]) +} + +model ModelCommentLike { + modelCommentId String + userId String + createdAt DateTime @default(now()) @db.Timestamptz(3) + + modelComment ModelComment @relation(fields: [modelCommentId], references: [id], onDelete: Cascade) + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + + @@id([modelCommentId, userId]) + @@index([userId]) +} + model Event { id String @id @default(uuid()) type String diff --git a/apps/modeling-commons-backend/prisma/migrations/20260720000000_add_model_comment/migration.sql b/apps/modeling-commons-backend/prisma/migrations/20260720000000_add_model_comment/migration.sql new file mode 100644 index 00000000..bca2ef29 --- /dev/null +++ b/apps/modeling-commons-backend/prisma/migrations/20260720000000_add_model_comment/migration.sql @@ -0,0 +1,56 @@ +-- CreateTable +CREATE TABLE "ModelComment" ( + "id" TEXT NOT NULL, + "legacyId" INTEGER, + "parentId" TEXT, + "userId" TEXT, + "modelId" TEXT NOT NULL, + "versionNumber" INTEGER, + "content" TEXT, + "likesCount" INTEGER NOT NULL DEFAULT 0, + "createdAt" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMPTZ(3) NOT NULL, + "editedAt" TIMESTAMPTZ(3), + "deletedAt" TIMESTAMPTZ(3), + + CONSTRAINT "ModelComment_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "ModelCommentLike" ( + "modelCommentId" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "createdAt" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "ModelCommentLike_pkey" PRIMARY KEY ("modelCommentId","userId") +); + +-- CreateIndex +CREATE UNIQUE INDEX "ModelComment_legacyId_key" ON "ModelComment"("legacyId"); + +-- CreateIndex +CREATE INDEX "ModelComment_modelId_parentId_createdAt_idx" ON "ModelComment"("modelId", "parentId", "createdAt"); + +-- CreateIndex +CREATE INDEX "ModelComment_parentId_idx" ON "ModelComment"("parentId"); + +-- CreateIndex +CREATE INDEX "ModelComment_userId_idx" ON "ModelComment"("userId"); + +-- CreateIndex +CREATE INDEX "ModelCommentLike_userId_idx" ON "ModelCommentLike"("userId"); + +-- AddForeignKey +ALTER TABLE "ModelComment" ADD CONSTRAINT "ModelComment_modelId_fkey" FOREIGN KEY ("modelId") REFERENCES "Model"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ModelComment" ADD CONSTRAINT "ModelComment_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ModelComment" ADD CONSTRAINT "ModelComment_parentId_fkey" FOREIGN KEY ("parentId") REFERENCES "ModelComment"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ModelCommentLike" ADD CONSTRAINT "ModelCommentLike_modelCommentId_fkey" FOREIGN KEY ("modelCommentId") REFERENCES "ModelComment"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ModelCommentLike" ADD CONSTRAINT "ModelCommentLike_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/apps/modeling-commons-backend/prisma/schema.prisma b/apps/modeling-commons-backend/prisma/schema.prisma index 3d14af33..09c58ea3 100644 --- a/apps/modeling-commons-backend/prisma/schema.prisma +++ b/apps/modeling-commons-backend/prisma/schema.prisma @@ -87,6 +87,8 @@ model User { modelLikes ModelLike[] modelInteractions ModelInteraction[] modelDrafts ModelDraft[] + comments ModelComment[] + commentLikes ModelCommentLike[] // Better Auth Admin plugin role String? @@ -201,6 +203,7 @@ model Model { likes ModelLike[] interactions ModelInteraction[] drafts ModelDraft[] + comments ModelComment[] @@unique([id, latestVersionNumber]) @@ -358,6 +361,48 @@ model ModelDraft { @@index([modelId]) } +model ModelComment { + id String @id @default(uuid()) + legacyId Int? @unique + + parentId String? + userId String? + + modelId String + versionNumber Int? + + content String? @db.Text + likesCount Int @default(0) + + createdAt DateTime @default(now()) @db.Timestamptz(3) + updatedAt DateTime @updatedAt @db.Timestamptz(3) + editedAt DateTime? @db.Timestamptz(3) + deletedAt DateTime? @db.Timestamptz(3) + + model Model @relation(fields: [modelId], references: [id], onDelete: Cascade) + user User? @relation(fields: [userId], references: [id], onDelete: SetNull) + parent ModelComment? @relation("CommentReplies", fields: [parentId], references: [id], onDelete: Cascade) + replies ModelComment[] @relation("CommentReplies") + + likes ModelCommentLike[] + + @@index([modelId, parentId, createdAt]) + @@index([parentId]) + @@index([userId]) +} + +model ModelCommentLike { + modelCommentId String + userId String + createdAt DateTime @default(now()) @db.Timestamptz(3) + + modelComment ModelComment @relation(fields: [modelCommentId], references: [id], onDelete: Cascade) + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + + @@id([modelCommentId, userId]) + @@index([userId]) +} + model Event { id String @id @default(uuid()) type String From 1a9808c2cd81d42c9abfab0f55df5b5cbc034cc6 Mon Sep 17 00:00:00 2001 From: Omar Ibrahim Date: Mon, 20 Jul 2026 13:40:38 -0500 Subject: [PATCH 15/51] feat(commons): add model-comment domain layer - createComment factory (uuid id, trimmed content validation via rules.limits.comment.content), assertNotDeleted, assertParentMatchesModel, assertCanEdit, assertCanDelete - CommentNotFoundError, CommentDeletedError, CommentBodyInvalidError, ParentCommentMismatchError - COMMENT_TREE_DEFAULTS (2 replies x 3 levels) for the future query layer --- .../src/config/rules.ts | 4 + .../domain/model-comment.domain.spec.ts | 172 ++++++++++++++++++ .../domain/model-comment.domain.ts | 74 ++++++++ .../domain/model-comment.errors.ts | 29 +++ .../domain/model-comment.types.ts | 23 +++ 5 files changed, 302 insertions(+) create mode 100644 apps/modeling-commons-backend/src/modules/model-comment/domain/model-comment.domain.spec.ts create mode 100644 apps/modeling-commons-backend/src/modules/model-comment/domain/model-comment.domain.ts create mode 100644 apps/modeling-commons-backend/src/modules/model-comment/domain/model-comment.errors.ts create mode 100644 apps/modeling-commons-backend/src/modules/model-comment/domain/model-comment.types.ts diff --git a/apps/modeling-commons-backend/src/config/rules.ts b/apps/modeling-commons-backend/src/config/rules.ts index 6fd62711..913badd9 100644 --- a/apps/modeling-commons-backend/src/config/rules.ts +++ b/apps/modeling-commons-backend/src/config/rules.ts @@ -163,6 +163,10 @@ const rules = { '/sign-up': { window: 60, max: 10 }, }, } as BetterAuthRateLimitOptions, + + comment: { + content: { min: 1, max: 10_000 } as MinMax, + }, }, mime: { deniedTypes: [ diff --git a/apps/modeling-commons-backend/src/modules/model-comment/domain/model-comment.domain.spec.ts b/apps/modeling-commons-backend/src/modules/model-comment/domain/model-comment.domain.spec.ts new file mode 100644 index 00000000..faee6f20 --- /dev/null +++ b/apps/modeling-commons-backend/src/modules/model-comment/domain/model-comment.domain.spec.ts @@ -0,0 +1,172 @@ +import { describe, it, expect } from 'vitest'; +import modelCommentDomain from '#src/modules/model-comment/domain/model-comment.domain.ts'; +import { + CommentBodyInvalidError, + CommentDeletedError, + ParentCommentMismatchError, +} from '#src/modules/model-comment/domain/model-comment.errors.ts'; +import { ForbiddenException } from '#src/shared/exceptions/index.ts'; +import type { ModelCommentEntity } from '#src/modules/model-comment/domain/model-comment.types.ts'; + +const domain = modelCommentDomain(); + +function makeComment(overrides: Partial = {}): ModelCommentEntity { + const now = new Date(); + return { + id: 'comment-1', + legacyId: null, + parentId: null, + userId: 'user-1', + modelId: 'model-1', + versionNumber: null, + content: 'hello world', + likesCount: 0, + createdAt: now, + updatedAt: now, + editedAt: null, + deletedAt: null, + ...overrides, + }; +} + +describe('modelCommentDomain', () => { + describe('createComment', () => { + it('builds a well-formed entity', () => { + const comment = domain.createComment({ + modelId: 'model-1', + userId: 'user-1', + content: 'hello world', + }); + + expect(comment.modelId).toBe('model-1'); + expect(comment.userId).toBe('user-1'); + expect(comment.content).toBe('hello world'); + expect(comment.parentId).toBeNull(); + expect(comment.versionNumber).toBeNull(); + expect(comment.editedAt).toBeNull(); + expect(comment.deletedAt).toBeNull(); + expect(comment.likesCount).toBe(0); + expect(comment.createdAt).toEqual(comment.updatedAt); + expect(typeof comment.id).toBe('string'); + expect(comment.id.length).toBeGreaterThan(0); + }); + + it('carries parentId and versionNumber when provided', () => { + const comment = domain.createComment({ + modelId: 'model-1', + userId: 'user-1', + parentId: 'parent-1', + versionNumber: 2, + content: 'a reply', + }); + + expect(comment.parentId).toBe('parent-1'); + expect(comment.versionNumber).toBe(2); + }); + + it('trims whitespace before storing content', () => { + const comment = domain.createComment({ + modelId: 'model-1', + userId: 'user-1', + content: ' hello ', + }); + + expect(comment.content).toBe('hello'); + }); + + it('throws CommentBodyInvalidError for empty content', () => { + expect(() => + domain.createComment({ modelId: 'model-1', userId: 'user-1', content: '' }), + ).toThrow(CommentBodyInvalidError); + }); + + it('throws CommentBodyInvalidError for whitespace-only content', () => { + expect(() => + domain.createComment({ modelId: 'model-1', userId: 'user-1', content: ' \n\t ' }), + ).toThrow(CommentBodyInvalidError); + }); + + it('throws CommentBodyInvalidError for content over 10,000 characters', () => { + const tooLong = 'a'.repeat(10_001); + expect(() => + domain.createComment({ modelId: 'model-1', userId: 'user-1', content: tooLong }), + ).toThrow(CommentBodyInvalidError); + }); + + it('accepts content at exactly the 10,000 character bound', () => { + const atBound = 'a'.repeat(10_000); + const comment = domain.createComment({ + modelId: 'model-1', + userId: 'user-1', + content: atBound, + }); + expect(comment.content).toHaveLength(10_000); + }); + }); + + describe('assertNotDeleted', () => { + it('passes when not deleted', () => { + expect(() => domain.assertNotDeleted(makeComment())).not.toThrow(); + }); + + it('throws CommentDeletedError when deletedAt is set', () => { + expect(() => + domain.assertNotDeleted(makeComment({ deletedAt: new Date() })), + ).toThrow(CommentDeletedError); + }); + }); + + describe('assertParentMatchesModel', () => { + it('passes when the parent belongs to the model', () => { + const parent = makeComment({ modelId: 'model-1' }); + expect(() => domain.assertParentMatchesModel(parent, 'model-1')).not.toThrow(); + }); + + it('throws ParentCommentMismatchError when the parent belongs to a different model', () => { + const parent = makeComment({ modelId: 'model-2' }); + expect(() => domain.assertParentMatchesModel(parent, 'model-1')).toThrow( + ParentCommentMismatchError, + ); + }); + }); + + describe('assertCanEdit', () => { + it('passes for the author', () => { + const comment = makeComment({ userId: 'user-1' }); + expect(() => domain.assertCanEdit(comment, 'user-1')).not.toThrow(); + }); + + it('throws ForbiddenException for a non-author', () => { + const comment = makeComment({ userId: 'user-1' }); + expect(() => domain.assertCanEdit(comment, 'user-2')).toThrow(ForbiddenException); + }); + + it('throws ForbiddenException for an admin who is not the author', () => { + const comment = makeComment({ userId: 'user-1' }); + expect(() => domain.assertCanEdit(comment, 'admin-1')).toThrow(ForbiddenException); + }); + }); + + describe('assertCanDelete', () => { + it('passes for the author', () => { + const comment = makeComment({ userId: 'user-1' }); + expect(() => + domain.assertCanDelete(comment, { id: 'user-1', systemRole: 'user' }), + ).not.toThrow(); + }); + + it('passes for an admin who is not the author', () => { + const comment = makeComment({ userId: 'user-1' }); + expect(() => + domain.assertCanDelete(comment, { id: 'admin-1', systemRole: 'admin' }), + ).not.toThrow(); + }); + + it('throws ForbiddenException for a random user', () => { + const comment = makeComment({ userId: 'user-1' }); + expect(() => + domain.assertCanDelete(comment, { id: 'user-2', systemRole: 'user' }), + ).toThrow(ForbiddenException); + }); + }); +}); diff --git a/apps/modeling-commons-backend/src/modules/model-comment/domain/model-comment.domain.ts b/apps/modeling-commons-backend/src/modules/model-comment/domain/model-comment.domain.ts new file mode 100644 index 00000000..99a8c709 --- /dev/null +++ b/apps/modeling-commons-backend/src/modules/model-comment/domain/model-comment.domain.ts @@ -0,0 +1,74 @@ +import { randomUUID } from 'node:crypto'; +import rules from '#src/config/rules.ts'; +import { ForbiddenException } from '#src/shared/exceptions/index.ts'; +import { + CommentBodyInvalidError, + CommentDeletedError, + ParentCommentMismatchError, +} from '#src/modules/model-comment/domain/model-comment.errors.ts'; +import type { + CommentAuthCaller, + CreateCommentProps, + ModelCommentEntity, +} from '#src/modules/model-comment/domain/model-comment.types.ts'; + +const CONTENT_LENGTH = rules.limits.comment.content; + +export default function modelCommentDomain() { + return { + createComment(props: CreateCommentProps): ModelCommentEntity { + const content = props.content.trim(); + const min = CONTENT_LENGTH.min ?? 0; + const max = CONTENT_LENGTH.max ?? Infinity; + + if (content.length < min) { + throw new CommentBodyInvalidError('content is empty'); + } + if (content.length > max) { + throw new CommentBodyInvalidError(`content exceeds ${max} characters`); + } + + const now = new Date(); + return { + id: randomUUID(), + legacyId: null, + parentId: props.parentId ?? null, + userId: props.userId, + modelId: props.modelId, + versionNumber: props.versionNumber ?? null, + content, + likesCount: 0, + createdAt: now, + updatedAt: now, + editedAt: null, + deletedAt: null, + }; + }, + + assertNotDeleted(comment: ModelCommentEntity): void { + if (comment.deletedAt !== null) { + throw new CommentDeletedError(comment.id); + } + }, + + assertParentMatchesModel(parent: ModelCommentEntity, modelId: string): void { + if (parent.modelId !== modelId) { + throw new ParentCommentMismatchError(); + } + }, + + assertCanEdit(comment: ModelCommentEntity, callerId: string): void { + if (comment.userId !== callerId) { + throw new ForbiddenException('Only the comment author can edit this comment'); + } + }, + + assertCanDelete(comment: ModelCommentEntity, caller: CommentAuthCaller): void { + const isAuthor = comment.userId === caller.id; + const isAdmin = caller.systemRole === 'admin'; + if (!isAuthor && !isAdmin) { + throw new ForbiddenException('Only the comment author or an admin can delete this comment'); + } + }, + }; +} diff --git a/apps/modeling-commons-backend/src/modules/model-comment/domain/model-comment.errors.ts b/apps/modeling-commons-backend/src/modules/model-comment/domain/model-comment.errors.ts new file mode 100644 index 00000000..9022d7a8 --- /dev/null +++ b/apps/modeling-commons-backend/src/modules/model-comment/domain/model-comment.errors.ts @@ -0,0 +1,29 @@ +import { + ArgumentInvalidException, + ConflictException, + NotFoundException, +} from '#src/shared/exceptions/index.ts'; + +export class CommentNotFoundError extends NotFoundException { + constructor(id: string) { + super(`Comment ${id} not found`); + } +} + +export class CommentDeletedError extends ConflictException { + constructor(id: string) { + super(`Comment ${id} is deleted`); + } +} + +export class CommentBodyInvalidError extends ArgumentInvalidException { + constructor(reason: string) { + super(`Invalid comment content: ${reason}`); + } +} + +export class ParentCommentMismatchError extends ArgumentInvalidException { + constructor() { + super('Parent comment belongs to a different model'); + } +} diff --git a/apps/modeling-commons-backend/src/modules/model-comment/domain/model-comment.types.ts b/apps/modeling-commons-backend/src/modules/model-comment/domain/model-comment.types.ts new file mode 100644 index 00000000..21cf40a4 --- /dev/null +++ b/apps/modeling-commons-backend/src/modules/model-comment/domain/model-comment.types.ts @@ -0,0 +1,23 @@ +import type { ModelComment } from '#prisma/index'; + +export type ModelCommentEntity = ModelComment; + +export type CreateCommentProps = { + modelId: string; + userId: string; + parentId?: string; + versionNumber?: number; + content: string; +}; + +export type CommentAuthCaller = { + id: string; + systemRole?: string; +}; + +// Mirrors the frontend's `maximumNested` / `maximumShownRepliesPerLevel` +// (thread-shape/UX config, not a security bound — see rules.ts for that). +export const COMMENT_TREE_DEFAULTS = { + maximumNested: 3, + maximumShownRepliesPerLevel: 2, +}; From 4addf6557c09666f2fa3fe63c0274c2939f31560 Mon Sep 17 00:00:00 2001 From: Omar Ibrahim Date: Mon, 20 Jul 2026 13:44:34 -0500 Subject: [PATCH 16/51] feat(commons): add model-comment DTOs and route param schemas - create/update-comment request DTOs, sourcing content bounds from rules.ts (limits.comment.content) instead of a duplicated literal - list-comments query DTO intersecting the shared paginated query with a createdAt/updatedAt/likes sort - recursive comment response DTO (Type.Cyclic + Type.Ref, this typebox version has no Type.Recursive) with a nested Paginated replies envelope, plus its paginated-list wrapper - model-comment.schemas.ts route params, naming the model param `id` (not `modelId`) to match resolveModel()'s hard-read of request.params.id --- .../dtos/comment.paginated.response.dto.ts | 10 +++++ .../dtos/comment.response.dto.ts | 45 +++++++++++++++++++ .../dtos/create-comment.request.dto.ts | 15 +++++++ .../dtos/list-comments.query.dto.ts | 15 +++++++ .../dtos/update-comment.request.dto.ts | 13 ++++++ .../model-comment/model-comment.schemas.ts | 12 +++++ 6 files changed, 110 insertions(+) create mode 100644 apps/modeling-commons-backend/src/modules/model-comment/dtos/comment.paginated.response.dto.ts create mode 100644 apps/modeling-commons-backend/src/modules/model-comment/dtos/comment.response.dto.ts create mode 100644 apps/modeling-commons-backend/src/modules/model-comment/dtos/create-comment.request.dto.ts create mode 100644 apps/modeling-commons-backend/src/modules/model-comment/dtos/list-comments.query.dto.ts create mode 100644 apps/modeling-commons-backend/src/modules/model-comment/dtos/update-comment.request.dto.ts create mode 100644 apps/modeling-commons-backend/src/modules/model-comment/model-comment.schemas.ts diff --git a/apps/modeling-commons-backend/src/modules/model-comment/dtos/comment.paginated.response.dto.ts b/apps/modeling-commons-backend/src/modules/model-comment/dtos/comment.paginated.response.dto.ts new file mode 100644 index 00000000..fe332d79 --- /dev/null +++ b/apps/modeling-commons-backend/src/modules/model-comment/dtos/comment.paginated.response.dto.ts @@ -0,0 +1,10 @@ +import { Type } from 'typebox'; +import { commentResponseDtoSchema } from '#src/modules/model-comment/dtos/comment.response.dto.ts'; +import { paginatedResponseBaseSchema } from '#src/shared/api/paginated.response.base.ts'; + +export const commentPaginatedResponseSchema = Type.Intersect([ + paginatedResponseBaseSchema, + Type.Object({ + data: Type.Array(commentResponseDtoSchema), + }), +]); diff --git a/apps/modeling-commons-backend/src/modules/model-comment/dtos/comment.response.dto.ts b/apps/modeling-commons-backend/src/modules/model-comment/dtos/comment.response.dto.ts new file mode 100644 index 00000000..afb6ba6c --- /dev/null +++ b/apps/modeling-commons-backend/src/modules/model-comment/dtos/comment.response.dto.ts @@ -0,0 +1,45 @@ +import { Type, type Static } from 'typebox'; + +export const commentAuthorDtoSchema = Type.Object({ + id: Type.String({ format: 'uuid' }), + name: Type.String(), + image: Type.String(), +}); + +export type CommentAuthorDto = Static; + +export const commentResponseDtoSchema = Type.Cyclic( + { + Comment: Type.Object({ + id: Type.String({ format: 'uuid' }), + modelId: Type.String({ format: 'uuid' }), + parentId: Type.Optional(Type.String({ format: 'uuid' })), + versionNumber: Type.Optional(Type.Integer()), + legacyId: Type.Optional(Type.Integer()), + author: commentAuthorDtoSchema, + content: Type.String(), + createdAt: Type.String(), + edited: Type.Optional(Type.Boolean()), + deleted: Type.Optional(Type.Boolean()), + likes: Type.Integer(), + likedByMe: Type.Optional(Type.Boolean()), + permissions: Type.Optional( + Type.Object({ + canEdit: Type.Optional(Type.Boolean()), + canDelete: Type.Optional(Type.Boolean()), + }), + ), + replies: Type.Optional( + Type.Object({ + count: Type.Integer(), + limit: Type.Integer(), + page: Type.Integer(), + data: Type.Array(Type.Ref('Comment')), + }), + ), + }), + }, + 'Comment', +); + +export type CommentResponseDto = Static; diff --git a/apps/modeling-commons-backend/src/modules/model-comment/dtos/create-comment.request.dto.ts b/apps/modeling-commons-backend/src/modules/model-comment/dtos/create-comment.request.dto.ts new file mode 100644 index 00000000..b1cfb61c --- /dev/null +++ b/apps/modeling-commons-backend/src/modules/model-comment/dtos/create-comment.request.dto.ts @@ -0,0 +1,15 @@ +import { Type, type Static } from 'typebox'; +import rules from '#src/config/rules.ts'; + +const CONTENT_LENGTH = rules.limits.comment.content; + +export const createCommentRequestDtoSchema = Type.Object({ + content: Type.String({ + minLength: CONTENT_LENGTH.min, + maxLength: CONTENT_LENGTH.max, + }), + parentId: Type.Optional(Type.String({ format: 'uuid' })), + versionNumber: Type.Optional(Type.Integer({ minimum: 1 })), +}); + +export type CreateCommentRequestDto = Static; diff --git a/apps/modeling-commons-backend/src/modules/model-comment/dtos/list-comments.query.dto.ts b/apps/modeling-commons-backend/src/modules/model-comment/dtos/list-comments.query.dto.ts new file mode 100644 index 00000000..0efcd94b --- /dev/null +++ b/apps/modeling-commons-backend/src/modules/model-comment/dtos/list-comments.query.dto.ts @@ -0,0 +1,15 @@ +import { Type, type Static } from 'typebox'; +import { paginatedQueryRequestDtoSchema } from '#src/shared/api/paginated-query.request.dto.ts'; + +export const commentSortSchema = Type.Optional( + Type.Union([Type.Literal('createdAt'), Type.Literal('updatedAt'), Type.Literal('likes')], { + default: 'createdAt', + }), +); + +export const listCommentsQueryDtoSchema = Type.Intersect([ + paginatedQueryRequestDtoSchema, + Type.Object({ sort: commentSortSchema }), +]); + +export type ListCommentsQueryDto = Static; diff --git a/apps/modeling-commons-backend/src/modules/model-comment/dtos/update-comment.request.dto.ts b/apps/modeling-commons-backend/src/modules/model-comment/dtos/update-comment.request.dto.ts new file mode 100644 index 00000000..c26c9fbc --- /dev/null +++ b/apps/modeling-commons-backend/src/modules/model-comment/dtos/update-comment.request.dto.ts @@ -0,0 +1,13 @@ +import { Type, type Static } from 'typebox'; +import rules from '#src/config/rules.ts'; + +const CONTENT_LENGTH = rules.limits.comment.content; + +export const updateCommentRequestDtoSchema = Type.Object({ + content: Type.String({ + minLength: CONTENT_LENGTH.min, + maxLength: CONTENT_LENGTH.max, + }), +}); + +export type UpdateCommentRequestDto = Static; diff --git a/apps/modeling-commons-backend/src/modules/model-comment/model-comment.schemas.ts b/apps/modeling-commons-backend/src/modules/model-comment/model-comment.schemas.ts new file mode 100644 index 00000000..d539a26d --- /dev/null +++ b/apps/modeling-commons-backend/src/modules/model-comment/model-comment.schemas.ts @@ -0,0 +1,12 @@ +import { Type, type Static } from 'typebox'; + +export const modelCommentParamsSchema = Type.Object({ + id: Type.String({ format: 'uuid' }), +}); +export type ModelCommentParams = Static; + +export const modelCommentDetailParamsSchema = Type.Object({ + id: Type.String({ format: 'uuid' }), + commentId: Type.String({ format: 'uuid' }), +}); +export type ModelCommentDetailParams = Static; From 2bada66d6609a0f6185ef0e046e0462eb3f01b36 Mon Sep 17 00:00:00 2001 From: Omar Ibrahim Date: Mon, 20 Jul 2026 13:48:24 -0500 Subject: [PATCH 17/51] feat(commons): add model-comment repository layer - Prisma-backed ModelCommentRepository (port, impl, mock) mirroring model-author's file split; every read hydrates user + likedByMe via a single filtered _count include, no per-row round trips. - listTopLevel/listReplies use paginate() with one findMany + count; addLikeTx/removeLikeTx keep likesCount in sync with the like rows. - Extend ModelCommentEntity with optional user/likedByMe so the repository's hydrated reads and plain writes share one type. --- .../database/model-comment.record.ts | 15 ++ .../database/model-comment.repository.mock.ts | 19 +++ .../database/model-comment.repository.port.ts | 32 ++++ .../database/model-comment.repository.ts | 160 ++++++++++++++++++ .../domain/model-comment.types.ts | 14 +- 5 files changed, 239 insertions(+), 1 deletion(-) create mode 100644 apps/modeling-commons-backend/src/modules/model-comment/database/model-comment.record.ts create mode 100644 apps/modeling-commons-backend/src/modules/model-comment/database/model-comment.repository.mock.ts create mode 100644 apps/modeling-commons-backend/src/modules/model-comment/database/model-comment.repository.port.ts create mode 100644 apps/modeling-commons-backend/src/modules/model-comment/database/model-comment.repository.ts diff --git a/apps/modeling-commons-backend/src/modules/model-comment/database/model-comment.record.ts b/apps/modeling-commons-backend/src/modules/model-comment/database/model-comment.record.ts new file mode 100644 index 00000000..1b05f6c9 --- /dev/null +++ b/apps/modeling-commons-backend/src/modules/model-comment/database/model-comment.record.ts @@ -0,0 +1,15 @@ +import type { Prisma } from '#prisma/index'; + +// `_count.likes` is filtered to `viewerId` so every read hydrates `likedByMe` +// from the same query (no per-row round trip). A viewerId of '' never +// matches a real uuid, so the count is always 0 for anonymous reads. +export function modelCommentInclude(viewerId?: string) { + return { + user: { select: { id: true, name: true, image: true } }, + _count: { select: { likes: { where: { userId: viewerId ?? '' } } } }, + } satisfies Prisma.ModelCommentInclude; +} + +export type ModelCommentRecord = Prisma.ModelCommentGetPayload<{ + include: ReturnType; +}>; diff --git a/apps/modeling-commons-backend/src/modules/model-comment/database/model-comment.repository.mock.ts b/apps/modeling-commons-backend/src/modules/model-comment/database/model-comment.repository.mock.ts new file mode 100644 index 00000000..87f19869 --- /dev/null +++ b/apps/modeling-commons-backend/src/modules/model-comment/database/model-comment.repository.mock.ts @@ -0,0 +1,19 @@ +import { vi } from 'vitest'; +import type { ModelCommentRepository } from '#src/modules/model-comment/database/model-comment.repository.port.ts'; + +export function mockModelCommentRepository(): { + [K in keyof ModelCommentRepository]: ReturnType; +} { + return { + findById: vi.fn(), + findByIdTx: vi.fn(), + listTopLevel: vi.fn(), + listReplies: vi.fn(), + countRepliesByParent: vi.fn(), + insertTx: vi.fn(), + updateContentTx: vi.fn(), + softDeleteTx: vi.fn(), + addLikeTx: vi.fn(), + removeLikeTx: vi.fn(), + }; +} diff --git a/apps/modeling-commons-backend/src/modules/model-comment/database/model-comment.repository.port.ts b/apps/modeling-commons-backend/src/modules/model-comment/database/model-comment.repository.port.ts new file mode 100644 index 00000000..e2d5447f --- /dev/null +++ b/apps/modeling-commons-backend/src/modules/model-comment/database/model-comment.repository.port.ts @@ -0,0 +1,32 @@ +import type { ModelCommentEntity } from '#src/modules/model-comment/domain/model-comment.types.ts'; +import type { Paginated, PaginatedQueryParams } from '#src/shared/db/repository.port.ts'; +import type { TransactionContext } from '#src/shared/db/transaction.port.ts'; + +export interface ModelCommentRepository { + findById(id: string, viewerId?: string): Promise; + findByIdTx(ctx: TransactionContext, id: string): Promise; + + // Top-level comments of a model (parentId = null). + listTopLevel( + modelId: string, + params: PaginatedQueryParams, + viewerId?: string, + ): Promise>; + + // One page of a single parent's direct replies. + listReplies( + parentId: string, + params: PaginatedQueryParams, + viewerId?: string, + ): Promise>; + + // Batch total-reply counts (for nodes we embed but don't expand at the depth limit). + countRepliesByParent(parentIds: string[]): Promise>; + + insertTx(ctx: TransactionContext, entity: ModelCommentEntity): Promise; + updateContentTx(ctx: TransactionContext, id: string, content: string, at: Date): Promise; + softDeleteTx(ctx: TransactionContext, id: string, at: Date): Promise; + + addLikeTx(ctx: TransactionContext, commentId: string, userId: string): Promise; // false if already liked + removeLikeTx(ctx: TransactionContext, commentId: string, userId: string): Promise; // false if not liked +} diff --git a/apps/modeling-commons-backend/src/modules/model-comment/database/model-comment.repository.ts b/apps/modeling-commons-backend/src/modules/model-comment/database/model-comment.repository.ts new file mode 100644 index 00000000..4d805f0e --- /dev/null +++ b/apps/modeling-commons-backend/src/modules/model-comment/database/model-comment.repository.ts @@ -0,0 +1,160 @@ +import type { Prisma } from '#prisma/index'; +import { + modelCommentInclude, + type ModelCommentRecord, +} from '#src/modules/model-comment/database/model-comment.record.ts'; +import type { ModelCommentRepository } from '#src/modules/model-comment/database/model-comment.repository.port.ts'; +import type { ModelCommentEntity } from '#src/modules/model-comment/domain/model-comment.types.ts'; +import { resolveTransaction } from '#src/shared/db/prisma-transaction.manager.ts'; +import { paginate, type OrderBy, type PaginatedQueryParams, type Paginated } from '#src/shared/db/repository.port.ts'; +import type { TransactionContext } from '#src/shared/db/transaction.port.ts'; + +function toEntity(record: ModelCommentRecord): ModelCommentEntity { + const { user, _count, ...comment } = record; + return { + ...comment, + user: user ?? undefined, + likedByMe: _count.likes > 0, + }; +} + +// `sort` is a DTO-facing name (`likes`), not the Prisma column (`likesCount`); +// this is the one place that reconciles the two, and direction is fixed per +// field rather than caller-controlled (likes always desc, dates always asc +// for chronological thread reading). +function toOrderBy(orderBy: OrderBy): Prisma.ModelCommentOrderByWithRelationInput { + if (orderBy.field === 'likes') { + return { likesCount: 'desc' }; + } + return { [orderBy.field]: 'asc' }; +} + +export default function modelCommentRepository({ db }: Dependencies): ModelCommentRepository { + return { + async findById(id: string, viewerId?: string): Promise { + const record = await db.modelComment.findUnique({ + where: { id }, + include: modelCommentInclude(viewerId), + }); + return record ? toEntity(record) : undefined; + }, + + async findByIdTx(ctx: TransactionContext, id: string): Promise { + const client = resolveTransaction(ctx); + const record = await client.modelComment.findUnique({ + where: { id }, + include: modelCommentInclude(), + }); + return record ? toEntity(record) : undefined; + }, + + async listTopLevel( + modelId: string, + params: PaginatedQueryParams, + viewerId?: string, + ): Promise> { + const where = { modelId, parentId: null }; + const [count, records] = await Promise.all([ + db.modelComment.count({ where }), + db.modelComment.findMany({ + where, + include: modelCommentInclude(viewerId), + orderBy: toOrderBy(params.orderBy), + skip: params.offset, + take: params.limit, + }), + ]); + return paginate(records.map(toEntity), params, count); + }, + + async listReplies( + parentId: string, + params: PaginatedQueryParams, + viewerId?: string, + ): Promise> { + const where = { parentId }; + const [count, records] = await Promise.all([ + db.modelComment.count({ where }), + db.modelComment.findMany({ + where, + include: modelCommentInclude(viewerId), + orderBy: toOrderBy(params.orderBy), + skip: params.offset, + take: params.limit, + }), + ]); + return paginate(records.map(toEntity), params, count); + }, + + async countRepliesByParent(parentIds: string[]): Promise> { + const result = new Map(); + if (parentIds.length === 0) return result; + + const groups = await db.modelComment.groupBy({ + by: ['parentId'], + where: { parentId: { in: parentIds } }, + _count: { _all: true }, + }); + for (const group of groups) { + if (group.parentId) result.set(group.parentId, group._count._all); + } + return result; + }, + + async insertTx(ctx: TransactionContext, entity: ModelCommentEntity): Promise { + const client = resolveTransaction(ctx); + const { user: _user, likedByMe: _likedByMe, ...data } = entity; + await client.modelComment.create({ data }); + }, + + async updateContentTx( + ctx: TransactionContext, + id: string, + content: string, + at: Date, + ): Promise { + const client = resolveTransaction(ctx); + await client.modelComment.update({ + where: { id }, + data: { content, editedAt: at }, + }); + }, + + async softDeleteTx(ctx: TransactionContext, id: string, at: Date): Promise { + const client = resolveTransaction(ctx); + await client.modelComment.update({ + where: { id }, + data: { deletedAt: at, content: null }, + }); + }, + + async addLikeTx(ctx: TransactionContext, commentId: string, userId: string): Promise { + const client = resolveTransaction(ctx); + const existing = await client.modelCommentLike.findUnique({ + where: { modelCommentId_userId: { modelCommentId: commentId, userId } }, + }); + if (existing) return false; + + await client.modelCommentLike.create({ data: { modelCommentId: commentId, userId } }); + await client.modelComment.update({ + where: { id: commentId }, + data: { likesCount: { increment: 1 } }, + }); + return true; + }, + + async removeLikeTx(ctx: TransactionContext, commentId: string, userId: string): Promise { + const client = resolveTransaction(ctx); + const deleted = await client.modelCommentLike.deleteMany({ + where: { modelCommentId: commentId, userId }, + }); + if (deleted.count === 0) return false; + + await client.modelComment.updateMany({ + where: { id: commentId, likesCount: { gt: 0 } }, + data: { likesCount: { decrement: 1 } }, + }); + return true; + }, + }; +} diff --git a/apps/modeling-commons-backend/src/modules/model-comment/domain/model-comment.types.ts b/apps/modeling-commons-backend/src/modules/model-comment/domain/model-comment.types.ts index 21cf40a4..14d67b3a 100644 --- a/apps/modeling-commons-backend/src/modules/model-comment/domain/model-comment.types.ts +++ b/apps/modeling-commons-backend/src/modules/model-comment/domain/model-comment.types.ts @@ -1,6 +1,18 @@ import type { ModelComment } from '#prisma/index'; -export type ModelCommentEntity = ModelComment; +export type ModelCommentAuthor = { + id: string; + name: string | null; + image: string | null; +}; + +// `user`/`likedByMe` are populated by the repository's read paths (a hydrated +// include + a viewer-correlated like count); writes and mutation helpers +// operate on the bare `ModelComment` fields and leave them undefined. +export type ModelCommentEntity = ModelComment & { + user?: ModelCommentAuthor | null; + likedByMe?: boolean; +}; export type CreateCommentProps = { modelId: string; From 61241486059ec50714454d0b8013680bd8246460 Mon Sep 17 00:00:00 2001 From: Omar Ibrahim Date: Mon, 20 Jul 2026 13:55:28 -0500 Subject: [PATCH 18/51] feat(commons): add model-comment write service with audit and notifications - Add create/updateContent/softDelete/like/unlike, each transactional with model_comment.created/updated/deleted event audit; likes are not audited. - Post-commit, fire-and-forget email notification to a model's authors (minus the commenter, plus a differing parent author on replies) via the mail module. - Add model_comment.created/updated/deleted to the shared KnownEvents union. - Add src/modules/model-comment/index.ts to wire the module's DI types (repository, domain, service) into the global Dependencies interface. --- .../event/database/event.repository.port.ts | 3 + .../src/modules/model-comment/index.ts | 12 + .../model-comment.service.spec.ts | 308 ++++++++++++++++++ .../model-comment/model-comment.service.ts | 197 +++++++++++ 4 files changed, 520 insertions(+) create mode 100644 apps/modeling-commons-backend/src/modules/model-comment/index.ts create mode 100644 apps/modeling-commons-backend/src/modules/model-comment/model-comment.service.spec.ts create mode 100644 apps/modeling-commons-backend/src/modules/model-comment/model-comment.service.ts diff --git a/apps/modeling-commons-backend/src/modules/event/database/event.repository.port.ts b/apps/modeling-commons-backend/src/modules/event/database/event.repository.port.ts index 1a0d465d..4cec47c0 100644 --- a/apps/modeling-commons-backend/src/modules/event/database/event.repository.port.ts +++ b/apps/modeling-commons-backend/src/modules/event/database/event.repository.port.ts @@ -11,6 +11,9 @@ export type KnownEvents = | 'model_author.added' | 'model_author.ownership_transferred' | 'model_author.removed' + | 'model_comment.created' + | 'model_comment.deleted' + | 'model_comment.updated' | 'model_permission.granted' | 'model_permission.revoked' | 'model_version.created' diff --git a/apps/modeling-commons-backend/src/modules/model-comment/index.ts b/apps/modeling-commons-backend/src/modules/model-comment/index.ts new file mode 100644 index 00000000..61f52d3a --- /dev/null +++ b/apps/modeling-commons-backend/src/modules/model-comment/index.ts @@ -0,0 +1,12 @@ +import type { ModelCommentRepository } from '#src/modules/model-comment/database/model-comment.repository.port.ts'; +import type modelCommentDomain from '#src/modules/model-comment/domain/model-comment.domain.ts'; + +declare global { + export interface Dependencies { + modelCommentRepository: ModelCommentRepository; + modelCommentDomain: ReturnType; + modelCommentService: ReturnType< + typeof import('#src/modules/model-comment/model-comment.service.ts').default + >; + } +} diff --git a/apps/modeling-commons-backend/src/modules/model-comment/model-comment.service.spec.ts b/apps/modeling-commons-backend/src/modules/model-comment/model-comment.service.spec.ts new file mode 100644 index 00000000..c9089dbe --- /dev/null +++ b/apps/modeling-commons-backend/src/modules/model-comment/model-comment.service.spec.ts @@ -0,0 +1,308 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import makeModelCommentService from '#src/modules/model-comment/model-comment.service.ts'; +import modelCommentDomain from '#src/modules/model-comment/domain/model-comment.domain.ts'; +import { + CommentDeletedError, + CommentNotFoundError, + ParentCommentMismatchError, +} from '#src/modules/model-comment/domain/model-comment.errors.ts'; +import { ForbiddenException } from '#src/shared/exceptions/index.ts'; +import { mockTransactionManager } from '#src/shared/test/mock-transaction-manager.ts'; +import { mockModelCommentRepository } from '#src/modules/model-comment/database/model-comment.repository.mock.ts'; +import { mockEventRepository } from '#src/modules/event/database/event.repository.mock.ts'; +import { mockModelAuthorRepository } from '#src/modules/model-author/database/model-author.repository.mock.ts'; +import { mockUserRepository } from '#src/modules/user/database/user.repository.mock.ts'; +import type { ModelCommentEntity } from '#src/modules/model-comment/domain/model-comment.types.ts'; + +function makeComment(overrides: Partial = {}): ModelCommentEntity { + return { + id: 'comment-1', + legacyId: null, + parentId: null, + userId: 'author-1', + modelId: 'model-1', + versionNumber: null, + content: 'hello', + likesCount: 0, + createdAt: new Date('2026-01-01'), + updatedAt: new Date('2026-01-01'), + editedAt: null, + deletedAt: null, + ...overrides, + }; +} + +// Flushes the fire-and-forget notification promise chain started inside `create`. +async function flushMicrotasks(): Promise { + await new Promise((resolve) => setImmediate(resolve)); + await new Promise((resolve) => setImmediate(resolve)); +} + +describe('modelCommentService', () => { + const modelCommentRepository = mockModelCommentRepository(); + const eventRepository = mockEventRepository(); + const modelAuthorRepository = mockModelAuthorRepository(); + const userRepository = mockUserRepository(); + const mailService = { sendMail: vi.fn() }; + const mailDomain = { createNotificationEmail: vi.fn() }; + const transactionManager = mockTransactionManager(); + const domain = modelCommentDomain(); + const logger = { warn: vi.fn(), error: vi.fn(), info: vi.fn() }; + + const service = makeModelCommentService({ + transactionManager, + modelCommentRepository, + modelCommentDomain: domain, + eventRepository, + modelAuthorRepository, + userRepository, + mailService, + mailDomain, + logger, + } as never); + + beforeEach(() => { + vi.clearAllMocks(); + mailDomain.createNotificationEmail.mockResolvedValue({ + from: 'a@b.com', + to: 'x@y.com', + subject: 'subj', + html: '

', + text: 'p', + }); + mailService.sendMail.mockResolvedValue(undefined); + }); + + describe('create', () => { + it('writes a comment row and event, and notifies authors except the commenter', async () => { + modelAuthorRepository.findAllByModel.mockResolvedValue([ + { modelId: 'model-1', userId: 'author-1', role: 'owner' }, + { modelId: 'model-1', userId: 'commenter-1', role: 'contributor' }, + ]); + userRepository.findOneById.mockResolvedValue({ id: 'author-1', email: 'author@x.com', name: 'Author' }); + + const result = await service.create({ + modelId: 'model-1', + userId: 'commenter-1', + content: 'hello world', + }); + + expect(result.id).toBeTypeOf('string'); + expect(modelCommentRepository.insertTx).toHaveBeenCalledOnce(); + expect(eventRepository.insert).toHaveBeenCalledWith( + expect.anything(), + expect.objectContaining({ + type: 'model_comment.created', + actorId: 'commenter-1', + resourceType: 'model', + resourceId: 'model-1', + payload: expect.objectContaining({ parentId: null }), + }), + ); + + await flushMicrotasks(); + + expect(userRepository.findOneById).toHaveBeenCalledWith('author-1'); + expect(userRepository.findOneById).not.toHaveBeenCalledWith('commenter-1'); + expect(mailService.sendMail).toHaveBeenCalledOnce(); + }); + + it('drops versionNumber on a reply', async () => { + const parent = makeComment({ id: 'parent-1', modelId: 'model-1' }); + modelCommentRepository.findById.mockResolvedValue(parent); + modelAuthorRepository.findAllByModel.mockResolvedValue([]); + + await service.create({ + modelId: 'model-1', + userId: 'commenter-1', + parentId: 'parent-1', + versionNumber: 3, + content: 'a reply', + }); + + const insertedEntity = modelCommentRepository.insertTx.mock.calls[0]![1]; + expect(insertedEntity.versionNumber).toBeNull(); + expect(insertedEntity.parentId).toBe('parent-1'); + }); + + it('throws ParentCommentMismatchError when the parent belongs to a different model', async () => { + const parent = makeComment({ id: 'parent-1', modelId: 'other-model' }); + modelCommentRepository.findById.mockResolvedValue(parent); + + await expect( + service.create({ modelId: 'model-1', userId: 'commenter-1', parentId: 'parent-1', content: 'x' }), + ).rejects.toThrow(ParentCommentMismatchError); + }); + + it('throws CommentDeletedError when the parent is soft-deleted', async () => { + const parent = makeComment({ id: 'parent-1', modelId: 'model-1', deletedAt: new Date() }); + modelCommentRepository.findById.mockResolvedValue(parent); + + await expect( + service.create({ modelId: 'model-1', userId: 'commenter-1', parentId: 'parent-1', content: 'x' }), + ).rejects.toThrow(CommentDeletedError); + }); + + it('throws CommentNotFoundError when the parent does not exist', async () => { + modelCommentRepository.findById.mockResolvedValue(undefined); + + await expect( + service.create({ modelId: 'model-1', userId: 'commenter-1', parentId: 'missing', content: 'x' }), + ).rejects.toThrow(CommentNotFoundError); + }); + + it('does not throw out of create when notifying recipients rejects', async () => { + modelAuthorRepository.findAllByModel.mockResolvedValue([ + { modelId: 'model-1', userId: 'author-1', role: 'owner' }, + ]); + userRepository.findOneById.mockResolvedValue({ id: 'author-1', email: 'author@x.com', name: 'Author' }); + mailService.sendMail.mockRejectedValue(new Error('smtp down')); + + await expect( + service.create({ modelId: 'model-1', userId: 'commenter-1', content: 'hello' }), + ).resolves.toEqual(expect.objectContaining({ id: expect.any(String) })); + + await flushMicrotasks(); + + expect(logger.error).toHaveBeenCalled(); + }); + }); + + describe('updateContent', () => { + it('throws ForbiddenException when the caller is not the author', async () => { + modelCommentRepository.findById.mockResolvedValue(makeComment({ userId: 'author-1' })); + + await expect( + service.updateContent({ + modelId: 'model-1', + commentId: 'comment-1', + callerId: 'someone-else', + content: 'new content', + }), + ).rejects.toThrow(ForbiddenException); + }); + + it('sets editedAt and emits model_comment.updated when the author edits', async () => { + modelCommentRepository.findById.mockResolvedValue(makeComment({ userId: 'author-1' })); + + await service.updateContent({ + modelId: 'model-1', + commentId: 'comment-1', + callerId: 'author-1', + content: 'new content', + }); + + expect(modelCommentRepository.updateContentTx).toHaveBeenCalledWith( + expect.anything(), + 'comment-1', + 'new content', + expect.any(Date), + ); + expect(eventRepository.insert).toHaveBeenCalledWith( + expect.anything(), + expect.objectContaining({ type: 'model_comment.updated', actorId: 'author-1' }), + ); + }); + + it('throws CommentNotFoundError when the comment belongs to a different model', async () => { + modelCommentRepository.findById.mockResolvedValue(makeComment({ modelId: 'other-model' })); + + await expect( + service.updateContent({ + modelId: 'model-1', + commentId: 'comment-1', + callerId: 'author-1', + content: 'x', + }), + ).rejects.toThrow(CommentNotFoundError); + }); + }); + + describe('softDelete', () => { + it('lets the author delete their own comment with byAdmin:false', async () => { + modelCommentRepository.findById.mockResolvedValue(makeComment({ userId: 'author-1' })); + + await service.softDelete({ modelId: 'model-1', commentId: 'comment-1', caller: { id: 'author-1' } }); + + expect(modelCommentRepository.softDeleteTx).toHaveBeenCalledOnce(); + expect(eventRepository.insert).toHaveBeenCalledWith( + expect.anything(), + expect.objectContaining({ + type: 'model_comment.deleted', + payload: expect.objectContaining({ byAdmin: false }), + }), + ); + }); + + it('lets an admin delete someone else\'s comment with byAdmin:true', async () => { + modelCommentRepository.findById.mockResolvedValue(makeComment({ userId: 'author-1' })); + + await service.softDelete({ + modelId: 'model-1', + commentId: 'comment-1', + caller: { id: 'admin-1', systemRole: 'admin' }, + }); + + expect(eventRepository.insert).toHaveBeenCalledWith( + expect.anything(), + expect.objectContaining({ payload: expect.objectContaining({ byAdmin: true }) }), + ); + }); + + it('throws ForbiddenException for any other user', async () => { + modelCommentRepository.findById.mockResolvedValue(makeComment({ userId: 'author-1' })); + + await expect( + service.softDelete({ modelId: 'model-1', commentId: 'comment-1', caller: { id: 'random-user' } }), + ).rejects.toThrow(ForbiddenException); + }); + + it('throws CommentNotFoundError when the comment belongs to a different model', async () => { + modelCommentRepository.findById.mockResolvedValue(makeComment({ modelId: 'other-model' })); + + await expect( + service.softDelete({ modelId: 'model-1', commentId: 'comment-1', caller: { id: 'author-1' } }), + ).rejects.toThrow(CommentNotFoundError); + }); + }); + + describe('like/unlike', () => { + it('is idempotent across repeated likes and never writes an Event', async () => { + modelCommentRepository.findById.mockResolvedValue(makeComment()); + modelCommentRepository.addLikeTx.mockResolvedValueOnce(true).mockResolvedValueOnce(false); + + await service.like({ modelId: 'model-1', commentId: 'comment-1', userId: 'liker-1' }); + await service.like({ modelId: 'model-1', commentId: 'comment-1', userId: 'liker-1' }); + + expect(modelCommentRepository.addLikeTx).toHaveBeenCalledTimes(2); + expect(eventRepository.insert).not.toHaveBeenCalled(); + }); + + it('no-ops when unliking a comment that was not liked', async () => { + modelCommentRepository.findById.mockResolvedValue(makeComment()); + modelCommentRepository.removeLikeTx.mockResolvedValue(false); + + await expect( + service.unlike({ modelId: 'model-1', commentId: 'comment-1', userId: 'liker-1' }), + ).resolves.toBeUndefined(); + + expect(eventRepository.insert).not.toHaveBeenCalled(); + }); + + it('throws CommentNotFoundError on like when the comment belongs to a different model', async () => { + modelCommentRepository.findById.mockResolvedValue(makeComment({ modelId: 'other-model' })); + + await expect( + service.like({ modelId: 'model-1', commentId: 'comment-1', userId: 'liker-1' }), + ).rejects.toThrow(CommentNotFoundError); + }); + + it('throws CommentNotFoundError on unlike when the comment belongs to a different model', async () => { + modelCommentRepository.findById.mockResolvedValue(makeComment({ modelId: 'other-model' })); + + await expect( + service.unlike({ modelId: 'model-1', commentId: 'comment-1', userId: 'liker-1' }), + ).rejects.toThrow(CommentNotFoundError); + }); + }); +}); diff --git a/apps/modeling-commons-backend/src/modules/model-comment/model-comment.service.ts b/apps/modeling-commons-backend/src/modules/model-comment/model-comment.service.ts new file mode 100644 index 00000000..c8cb1eae --- /dev/null +++ b/apps/modeling-commons-backend/src/modules/model-comment/model-comment.service.ts @@ -0,0 +1,197 @@ +import env from '#src/config/env.ts'; +import { CommentNotFoundError } from '#src/modules/model-comment/domain/model-comment.errors.ts'; +import type { + CommentAuthCaller, + ModelCommentEntity, +} from '#src/modules/model-comment/domain/model-comment.types.ts'; + +export type CreateCommentInput = { + modelId: string; + userId: string; + parentId?: string; + versionNumber?: number; + content: string; +}; + +export type UpdateCommentContentInput = { + modelId: string; + commentId: string; + callerId: string; + content: string; +}; + +export type SoftDeleteCommentInput = { + modelId: string; + commentId: string; + caller: CommentAuthCaller; +}; + +export type CommentLikeInput = { + modelId: string; + commentId: string; + userId: string; +}; + +export default function makeModelCommentService({ + transactionManager, + modelCommentRepository, + modelCommentDomain, + eventRepository, + modelAuthorRepository, + userRepository, + mailService, + mailDomain, + logger, +}: Dependencies) { + // No dedicated "new comment" template yet (plan §15 item 5 is polish); the + // unsubscribe link is a placeholder until a real preferences endpoint exists. + async function notifyOnNewComment(entity: ModelCommentEntity, parent?: ModelCommentEntity) { + try { + const authors = await modelAuthorRepository.findAllByModel(entity.modelId); + const recipientIds = new Set(authors.map((author) => author.userId)); + if (parent?.userId && parent.userId !== entity.userId) { + recipientIds.add(parent.userId); + } + if (entity.userId) { + recipientIds.delete(entity.userId); + } + + const results = await Promise.allSettled( + Array.from(recipientIds).map(async (recipientId) => { + const recipient = await userRepository.findOneById(recipientId); + if (!recipient?.email) return; + + const content = await mailDomain.createNotificationEmail( + recipient.email, + recipient.name ?? 'there', + 'Someone commented on a model you are involved with.', + `${env.product.website}/unsubscribe`, + ); + // mailDomain returns a plain { from, to, subject, html, text } object; mailService's + // param type comes straight from nodemailer's Mail class, which no other caller in + // this codebase satisfies structurally either — cast rather than widen that signature here. + await mailService.sendMail(content as never); + }), + ); + + for (const result of results) { + if (result.status === 'rejected') { + logger.error({ + name: 'ModelCommentService', + message: 'Failed to notify a comment recipient', + error: result.reason, + }); + } + } + } catch (error) { + logger.error({ + name: 'ModelCommentService', + message: 'Failed to notify comment authors', + error, + }); + } + } + + async function loadForModel(modelId: string, commentId: string): Promise { + const comment = await modelCommentRepository.findById(commentId); + if (!comment || comment.modelId !== modelId) { + throw new CommentNotFoundError(commentId); + } + return comment; + } + + return { + async create({ + modelId, + userId, + parentId, + versionNumber, + content, + }: CreateCommentInput): Promise<{ id: string }> { + let parent: ModelCommentEntity | undefined; + if (parentId) { + parent = await modelCommentRepository.findById(parentId); + if (!parent) throw new CommentNotFoundError(parentId); + modelCommentDomain.assertNotDeleted(parent); + modelCommentDomain.assertParentMatchesModel(parent, modelId); + } + + const entity = modelCommentDomain.createComment({ + modelId, + userId, + parentId, + versionNumber: parentId ? undefined : versionNumber, + content, + }); + + await transactionManager.run(async (ctx) => { + await modelCommentRepository.insertTx(ctx, entity); + await eventRepository.insert(ctx, { + type: 'model_comment.created', + actorId: userId, + resourceType: 'model', + resourceId: modelId, + payload: { commentId: entity.id, parentId: parentId ?? null }, + }); + }); + + void notifyOnNewComment(entity, parent); + + return { id: entity.id }; + }, + + async updateContent({ + modelId, + commentId, + callerId, + content, + }: UpdateCommentContentInput): Promise { + const comment = await loadForModel(modelId, commentId); + modelCommentDomain.assertNotDeleted(comment); + modelCommentDomain.assertCanEdit(comment, callerId); + + const now = new Date(); + await transactionManager.run(async (ctx) => { + await modelCommentRepository.updateContentTx(ctx, commentId, content, now); + await eventRepository.insert(ctx, { + type: 'model_comment.updated', + actorId: callerId, + resourceType: 'model', + resourceId: modelId, + payload: { commentId }, + }); + }); + }, + + async softDelete({ modelId, commentId, caller }: SoftDeleteCommentInput): Promise { + const comment = await loadForModel(modelId, commentId); + modelCommentDomain.assertNotDeleted(comment); + modelCommentDomain.assertCanDelete(comment, caller); + + const now = new Date(); + await transactionManager.run(async (ctx) => { + await modelCommentRepository.softDeleteTx(ctx, commentId, now); + await eventRepository.insert(ctx, { + type: 'model_comment.deleted', + actorId: caller.id, + resourceType: 'model', + resourceId: modelId, + payload: { commentId, byAdmin: caller.systemRole === 'admin' }, + }); + }); + }, + + async like({ modelId, commentId, userId }: CommentLikeInput): Promise { + const comment = await loadForModel(modelId, commentId); + modelCommentDomain.assertNotDeleted(comment); + + await transactionManager.run((ctx) => modelCommentRepository.addLikeTx(ctx, commentId, userId)); + }, + + async unlike({ modelId, commentId, userId }: CommentLikeInput): Promise { + await loadForModel(modelId, commentId); + + await transactionManager.run((ctx) => modelCommentRepository.removeLikeTx(ctx, commentId, userId)); + }, + }; +} From 48e0c12f89729a06416baf71d44d2d721ea7e652 Mon Sep 17 00:00:00 2001 From: Omar Ibrahim Date: Mon, 20 Jul 2026 14:04:28 -0500 Subject: [PATCH 19/51] Feat(commons): add model-comment mapper and bounded-tree read queries --- .../model-comment.mapper.spec.ts | 177 +++++++++++++ .../model-comment/model-comment.mapper.ts | 62 +++++ .../queries/get-comment.query.spec.ts | 145 +++++++++++ .../queries/get-comment.query.ts | 37 +++ .../queries/list-comments.query.spec.ts | 240 ++++++++++++++++++ .../queries/list-comments.query.ts | 121 +++++++++ 6 files changed, 782 insertions(+) create mode 100644 apps/modeling-commons-backend/src/modules/model-comment/model-comment.mapper.spec.ts create mode 100644 apps/modeling-commons-backend/src/modules/model-comment/model-comment.mapper.ts create mode 100644 apps/modeling-commons-backend/src/modules/model-comment/queries/get-comment.query.spec.ts create mode 100644 apps/modeling-commons-backend/src/modules/model-comment/queries/get-comment.query.ts create mode 100644 apps/modeling-commons-backend/src/modules/model-comment/queries/list-comments.query.spec.ts create mode 100644 apps/modeling-commons-backend/src/modules/model-comment/queries/list-comments.query.ts diff --git a/apps/modeling-commons-backend/src/modules/model-comment/model-comment.mapper.spec.ts b/apps/modeling-commons-backend/src/modules/model-comment/model-comment.mapper.spec.ts new file mode 100644 index 00000000..89af2dc2 --- /dev/null +++ b/apps/modeling-commons-backend/src/modules/model-comment/model-comment.mapper.spec.ts @@ -0,0 +1,177 @@ +import { describe, it, expect } from 'vitest'; +import modelCommentMapper from '#src/modules/model-comment/model-comment.mapper.ts'; +import type { ModelCommentEntity } from '#src/modules/model-comment/domain/model-comment.types.ts'; + +const mapper = modelCommentMapper(); + +function makeEntity(overrides: Partial = {}): ModelCommentEntity { + return { + id: 'comment-1', + legacyId: null, + parentId: null, + userId: 'user-1', + modelId: 'model-1', + versionNumber: null, + content: 'hello world', + likesCount: 3, + createdAt: new Date('2026-01-01T00:00:00.000Z'), + updatedAt: new Date('2026-01-01T00:00:00.000Z'), + editedAt: null, + deletedAt: null, + user: { id: 'user-1', name: 'Alice', image: 'https://cdn/alice.png' }, + likedByMe: false, + ...overrides, + }; +} + +describe('modelCommentMapper', () => { + describe('toResponse', () => { + it('maps a well-formed comment with an anonymous viewer', () => { + const result = mapper.toResponse(makeEntity()); + + expect(result).toEqual({ + id: 'comment-1', + modelId: 'model-1', + parentId: undefined, + versionNumber: undefined, + legacyId: undefined, + author: { id: 'user-1', name: 'Alice', image: 'https://cdn/alice.png' }, + content: 'hello world', + createdAt: '2026-01-01T00:00:00.000Z', + edited: undefined, + deleted: undefined, + likes: 3, + likedByMe: undefined, + permissions: undefined, + }); + }); + + it('projects a tombstone: placeholder author, [deleted] content, deleted:true, no permissions', () => { + const entity = makeEntity({ + content: null, + deletedAt: new Date('2026-01-02T00:00:00.000Z'), + user: null, + }); + + const result = mapper.toResponse(entity, { viewerId: 'user-1', viewerRole: 'admin' }); + + expect(result.content).toBe('[deleted]'); + expect(result.author).toEqual({ id: '', name: '[deleted]', image: '' }); + expect(result.deleted).toBe(true); + expect(result.permissions).toBeUndefined(); + expect(result.likes).toBe(3); + expect(result.likedByMe).toBe(false); + }); + + it('projects a tombstone even if the user relation is somehow still populated', () => { + const entity = makeEntity({ content: null, deletedAt: new Date() }); + + const result = mapper.toResponse(entity); + + expect(result.author).toEqual({ id: '', name: '[deleted]', image: '' }); + expect(result.content).toBe('[deleted]'); + }); + + it('falls back to a placeholder author when the user was hard-deleted (userId SetNull)', () => { + const entity = makeEntity({ user: null }); + + const result = mapper.toResponse(entity); + + expect(result.author).toEqual({ id: '', name: '[deleted]', image: '' }); + }); + + it('substitutes [deleted]/"" for a null name/image on a live user', () => { + const entity = makeEntity({ user: { id: 'user-1', name: null, image: null } }); + + const result = mapper.toResponse(entity); + + expect(result.author).toEqual({ id: 'user-1', name: '[deleted]', image: '' }); + }); + + it('sets edited:true only when editedAt is set', () => { + const edited = mapper.toResponse(makeEntity({ editedAt: new Date('2026-01-03T00:00:00.000Z') })); + expect(edited.edited).toBe(true); + + const notEdited = mapper.toResponse(makeEntity({ editedAt: null })); + expect(notEdited.edited).toBeUndefined(); + }); + + it('does not derive edited from updatedAt (a likesCount bump alone must not flip it)', () => { + const entity = makeEntity({ + editedAt: null, + updatedAt: new Date('2026-05-01T00:00:00.000Z'), + likesCount: 42, + }); + + const result = mapper.toResponse(entity); + + expect(result.edited).toBeUndefined(); + }); + + it('never sets edited:true for a deleted comment, even if editedAt was set before deletion', () => { + const entity = makeEntity({ + content: null, + deletedAt: new Date(), + editedAt: new Date('2026-01-02T00:00:00.000Z'), + }); + + const result = mapper.toResponse(entity); + + expect(result.edited).toBeUndefined(); + expect(result.deleted).toBe(true); + }); + + it('omits permissions entirely for an anonymous viewer (viewerId undefined)', () => { + const result = mapper.toResponse(makeEntity(), {}); + expect(result.permissions).toBeUndefined(); + }); + + it('reports canEdit/canDelete=true for the author', () => { + const result = mapper.toResponse(makeEntity({ userId: 'user-1' }), { viewerId: 'user-1' }); + expect(result.permissions).toEqual({ canEdit: true, canDelete: true }); + }); + + it('reports canEdit=false/canDelete=true for an admin viewer who is not the author', () => { + const result = mapper.toResponse(makeEntity({ userId: 'user-1' }), { + viewerId: 'admin-1', + viewerRole: 'admin', + }); + expect(result.permissions).toEqual({ canEdit: false, canDelete: true }); + }); + + it('reports canEdit=false/canDelete=false for a viewer who is neither author nor admin', () => { + const result = mapper.toResponse(makeEntity({ userId: 'user-1' }), { + viewerId: 'user-2', + viewerRole: 'user', + }); + expect(result.permissions).toEqual({ canEdit: false, canDelete: false }); + }); + + it('omits permissions for a deleted comment even with a viewerId present', () => { + const entity = makeEntity({ content: null, deletedAt: new Date() }); + const result = mapper.toResponse(entity, { viewerId: 'user-1' }); + expect(result.permissions).toBeUndefined(); + }); + + it('includes likedByMe only when a viewerId is present', () => { + const entity = makeEntity({ likedByMe: true }); + + expect(mapper.toResponse(entity).likedByMe).toBeUndefined(); + expect(mapper.toResponse(entity, { viewerId: 'user-1' }).likedByMe).toBe(true); + }); + + it('carries parentId, versionNumber, and legacyId through when present', () => { + const entity = makeEntity({ parentId: 'parent-1', versionNumber: 2, legacyId: 99 }); + const result = mapper.toResponse(entity); + + expect(result.parentId).toBe('parent-1'); + expect(result.versionNumber).toBe(2); + expect(result.legacyId).toBe(99); + }); + + it('never attaches a replies field — that is the query layer’s job', () => { + const result = mapper.toResponse(makeEntity()); + expect(result.replies).toBeUndefined(); + }); + }); +}); diff --git a/apps/modeling-commons-backend/src/modules/model-comment/model-comment.mapper.ts b/apps/modeling-commons-backend/src/modules/model-comment/model-comment.mapper.ts new file mode 100644 index 00000000..73999adf --- /dev/null +++ b/apps/modeling-commons-backend/src/modules/model-comment/model-comment.mapper.ts @@ -0,0 +1,62 @@ +import type { ModelCommentEntity } from '#src/modules/model-comment/domain/model-comment.types.ts'; +import type { CommentResponseDto } from '#src/modules/model-comment/dtos/comment.response.dto.ts'; + +export type CommentResponseCtx = { + viewerId?: string; + viewerRole?: string; +}; + +export type ModelCommentMapper = { + toResponse: (entity: ModelCommentEntity, ctx?: CommentResponseCtx) => CommentResponseDto; +}; + +// Not a `Mapper` (create-mapper.ts) — `toResponse` +// here takes a viewer context (permissions/likedByMe are viewer-relative), +// which that shared interface doesn't model. `replies` is deliberately never +// set: bounded-tree embedding is the query layer's job (list-comments/get-comment). +export default function modelCommentMapper(): ModelCommentMapper { + return { + toResponse(entity: ModelCommentEntity, ctx: CommentResponseCtx = {}): CommentResponseDto { + const deleted = entity.deletedAt !== null; + + const author = + deleted || !entity.user + ? { id: '', name: '[deleted]', image: '' } + : { + id: entity.user.id, + name: entity.user.name ?? '[deleted]', + image: entity.user.image ?? '', + }; + + const permissions = + !deleted && ctx.viewerId + ? { + canEdit: ctx.viewerId === entity.userId, + canDelete: ctx.viewerId === entity.userId || ctx.viewerRole === 'admin', + } + : undefined; + + return { + id: entity.id, + modelId: entity.modelId, + parentId: entity.parentId ?? undefined, + versionNumber: entity.versionNumber ?? undefined, + legacyId: entity.legacyId ?? undefined, + author, + content: deleted ? '[deleted]' : (entity.content ?? ''), + createdAt: entity.createdAt.toISOString(), + edited: !deleted && entity.editedAt !== null ? true : undefined, + deleted: deleted ? true : undefined, + likes: entity.likesCount, + likedByMe: ctx.viewerId ? entity.likedByMe : undefined, + permissions, + }; + }, + }; +} + +declare global { + interface Dependencies { + modelCommentMapper: ModelCommentMapper; + } +} diff --git a/apps/modeling-commons-backend/src/modules/model-comment/queries/get-comment.query.spec.ts b/apps/modeling-commons-backend/src/modules/model-comment/queries/get-comment.query.spec.ts new file mode 100644 index 00000000..4cf38e06 --- /dev/null +++ b/apps/modeling-commons-backend/src/modules/model-comment/queries/get-comment.query.spec.ts @@ -0,0 +1,145 @@ +import { describe, it, expect } from 'vitest'; +import makeGetCommentQuery from '#src/modules/model-comment/queries/get-comment.query.ts'; +import modelCommentMapper from '#src/modules/model-comment/model-comment.mapper.ts'; +import { mockModelCommentRepository } from '#src/modules/model-comment/database/model-comment.repository.mock.ts'; +import { CommentNotFoundError } from '#src/modules/model-comment/domain/model-comment.errors.ts'; +import type { ModelCommentEntity } from '#src/modules/model-comment/domain/model-comment.types.ts'; +import type { Paginated } from '#src/shared/db/repository.port.ts'; + +function makeEntity(overrides: Partial = {}): ModelCommentEntity { + return { + id: 'entity-id', + legacyId: null, + parentId: null, + userId: 'user-1', + modelId: 'model-1', + versionNumber: null, + content: 'hello', + likesCount: 0, + createdAt: new Date('2026-01-01T00:00:00.000Z'), + updatedAt: new Date('2026-01-01T00:00:00.000Z'), + editedAt: null, + deletedAt: null, + user: { id: 'user-1', name: 'Alice', image: null }, + likedByMe: false, + ...overrides, + }; +} + +function page(data: Array, count: number, limit = 20, pageNum = 0): Paginated { + return { data, count, limit, page: pageNum }; +} + +function buildQuery() { + const modelCommentRepository = mockModelCommentRepository(); + const mapper = modelCommentMapper(); + const query = makeGetCommentQuery({ + modelCommentRepository, + modelCommentMapper: mapper, + } as never); + return { query, modelCommentRepository }; +} + +describe('getCommentQuery', () => { + it('throws CommentNotFoundError when the comment does not exist', async () => { + const { query, modelCommentRepository } = buildQuery(); + modelCommentRepository.findById.mockResolvedValue(undefined); + + await expect(query.execute('missing', {})).rejects.toThrow(CommentNotFoundError); + }); + + it('re-roots the target comment with its own reply page (default limit 20)', async () => { + const { query, modelCommentRepository } = buildQuery(); + const target = makeEntity({ id: 'target-1' }); + const reply = makeEntity({ id: 'reply-1', parentId: 'target-1' }); + + modelCommentRepository.findById.mockResolvedValue(target); + modelCommentRepository.listReplies.mockImplementation(async (parentId: string) => + parentId === 'target-1' ? page([reply], 1, 20, 0) : page([], 0), + ); + + const result = await query.execute('target-1', {}); + + expect(result.id).toBe('target-1'); + expect(result.replies!.count).toBe(1); + expect(result.replies!.data[0]!.id).toBe('reply-1'); + expect(modelCommentRepository.listReplies).toHaveBeenCalledWith( + 'target-1', + expect.objectContaining({ limit: 20, page: 0 }), + undefined, + ); + }); + + it('honors ?page=N (and a caller-supplied limit) on the root comment’s own reply page', async () => { + const { query, modelCommentRepository } = buildQuery(); + const target = makeEntity({ id: 'target-1' }); + + modelCommentRepository.findById.mockResolvedValue(target); + modelCommentRepository.listReplies.mockResolvedValue(page([], 0, 5, 2)); + + await query.execute('target-1', { page: 2, limit: 5 }); + + expect(modelCommentRepository.listReplies).toHaveBeenCalledWith( + 'target-1', + expect.objectContaining({ limit: 5, page: 2, offset: 10 }), + undefined, + ); + }); + + it('maps sort=likes to a descending likesCount orderBy for the root reply page', async () => { + const { query, modelCommentRepository } = buildQuery(); + modelCommentRepository.findById.mockResolvedValue(makeEntity({ id: 'target-1' })); + modelCommentRepository.listReplies.mockResolvedValue(page([], 0)); + + await query.execute('target-1', { sort: 'likes' }); + + expect(modelCommentRepository.listReplies).toHaveBeenCalledWith( + 'target-1', + expect.objectContaining({ orderBy: { field: 'likes', param: 'desc' } }), + undefined, + ); + }); + + it('passes viewerId through to findById/listReplies and the mapper', async () => { + const { query, modelCommentRepository } = buildQuery(); + const target = makeEntity({ id: 'target-1', userId: 'user-1' }); + modelCommentRepository.findById.mockResolvedValue(target); + modelCommentRepository.listReplies.mockResolvedValue(page([], 0)); + + const result = await query.execute('target-1', {}, { viewerId: 'user-1' }); + + expect(modelCommentRepository.findById).toHaveBeenCalledWith('target-1', 'user-1'); + expect(modelCommentRepository.listReplies).toHaveBeenCalledWith( + 'target-1', + expect.anything(), + 'user-1', + ); + expect(result.permissions).toEqual({ canEdit: true, canDelete: true }); + }); + + it('bounds deeper levels to the standard 2-per-level embed even though the root page uses a bigger limit', async () => { + const { query, modelCommentRepository } = buildQuery(); + const target = makeEntity({ id: 'target-1' }); + const child = makeEntity({ id: 'child-1', parentId: 'target-1' }); + const grandchild1 = makeEntity({ id: 'gc-1', parentId: 'child-1' }); + const grandchild2 = makeEntity({ id: 'gc-2', parentId: 'child-1' }); + + modelCommentRepository.findById.mockResolvedValue(target); + modelCommentRepository.listReplies.mockImplementation(async (parentId: string) => { + if (parentId === 'target-1') return page([child], 1, 20, 0); + if (parentId === 'child-1') return page([grandchild1, grandchild2], 3, 2, 0); // 3 total, 2 embedded + return page([], 0); + }); + + const result = await query.execute('target-1', {}); + + const childDto = result.replies!.data[0]!; + expect(childDto.replies!.count).toBe(3); + expect(childDto.replies!.data.map((c) => c.id)).toEqual(['gc-1', 'gc-2']); + expect(modelCommentRepository.listReplies).toHaveBeenCalledWith( + 'child-1', + expect.objectContaining({ limit: 2 }), + undefined, + ); + }); +}); diff --git a/apps/modeling-commons-backend/src/modules/model-comment/queries/get-comment.query.ts b/apps/modeling-commons-backend/src/modules/model-comment/queries/get-comment.query.ts new file mode 100644 index 00000000..85722fcd --- /dev/null +++ b/apps/modeling-commons-backend/src/modules/model-comment/queries/get-comment.query.ts @@ -0,0 +1,37 @@ +import { CommentNotFoundError } from '#src/modules/model-comment/domain/model-comment.errors.ts'; +import type { CommentResponseDto } from '#src/modules/model-comment/dtos/comment.response.dto.ts'; +import type { ListCommentsQueryDto } from '#src/modules/model-comment/dtos/list-comments.query.dto.ts'; +import type { CommentResponseCtx } from '#src/modules/model-comment/model-comment.mapper.ts'; +import { + commentOrderBy, + expandCommentTree, +} from '#src/modules/model-comment/queries/list-comments.query.ts'; +import { paginatedQueryBase } from '#src/shared/ddd/query.base.ts'; + +export default function makeGetCommentQuery({ modelCommentRepository, modelCommentMapper }: Dependencies) { + return { + async execute( + commentId: string, + query: ListCommentsQueryDto, + ctx: CommentResponseCtx = {}, + ): Promise { + const target = await modelCommentRepository.findById(commentId, ctx.viewerId); + if (!target) throw new CommentNotFoundError(commentId); + + // The re-rooted comment's own reply page honors the caller's + // `page`/`limit`/`sort` (default limit 20, per plan §8); deeper levels + // fall back to the fixed bounded-embed defaults inside `expandCommentTree`. + // Spread `query` rather than naming `limit`/`page` explicitly: `paginatedQueryBase` + // only fills in its defaults for an *absent* key, not an explicit `undefined` value. + const repliesParams = paginatedQueryBase({ ...query, orderBy: commentOrderBy(query.sort) }); + + return expandCommentTree( + { modelCommentRepository, modelCommentMapper }, + target, + 0, + ctx, + repliesParams, + ); + }, + }; +} diff --git a/apps/modeling-commons-backend/src/modules/model-comment/queries/list-comments.query.spec.ts b/apps/modeling-commons-backend/src/modules/model-comment/queries/list-comments.query.spec.ts new file mode 100644 index 00000000..255bbe4e --- /dev/null +++ b/apps/modeling-commons-backend/src/modules/model-comment/queries/list-comments.query.spec.ts @@ -0,0 +1,240 @@ +import { describe, it, expect } from 'vitest'; +import makeListCommentsQuery from '#src/modules/model-comment/queries/list-comments.query.ts'; +import modelCommentMapper from '#src/modules/model-comment/model-comment.mapper.ts'; +import { mockModelCommentRepository } from '#src/modules/model-comment/database/model-comment.repository.mock.ts'; +import type { ModelCommentEntity } from '#src/modules/model-comment/domain/model-comment.types.ts'; +import type { Paginated } from '#src/shared/db/repository.port.ts'; + +function makeEntity(overrides: Partial = {}): ModelCommentEntity { + return { + id: 'entity-id', + legacyId: null, + parentId: null, + userId: 'user-1', + modelId: 'model-1', + versionNumber: null, + content: 'hello', + likesCount: 0, + createdAt: new Date('2026-01-01T00:00:00.000Z'), + updatedAt: new Date('2026-01-01T00:00:00.000Z'), + editedAt: null, + deletedAt: null, + user: { id: 'user-1', name: 'Alice', image: null }, + likedByMe: false, + ...overrides, + }; +} + +function page(data: Array, count: number, limit = 2, pageNum = 0): Paginated { + return { data, count, limit, page: pageNum }; +} + +function buildQuery() { + const modelCommentRepository = mockModelCommentRepository(); + const mapper = modelCommentMapper(); + const query = makeListCommentsQuery({ + modelCommentRepository, + modelCommentMapper: mapper, + } as never); + return { query, modelCommentRepository }; +} + +describe('listCommentsQuery', () => { + it('returns a paginated page of top-level comments using listTopLevel', async () => { + const { query, modelCommentRepository } = buildQuery(); + const root = makeEntity({ id: 'root-1' }); + + modelCommentRepository.listTopLevel.mockResolvedValue(page([root], 1, 20, 0)); + modelCommentRepository.listReplies.mockResolvedValue(page([], 0)); + + const result = await query.execute('model-1', {}); + + expect(result.count).toBe(1); + expect(result.data).toHaveLength(1); + expect(result.data[0]!.id).toBe('root-1'); + expect(result.data[0]!.replies).toBeUndefined(); + expect(modelCommentRepository.listTopLevel).toHaveBeenCalledWith( + 'model-1', + expect.objectContaining({ limit: 20, page: 0, orderBy: { field: 'createdAt', param: 'asc' } }), + undefined, + ); + }); + + it('maps sort=likes to a descending likesCount orderBy', async () => { + const { query, modelCommentRepository } = buildQuery(); + modelCommentRepository.listTopLevel.mockResolvedValue(page([], 0)); + + await query.execute('model-1', { sort: 'likes' }); + + expect(modelCommentRepository.listTopLevel).toHaveBeenCalledWith( + 'model-1', + expect.objectContaining({ orderBy: { field: 'likes', param: 'desc' } }), + undefined, + ); + }); + + it('passes viewerId through to listTopLevel/listReplies and the mapper (permissions/likedByMe)', async () => { + const { query, modelCommentRepository } = buildQuery(); + const root = makeEntity({ id: 'root-1', userId: 'user-1', likedByMe: true }); + + modelCommentRepository.listTopLevel.mockResolvedValue(page([root], 1)); + modelCommentRepository.listReplies.mockResolvedValue(page([], 0)); + + const result = await query.execute('model-1', {}, { viewerId: 'user-1', viewerRole: 'user' }); + + expect(modelCommentRepository.listTopLevel).toHaveBeenCalledWith( + 'model-1', + expect.anything(), + 'user-1', + ); + expect(result.data[0]!.permissions).toEqual({ canEdit: true, canDelete: true }); + expect(result.data[0]!.likedByMe).toBe(true); + }); + + it('embeds at most 2 replies per node while reporting the true total count', async () => { + const { query, modelCommentRepository } = buildQuery(); + const root = makeEntity({ id: 'root-1' }); + const c1 = makeEntity({ id: 'c1', parentId: 'root-1' }); + const c2 = makeEntity({ id: 'c2', parentId: 'root-1' }); + + modelCommentRepository.listTopLevel.mockResolvedValue(page([root], 1)); + modelCommentRepository.listReplies.mockImplementation(async (parentId: string) => { + if (parentId === 'root-1') return page([c1, c2], 5, 2, 0); // 5 total, only 2 embedded + return page([], 0); + }); + + const result = await query.execute('model-1', {}); + + const rootDto = result.data[0]!; + expect(rootDto.replies).toBeDefined(); + expect(rootDto.replies!.count).toBe(5); + expect(rootDto.replies!.data).toHaveLength(2); + expect(rootDto.replies!.data.map((c) => c.id)).toEqual(['c1', 'c2']); + }); + + it('stops recursing at 3 levels: the deepest expanded level uses countRepliesByParent instead of listReplies', async () => { + const { query, modelCommentRepository } = buildQuery(); + const root = makeEntity({ id: 'root' }); + const depth1 = makeEntity({ id: 'd1', parentId: 'root' }); + const depth2 = makeEntity({ id: 'd2', parentId: 'd1' }); + const depth3 = makeEntity({ id: 'd3', parentId: 'd2' }); + + modelCommentRepository.listTopLevel.mockResolvedValue(page([root], 1)); + modelCommentRepository.listReplies.mockImplementation(async (parentId: string) => { + if (parentId === 'root') return page([depth1], 1); + if (parentId === 'd1') return page([depth2], 1); + if (parentId === 'd2') return page([depth3], 1); + return page([], 0); // depth3's own replies must never be fetched via listReplies + }); + modelCommentRepository.countRepliesByParent.mockResolvedValue(new Map([['d3', 7]])); + + const result = await query.execute('model-1', {}); + + const rootDto = result.data[0]!; + const d1Dto = rootDto.replies!.data[0]!; + const d2Dto = d1Dto.replies!.data[0]!; + const d3Dto = d2Dto.replies!.data[0]!; + + expect(d3Dto.id).toBe('d3'); + expect(d3Dto.replies).toEqual({ count: 7, limit: 2, page: 0, data: [] }); + expect(modelCommentRepository.countRepliesByParent).toHaveBeenCalledWith(['d3']); + // listReplies must not have been asked for d3's children (only 3 calls: root, d1, d2) + expect(modelCommentRepository.listReplies).toHaveBeenCalledTimes(3); + }); + + it('drops a childless deleted top-level comment from the page', async () => { + const { query, modelCommentRepository } = buildQuery(); + const live = makeEntity({ id: 'live-root' }); + const tombstone = makeEntity({ id: 'dead-root', content: null, deletedAt: new Date() }); + + modelCommentRepository.listTopLevel.mockResolvedValue(page([live, tombstone], 2)); + modelCommentRepository.listReplies.mockResolvedValue(page([], 0)); + + const result = await query.execute('model-1', {}); + + expect(result.data.map((c) => c.id)).toEqual(['live-root']); + expect(result.count).toBe(2); // raw total is untouched by the app-level filter + }); + + it('keeps a deleted top-level comment that still has replies', async () => { + const { query, modelCommentRepository } = buildQuery(); + const tombstone = makeEntity({ id: 'dead-root', content: null, deletedAt: new Date() }); + const reply = makeEntity({ id: 'reply-1', parentId: 'dead-root' }); + + modelCommentRepository.listTopLevel.mockResolvedValue(page([tombstone], 1)); + modelCommentRepository.listReplies.mockImplementation(async (parentId: string) => + parentId === 'dead-root' ? page([reply], 1) : page([], 0), + ); + + const result = await query.execute('model-1', {}); + + expect(result.data).toHaveLength(1); + expect(result.data[0]!.id).toBe('dead-root'); + expect(result.data[0]!.deleted).toBe(true); + expect(result.data[0]!.content).toBe('[deleted]'); + expect(result.data[0]!.replies!.count).toBe(1); + }); + + it('filters tombstones at the deepest (count-only) level too: childless dropped, has-replies kept', async () => { + const { query, modelCommentRepository } = buildQuery(); + const root = makeEntity({ id: 'root' }); + const depth1 = makeEntity({ id: 'd1', parentId: 'root' }); + const depth2 = makeEntity({ id: 'd2', parentId: 'd1' }); + const deadChildless = makeEntity({ + id: 'e-dead-childless', + parentId: 'd2', + content: null, + deletedAt: new Date(), + }); + const deadWithReplies = makeEntity({ + id: 'e-dead-with-replies', + parentId: 'd2', + content: null, + deletedAt: new Date(), + }); + + modelCommentRepository.listTopLevel.mockResolvedValue(page([root], 1)); + modelCommentRepository.listReplies.mockImplementation(async (parentId: string) => { + if (parentId === 'root') return page([depth1], 1); + if (parentId === 'd1') return page([depth2], 1); + if (parentId === 'd2') return page([deadChildless, deadWithReplies], 2); + return page([], 0); + }); + modelCommentRepository.countRepliesByParent.mockResolvedValue( + new Map([ + ['e-dead-childless', 0], + ['e-dead-with-replies', 3], + ]), + ); + + const result = await query.execute('model-1', {}); + + const d2Dto = result.data[0]!.replies!.data[0]!.replies!.data[0]!; + const leafIds = d2Dto.replies!.data.map((c) => c.id); + + expect(leafIds).toEqual(['e-dead-with-replies']); + expect(d2Dto.replies!.data[0]!.replies).toEqual({ count: 3, limit: 2, page: 0, data: [] }); + }); + + it('drops a childless deleted reply from a parent’s embedded replies (nested, not just top-level)', async () => { + const { query, modelCommentRepository } = buildQuery(); + const root = makeEntity({ id: 'root' }); + const liveChild = makeEntity({ id: 'live-child', parentId: 'root' }); + const deadChild = makeEntity({ + id: 'dead-child', + parentId: 'root', + content: null, + deletedAt: new Date(), + }); + + modelCommentRepository.listTopLevel.mockResolvedValue(page([root], 1)); + modelCommentRepository.listReplies.mockImplementation(async (parentId: string) => { + if (parentId === 'root') return page([liveChild, deadChild], 2); + return page([], 0); // neither child has replies of its own + }); + + const result = await query.execute('model-1', {}); + + expect(result.data[0]!.replies!.data.map((c) => c.id)).toEqual(['live-child']); + }); +}); diff --git a/apps/modeling-commons-backend/src/modules/model-comment/queries/list-comments.query.ts b/apps/modeling-commons-backend/src/modules/model-comment/queries/list-comments.query.ts new file mode 100644 index 00000000..d71d88df --- /dev/null +++ b/apps/modeling-commons-backend/src/modules/model-comment/queries/list-comments.query.ts @@ -0,0 +1,121 @@ +import { + COMMENT_TREE_DEFAULTS, + type ModelCommentEntity, +} from '#src/modules/model-comment/domain/model-comment.types.ts'; +import type { ModelCommentRepository } from '#src/modules/model-comment/database/model-comment.repository.port.ts'; +import type { CommentResponseDto } from '#src/modules/model-comment/dtos/comment.response.dto.ts'; +import type { ListCommentsQueryDto } from '#src/modules/model-comment/dtos/list-comments.query.dto.ts'; +import type { CommentResponseCtx } from '#src/modules/model-comment/model-comment.mapper.ts'; +import { + paginate, + type OrderBy, + type Paginated, + type PaginatedQueryParams, +} from '#src/shared/db/repository.port.ts'; +import { paginatedQueryBase } from '#src/shared/ddd/query.base.ts'; + +const EMBED_LIMIT = COMMENT_TREE_DEFAULTS.maximumShownRepliesPerLevel; + +// Deeper levels never honor a caller-supplied sort — a reply thread reads +// chronologically regardless of how the top-level page (or, for `get-comment`, +// the root's own reply page) is sorted. +const EMBED_ORDER_BY: OrderBy = { field: 'createdAt', param: 'asc' }; + +export const EMBED_PARAMS: PaginatedQueryParams = { + limit: EMBED_LIMIT, + page: 0, + offset: 0, + orderBy: EMBED_ORDER_BY, +}; + +export function commentOrderBy(sort?: ListCommentsQueryDto['sort']): OrderBy { + return { field: sort ?? 'createdAt', param: sort === 'likes' ? 'desc' : 'asc' }; +} + +type CommentTreeDeps = { + modelCommentRepository: ModelCommentRepository; + modelCommentMapper: Dependencies['modelCommentMapper']; +}; + +// Bounded BFS shared by `list-comments` (a page of roots) and `get-comment` +// (one re-rooted comment): recurse up to `COMMENT_TREE_DEFAULTS.maximumNested` +// levels, embedding at most `maximumShownRepliesPerLevel` replies per node. +// The deepest expanded level's own children aren't fetched, only counted +// (`countRepliesByParent`), so the UI can offer "continue this thread (N)". +// A childless deleted node is dropped from its parent's `replies.data` +// (decision #9); a deleted node with replies is kept. +export async function expandCommentTree( + deps: CommentTreeDeps, + entity: ModelCommentEntity, + depth: number, + ctx: CommentResponseCtx, + repliesParams: PaginatedQueryParams, +): Promise { + const dto = deps.modelCommentMapper.toResponse(entity, ctx); + const page = await deps.modelCommentRepository.listReplies(entity.id, repliesParams, ctx.viewerId); + if (page.count === 0) return dto; + + const nextDepth = depth + 1; + let data: Array; + + if (nextDepth < COMMENT_TREE_DEFAULTS.maximumNested) { + const expandedPairs = await Promise.all( + page.data.map(async (child) => ({ + child, + childDto: await expandCommentTree(deps, child, nextDepth, ctx, EMBED_PARAMS), + })), + ); + data = expandedPairs + .filter(({ child, childDto }) => !(child.deletedAt !== null && childDto.replies === undefined)) + .map(({ childDto }) => childDto); + } else { + const ids = page.data.map((child) => child.id); + const counts = await deps.modelCommentRepository.countRepliesByParent(ids); + data = page.data + .filter((child) => !(child.deletedAt !== null && (counts.get(child.id) ?? 0) === 0)) + .map((child) => { + const childDto = deps.modelCommentMapper.toResponse(child, ctx); + const count = counts.get(child.id) ?? 0; + if (count > 0) { + childDto.replies = { count, limit: EMBED_LIMIT, page: 0, data: [] }; + } + return childDto; + }); + } + + dto.replies = { count: page.count, limit: page.limit, page: page.page, data }; + return dto; +} + +export default function makeListCommentsQuery({ + modelCommentRepository, + modelCommentMapper, +}: Dependencies) { + return { + async execute( + modelId: string, + query: ListCommentsQueryDto, + ctx: CommentResponseCtx = {}, + ): Promise> { + // Spread `query` rather than naming `limit`/`page` explicitly: `paginatedQueryBase` + // only fills in its defaults for an *absent* key, not an explicit `undefined` value + // (its own final spread re-overwrites the default in that case). + const params = paginatedQueryBase({ ...query, orderBy: commentOrderBy(query.sort) }); + + const rootsPage = await modelCommentRepository.listTopLevel(modelId, params, ctx.viewerId); + const deps: CommentTreeDeps = { modelCommentRepository, modelCommentMapper }; + + const expandedPairs = await Promise.all( + rootsPage.data.map(async (root) => ({ + root, + dto: await expandCommentTree(deps, root, 0, ctx, EMBED_PARAMS), + })), + ); + const roots = expandedPairs + .filter(({ root, dto }) => !(root.deletedAt !== null && dto.replies === undefined)) + .map(({ dto }) => dto); + + return paginate(roots, params, rootsPage.count); + }, + }; +} From beb790f81306a7f2fbc5a2e9d6007862bf3731ce Mon Sep 17 00:00:00 2001 From: Omar Ibrahim Date: Mon, 20 Jul 2026 14:09:17 -0500 Subject: [PATCH 20/51] feat(commons): add model-comment HTTP routes and DI wiring - Register all seven /v1/models/:id/comments... routes, mirroring model-author.route.ts's fastify.get/post/patch/delete style. - Complete the awilix Dependencies declaration in model-comment/index.ts (modelCommentMapper, listCommentsQuery, getCommentQuery), moving the mapper's inline declare global into it so there's a single source. --- .../src/modules/model-comment/index.ts | 8 + .../model-comment/model-comment.mapper.ts | 6 - .../model-comment/model-comment.route.ts | 163 ++++++++++++++++++ 3 files changed, 171 insertions(+), 6 deletions(-) create mode 100644 apps/modeling-commons-backend/src/modules/model-comment/model-comment.route.ts diff --git a/apps/modeling-commons-backend/src/modules/model-comment/index.ts b/apps/modeling-commons-backend/src/modules/model-comment/index.ts index 61f52d3a..690a16ab 100644 --- a/apps/modeling-commons-backend/src/modules/model-comment/index.ts +++ b/apps/modeling-commons-backend/src/modules/model-comment/index.ts @@ -1,12 +1,20 @@ import type { ModelCommentRepository } from '#src/modules/model-comment/database/model-comment.repository.port.ts'; import type modelCommentDomain from '#src/modules/model-comment/domain/model-comment.domain.ts'; +import type { ModelCommentMapper } from '#src/modules/model-comment/model-comment.mapper.ts'; declare global { export interface Dependencies { modelCommentRepository: ModelCommentRepository; modelCommentDomain: ReturnType; + modelCommentMapper: ModelCommentMapper; modelCommentService: ReturnType< typeof import('#src/modules/model-comment/model-comment.service.ts').default >; + listCommentsQuery: ReturnType< + typeof import('#src/modules/model-comment/queries/list-comments.query.ts').default + >; + getCommentQuery: ReturnType< + typeof import('#src/modules/model-comment/queries/get-comment.query.ts').default + >; } } diff --git a/apps/modeling-commons-backend/src/modules/model-comment/model-comment.mapper.ts b/apps/modeling-commons-backend/src/modules/model-comment/model-comment.mapper.ts index 73999adf..5702717e 100644 --- a/apps/modeling-commons-backend/src/modules/model-comment/model-comment.mapper.ts +++ b/apps/modeling-commons-backend/src/modules/model-comment/model-comment.mapper.ts @@ -54,9 +54,3 @@ export default function modelCommentMapper(): ModelCommentMapper { }, }; } - -declare global { - interface Dependencies { - modelCommentMapper: ModelCommentMapper; - } -} diff --git a/apps/modeling-commons-backend/src/modules/model-comment/model-comment.route.ts b/apps/modeling-commons-backend/src/modules/model-comment/model-comment.route.ts new file mode 100644 index 00000000..50a9dfcd --- /dev/null +++ b/apps/modeling-commons-backend/src/modules/model-comment/model-comment.route.ts @@ -0,0 +1,163 @@ +import { requireAuth } from '#src/shared/hooks/require-auth.ts'; +import { resolveModel } from '#src/shared/hooks/resolve-model.ts'; +import type { FastifyInstance } from 'fastify'; +import { idDtoSchema } from '#src/shared/api/id.response.dto.ts'; +import { + modelCommentParamsSchema, + modelCommentDetailParamsSchema, + type ModelCommentParams, + type ModelCommentDetailParams, +} from '#src/modules/model-comment/model-comment.schemas.ts'; +import { + createCommentRequestDtoSchema, + type CreateCommentRequestDto, +} from '#src/modules/model-comment/dtos/create-comment.request.dto.ts'; +import { + updateCommentRequestDtoSchema, + type UpdateCommentRequestDto, +} from '#src/modules/model-comment/dtos/update-comment.request.dto.ts'; +import { + listCommentsQueryDtoSchema, + type ListCommentsQueryDto, +} from '#src/modules/model-comment/dtos/list-comments.query.dto.ts'; +import { commentResponseDtoSchema } from '#src/modules/model-comment/dtos/comment.response.dto.ts'; +import { commentPaginatedResponseSchema } from '#src/modules/model-comment/dtos/comment.paginated.response.dto.ts'; + +export default async function modelCommentRoutes(fastify: FastifyInstance) { + const { modelCommentService, listCommentsQuery, getCommentQuery } = fastify.diContainer.cradle; + + fastify.get<{ Params: ModelCommentParams; Querystring: ListCommentsQueryDto }>( + '/v1/models/:id/comments', + { + schema: { + params: modelCommentParamsSchema, + querystring: listCommentsQueryDtoSchema, + response: { 200: commentPaginatedResponseSchema }, + tags: ['Comment'], + }, + preHandler: [resolveModel('read')], + }, + async (request) => { + return listCommentsQuery.execute(request.params.id, request.query, { + viewerId: request.user?.id, + viewerRole: request.user?.systemRole, + }); + }, + ); + + fastify.post<{ Params: ModelCommentParams; Body: CreateCommentRequestDto }>( + '/v1/models/:id/comments', + { + schema: { + params: modelCommentParamsSchema, + body: createCommentRequestDtoSchema, + response: { 201: idDtoSchema }, + tags: ['Comment'], + }, + preHandler: [requireAuth, resolveModel('read')], + }, + async (request, reply) => { + const { id } = await modelCommentService.create({ + modelId: request.params.id, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + userId: request.user!.id, + parentId: request.body.parentId, + versionNumber: request.body.versionNumber, + content: request.body.content, + }); + return reply.code(201).send({ id }); + }, + ); + + fastify.get<{ Params: ModelCommentDetailParams; Querystring: ListCommentsQueryDto }>( + '/v1/models/:id/comments/:commentId', + { + schema: { + params: modelCommentDetailParamsSchema, + querystring: listCommentsQueryDtoSchema, + response: { 200: commentResponseDtoSchema }, + tags: ['Comment'], + }, + preHandler: [resolveModel('read')], + }, + async (request) => { + return getCommentQuery.execute(request.params.commentId, request.query, { + viewerId: request.user?.id, + viewerRole: request.user?.systemRole, + }); + }, + ); + + fastify.patch<{ Params: ModelCommentDetailParams; Body: UpdateCommentRequestDto }>( + '/v1/models/:id/comments/:commentId', + { + schema: { + params: modelCommentDetailParamsSchema, + body: updateCommentRequestDtoSchema, + tags: ['Comment'], + }, + preHandler: [requireAuth, resolveModel('read')], + }, + async (request, reply) => { + await modelCommentService.updateContent({ + modelId: request.params.id, + commentId: request.params.commentId, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + callerId: request.user!.id, + content: request.body.content, + }); + return reply.code(204).send(); + }, + ); + + fastify.delete<{ Params: ModelCommentDetailParams }>( + '/v1/models/:id/comments/:commentId', + { + schema: { params: modelCommentDetailParamsSchema, tags: ['Comment'] }, + preHandler: [requireAuth, resolveModel('read')], + }, + async (request, reply) => { + await modelCommentService.softDelete({ + modelId: request.params.id, + commentId: request.params.commentId, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + caller: { id: request.user!.id, systemRole: request.user!.systemRole }, + }); + return reply.code(204).send(); + }, + ); + + fastify.post<{ Params: ModelCommentDetailParams }>( + '/v1/models/:id/comments/:commentId/like', + { + schema: { params: modelCommentDetailParamsSchema, tags: ['Comment'] }, + preHandler: [requireAuth, resolveModel('read')], + }, + async (request, reply) => { + await modelCommentService.like({ + modelId: request.params.id, + commentId: request.params.commentId, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + userId: request.user!.id, + }); + return reply.code(204).send(); + }, + ); + + fastify.delete<{ Params: ModelCommentDetailParams }>( + '/v1/models/:id/comments/:commentId/like', + { + schema: { params: modelCommentDetailParamsSchema, tags: ['Comment'] }, + preHandler: [requireAuth, resolveModel('read')], + }, + async (request, reply) => { + await modelCommentService.unlike({ + modelId: request.params.id, + commentId: request.params.commentId, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + userId: request.user!.id, + }); + return reply.code(204).send(); + }, + ); +} From 26d456e3c493622a228ed6d05effc65403e72c94 Mon Sep 17 00:00:00 2001 From: Omar Ibrahim Date: Mon, 20 Jul 2026 14:20:03 -0500 Subject: [PATCH 21/51] test(commons): add e2e coverage for model-comment API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Covers create/list/get/edit/delete/like routes under /v1/models/:id/comments per doc/discussion-plan.md §13: tombstones, bounded reply-tree embedding (2 x 3 levels), auth/permission checks, per-author email notification (mail-capture spy on mailService), and Event audit rows for create/update/delete with none for likes. Not executed in this environment: Docker/postgres is unavailable here, so the hand-authored add_model_comment migration remains unverified and this suite has not been run against a real database. --- .../tests/api/model-comment.feature | 214 ++++++++++ .../tests/api/model-comment.steps.ts | 378 ++++++++++++++++++ 2 files changed, 592 insertions(+) create mode 100644 apps/modeling-commons-backend/tests/api/model-comment.feature create mode 100644 apps/modeling-commons-backend/tests/api/model-comment.steps.ts diff --git a/apps/modeling-commons-backend/tests/api/model-comment.feature b/apps/modeling-commons-backend/tests/api/model-comment.feature new file mode 100644 index 00000000..27b227cf --- /dev/null +++ b/apps/modeling-commons-backend/tests/api/model-comment.feature @@ -0,0 +1,214 @@ +Feature: Model Comments + As a user + I want to comment and reply on models + So that I can discuss models with their authors and other users + + Scenario: Posting a comment succeeds + Given an authenticated user "owner" + And a public model "Commentable" created by "owner" + When "owner" comments "Nice model!" on "Commentable" + Then the response status should be 201 + And the response body should have property "id" + + Scenario: Commenting requires authentication + Given an authenticated user "owner" + And a public model "Auth Required" created by "owner" + When an anonymous viewer comments "Anonymous comment" on "Auth Required" + Then the response status should be 401 + + Scenario: Cannot comment on a private model owned by someone else + Given an authenticated user "owner" + And a private model "Secret" created by "owner" + And an authenticated user "stranger" + When "stranger" comments "Trying to peek" on "Secret" + Then the response status should be 403 + + Scenario: Replying with a parentId from a different model is rejected + Given an authenticated user "owner" + And a public model "Model A" created by "owner" + And a public model "Model B" created by "owner" + And "owner" has commented "Root in A" on "Model A" as "rootA" + When "owner" replies "Cross-model reply" to comment "rootA" on "Model B" + Then the response status should be 400 + + Scenario: A page of top-level comments embeds at most 2 replies per node + Given an authenticated user "owner" + And a public model "Popular" created by "owner" + And an authenticated user "commenter" + And "commenter" has commented "Root" on "Popular" as "root" + And "commenter" has replied "First" to comment "root" as "r1" + And "commenter" has replied "Second" to comment "root" as "r2" + And "commenter" has replied "Third" to comment "root" as "r3" + When "commenter" lists comments on "Popular" + Then the response status should be 200 + And comment "root" should have replies count 3 + And comment "root" should have 2 embedded replies + And comment "r1" should appear in the response + And comment "r2" should appear in the response + And comment "r3" should not appear in the response + + Scenario: Replies are embedded up to three levels deep, deeper nodes carry only a count + Given an authenticated user "owner" + And a public model "Threaded" created by "owner" + And an authenticated user "commenter" + And "commenter" has commented "Root comment" on "Threaded" as "root" + And "commenter" has replied "Reply L1" to comment "root" as "l1" + And "commenter" has replied "Reply L2" to comment "l1" as "l2" + And "commenter" has replied "Reply L3" to comment "l2" as "l3" + And "commenter" has replied "Reply L4" to comment "l3" as "l4" + When "commenter" lists comments on "Threaded" + Then the response status should be 200 + And comment "root" should appear in the response + And comment "l1" should appear in the response + And comment "l2" should appear in the response + And comment "l3" should appear in the response + And comment "l3" should have replies count 1 + And comment "l4" should not appear in the response + + Scenario: Getting a comment thread re-roots it and paginates its own replies + Given an authenticated user "owner" + And a public model "Thread Model" created by "owner" + And an authenticated user "commenter" + And "commenter" has commented "Root" on "Thread Model" as "root" + And "commenter" has replied "Reply A" to comment "root" as "replyA" + And "commenter" has replied "Reply B" to comment "root" as "replyB" + And "commenter" has replied "Reply C" to comment "root" as "replyC" + When "commenter" gets comment "root" on "Thread Model" with page 0 and limit 2 + Then the response status should be 200 + And the response body should have property "id" + And comment "replyA" should appear in the response + And comment "replyB" should appear in the response + And comment "replyC" should not appear in the response + When "commenter" gets comment "root" on "Thread Model" with page 1 and limit 2 + Then the response status should be 200 + And comment "replyC" should appear in the response + And comment "replyA" should not appear in the response + + Scenario: Only the author can edit a comment + Given an authenticated user "owner" + And a public model "Editable" created by "owner" + And "owner" has commented "Original text" on "Editable" as "mine" + And an authenticated user "intruder" + When "intruder" edits comment "mine" on "Editable" with content "Hijacked" + Then the response status should be 403 + + Scenario: The author can edit their own comment + Given an authenticated user "owner" + And a public model "Editable Own" created by "owner" + And "owner" has commented "Original text" on "Editable Own" as "mine" + When "owner" edits comment "mine" on "Editable Own" with content "Updated text" + Then the response status should be 204 + When "owner" gets comment "mine" on "Editable Own" + Then the response status should be 200 + And comment "mine" in the response should have property "content" equal to "Updated text" + And comment "mine" in the response should have property "edited" equal to "true" + + Scenario: Deleting a comment with no replies removes it entirely + Given an authenticated user "owner" + And a public model "Deletable" created by "owner" + And "owner" has commented "Delete me" on "Deletable" as "solo" + When "owner" deletes comment "solo" on "Deletable" + Then the response status should be 204 + When "owner" lists comments on "Deletable" + Then comment "solo" should not appear in the response + + Scenario: Deleting a comment with replies leaves a tombstone + Given an authenticated user "owner" + And a public model "Tombstone Model" created by "owner" + And an authenticated user "replier" + And "owner" has commented "Parent" on "Tombstone Model" as "parent" + And "replier" has replied "A reply" to comment "parent" as "child" + When "owner" deletes comment "parent" on "Tombstone Model" + Then the response status should be 204 + When "owner" lists comments on "Tombstone Model" + Then comment "parent" should appear in the response + And comment "parent" in the response should have property "deleted" equal to "true" + And comment "parent" in the response should have property "content" equal to "[deleted]" + And comment "child" should appear in the response + + Scenario: An admin can delete another user's comment + Given an authenticated user "owner" + And a public model "Moderated" created by "owner" + And "owner" has commented "Needs moderation" on "Moderated" as "flagged" + And an authenticated admin user "admin" + When "admin" deletes comment "flagged" on "Moderated" + Then the response status should be 204 + + Scenario: A random user cannot delete someone else's comment + Given an authenticated user "owner" + And a public model "Protected" created by "owner" + And "owner" has commented "Mine only" on "Protected" as "protected-comment" + And an authenticated user "rando" + When "rando" deletes comment "protected-comment" on "Protected" + Then the response status should be 403 + + Scenario: Liking and unliking a comment is reflected and idempotent + Given an authenticated user "owner" + And a public model "Likeable Comments" created by "owner" + And "owner" has commented "Like this" on "Likeable Comments" as "target" + And an authenticated user "fan" + When "fan" likes comment "target" on "Likeable Comments" + Then the response status should be 204 + When "fan" gets comment "target" on "Likeable Comments" + Then comment "target" in the response should have property "likes" equal to "1" + And comment "target" in the response should have property "likedByMe" equal to "true" + When "fan" likes comment "target" on "Likeable Comments" + Then the response status should be 204 + When "fan" gets comment "target" on "Likeable Comments" + Then comment "target" in the response should have property "likes" equal to "1" + When "fan" unlikes comment "target" on "Likeable Comments" + Then the response status should be 204 + When "fan" gets comment "target" on "Likeable Comments" + Then comment "target" in the response should have property "likes" equal to "0" + And comment "target" in the response should have property "likedByMe" equal to "false" + When "fan" unlikes comment "target" on "Likeable Comments" + Then the response status should be 204 + When "fan" gets comment "target" on "Likeable Comments" + Then comment "target" in the response should have property "likes" equal to "0" + + Scenario: Commenting notifies other authors but never the commenter + Given an authenticated user "owner" + And a public model "Notify Model" created by "owner" + And an authenticated user "contributor" + And "owner" has added "contributor" as a contributor to "Notify Model" + And an authenticated user "commenter" + And mail delivery is captured + When "commenter" comments "Great work!" on "Notify Model" + Then the response status should be 201 + And mail should have been sent to 2 recipients + And mail should have been sent to "owner" + And mail should have been sent to "contributor" + And mail should not have been sent to "commenter" + + Scenario: Commenting on your own model does not notify yourself + Given an authenticated user "owner" + And a public model "Solo Model" created by "owner" + And mail delivery is captured + When "owner" comments "Talking to myself" on "Solo Model" + Then the response status should be 201 + And no mail should have been sent + + Scenario: Comment lifecycle events are audited but likes are not + Given an authenticated admin user "admin" + And a public model "Audited Comments" created by "admin" + When "admin" lists admin events with resourceType "model" + Then the response body property "data" should have length 1 + And "admin" has commented "First" on "Audited Comments" as "audited" + When "admin" lists admin events with resourceType "model" + Then the response body property "data" should have length 2 + When "admin" lists admin events with type "model_comment.created" + Then the response body property "data" should have length 1 + When "admin" edits comment "audited" on "Audited Comments" with content "Edited" + Then the response status should be 204 + When "admin" lists admin events with resourceType "model" + Then the response body property "data" should have length 3 + When "admin" likes comment "audited" on "Audited Comments" + Then the response status should be 204 + When "admin" unlikes comment "audited" on "Audited Comments" + Then the response status should be 204 + When "admin" lists admin events with resourceType "model" + Then the response body property "data" should have length 3 + When "admin" deletes comment "audited" on "Audited Comments" + Then the response status should be 204 + When "admin" lists admin events with resourceType "model" + Then the response body property "data" should have length 4 diff --git a/apps/modeling-commons-backend/tests/api/model-comment.steps.ts b/apps/modeling-commons-backend/tests/api/model-comment.steps.ts new file mode 100644 index 00000000..38d15696 --- /dev/null +++ b/apps/modeling-commons-backend/tests/api/model-comment.steps.ts @@ -0,0 +1,378 @@ +import assert from 'node:assert'; +import { Given, Then, When } from '@cucumber/cucumber'; +import type { FastifyInstance } from 'fastify'; +import type { ICustomWorld } from '../support/custom-world.ts'; +import { type TestUser } from '../support/auth-helper.ts'; + +interface CommentRef { + id: string; + modelId: string; +} + +interface MailCall { + to: string; +} + +function getUsers(context: Record): Map { + if (!context['users']) context['users'] = new Map(); + return context['users'] as Map; +} + +function getModels(context: Record): Map { + if (!context['models']) context['models'] = new Map(); + return context['models'] as Map; +} + +function getComments(context: Record): Map { + if (!context['comments']) context['comments'] = new Map(); + return context['comments'] as Map; +} + +async function postComment( + server: FastifyInstance, + actor: TestUser | undefined, + modelId: string, + body: Record, +) { + return server.inject({ + method: 'POST', + url: `/api/v1/models/${modelId}/comments`, + payload: body, + headers: { + 'content-type': 'application/json', + ...(actor ? { cookie: actor.cookie } : {}), + }, + }); +} + +async function seedComment( + this: ICustomWorld, + actorName: string, + content: string, + modelId: string, + label: string, + parentId?: string, +): Promise { + const actor = getUsers(this.context).get(actorName)!; + const res = await postComment(this.server, actor, modelId, { + content, + ...(parentId ? { parentId } : {}), + }); + if (res.statusCode !== 201) { + throw new Error(`Failed to seed comment (${res.statusCode}): ${res.body}`); + } + const id = JSON.parse(res.body).id; + getComments(this.context).set(label, { id, modelId }); +} + +// Recursively locates a comment node by id in either a `Paginated` +// envelope (`{ data: [...] }`) or a single re-rooted `Comment`, descending +// into each node's embedded `replies` (itself a `{ data: [...] }` envelope). +function findCommentNode(body: unknown, id: string): Record | undefined { + if (!body || typeof body !== 'object') return undefined; + const container = body as { data?: unknown[]; id?: unknown; replies?: unknown }; + const candidates = Array.isArray(container.data) ? container.data : [container]; + + for (const candidate of candidates) { + const node = candidate as Record; + if (node?.['id'] === id) return node; + if (node?.['replies']) { + const found = findCommentNode(node['replies'], id); + if (found) return found; + } + } + return undefined; +} + +function commentId(this: ICustomWorld, label: string): string { + return getComments(this.context).get(label)!.id; +} + +When( + '{string} comments {string} on {string}', + async function (this: ICustomWorld, actorName: string, content: string, modelTitle: string) { + const actor = getUsers(this.context).get(actorName)!; + const modelId = getModels(this.context).get(modelTitle)!; + this.context.latestResponse = await postComment(this.server, actor, modelId, { content }); + }, +); + +When( + 'an anonymous viewer comments {string} on {string}', + async function (this: ICustomWorld, content: string, modelTitle: string) { + const modelId = getModels(this.context).get(modelTitle)!; + this.context.latestResponse = await postComment(this.server, undefined, modelId, { content }); + }, +); + +Given( + '{string} has commented {string} on {string} as {string}', + async function (this: ICustomWorld, actorName: string, content: string, modelTitle: string, label: string) { + const modelId = getModels(this.context).get(modelTitle)!; + await seedComment.call(this, actorName, content, modelId, label); + }, +); + +When( + '{string} replies {string} to comment {string} on {string}', + async function ( + this: ICustomWorld, + actorName: string, + content: string, + parentLabel: string, + modelTitle: string, + ) { + const actor = getUsers(this.context).get(actorName)!; + const modelId = getModels(this.context).get(modelTitle)!; + const parent = getComments(this.context).get(parentLabel)!; + this.context.latestResponse = await postComment(this.server, actor, modelId, { + content, + parentId: parent.id, + }); + }, +); + +Given( + '{string} has replied {string} to comment {string} as {string}', + async function ( + this: ICustomWorld, + actorName: string, + content: string, + parentLabel: string, + label: string, + ) { + const parent = getComments(this.context).get(parentLabel)!; + await seedComment.call(this, actorName, content, parent.modelId, label, parent.id); + }, +); + +When( + '{string} lists comments on {string}', + async function (this: ICustomWorld, actorName: string, modelTitle: string) { + const actor = getUsers(this.context).get(actorName)!; + const modelId = getModels(this.context).get(modelTitle)!; + this.context.latestResponse = await this.server.inject({ + method: 'GET', + url: `/api/v1/models/${modelId}/comments`, + headers: { cookie: actor.cookie }, + }); + }, +); + +When( + '{string} gets comment {string} on {string}', + async function (this: ICustomWorld, actorName: string, label: string, modelTitle: string) { + const actor = getUsers(this.context).get(actorName)!; + const modelId = getModels(this.context).get(modelTitle)!; + const id = commentId.call(this, label); + this.context.latestResponse = await this.server.inject({ + method: 'GET', + url: `/api/v1/models/${modelId}/comments/${id}`, + headers: { cookie: actor.cookie }, + }); + }, +); + +When( + '{string} gets comment {string} on {string} with page {int} and limit {int}', + async function ( + this: ICustomWorld, + actorName: string, + label: string, + modelTitle: string, + page: number, + limit: number, + ) { + const actor = getUsers(this.context).get(actorName)!; + const modelId = getModels(this.context).get(modelTitle)!; + const id = commentId.call(this, label); + this.context.latestResponse = await this.server.inject({ + method: 'GET', + url: `/api/v1/models/${modelId}/comments/${id}?page=${page}&limit=${limit}`, + headers: { cookie: actor.cookie }, + }); + }, +); + +When( + '{string} edits comment {string} on {string} with content {string}', + async function ( + this: ICustomWorld, + actorName: string, + label: string, + modelTitle: string, + content: string, + ) { + const actor = getUsers(this.context).get(actorName)!; + const modelId = getModels(this.context).get(modelTitle)!; + const id = commentId.call(this, label); + this.context.latestResponse = await this.server.inject({ + method: 'PATCH', + url: `/api/v1/models/${modelId}/comments/${id}`, + payload: { content }, + headers: { cookie: actor.cookie, 'content-type': 'application/json' }, + }); + }, +); + +When( + '{string} deletes comment {string} on {string}', + async function (this: ICustomWorld, actorName: string, label: string, modelTitle: string) { + const actor = getUsers(this.context).get(actorName)!; + const modelId = getModels(this.context).get(modelTitle)!; + const id = commentId.call(this, label); + this.context.latestResponse = await this.server.inject({ + method: 'DELETE', + url: `/api/v1/models/${modelId}/comments/${id}`, + headers: { cookie: actor.cookie }, + }); + }, +); + +When( + '{string} likes comment {string} on {string}', + async function (this: ICustomWorld, actorName: string, label: string, modelTitle: string) { + const actor = getUsers(this.context).get(actorName)!; + const modelId = getModels(this.context).get(modelTitle)!; + const id = commentId.call(this, label); + this.context.latestResponse = await this.server.inject({ + method: 'POST', + url: `/api/v1/models/${modelId}/comments/${id}/like`, + headers: { cookie: actor.cookie }, + }); + }, +); + +When( + '{string} unlikes comment {string} on {string}', + async function (this: ICustomWorld, actorName: string, label: string, modelTitle: string) { + const actor = getUsers(this.context).get(actorName)!; + const modelId = getModels(this.context).get(modelTitle)!; + const id = commentId.call(this, label); + this.context.latestResponse = await this.server.inject({ + method: 'DELETE', + url: `/api/v1/models/${modelId}/comments/${id}/like`, + headers: { cookie: actor.cookie }, + }); + }, +); + +Then( + 'comment {string} should appear in the response', + function (this: ICustomWorld, label: string) { + const body = JSON.parse(this.context.latestResponse!.body); + const node = findCommentNode(body, commentId.call(this, label)); + assert.ok(node, `Expected comment "${label}" to appear in the response`); + }, +); + +Then( + 'comment {string} should not appear in the response', + function (this: ICustomWorld, label: string) { + const body = JSON.parse(this.context.latestResponse!.body); + const node = findCommentNode(body, commentId.call(this, label)); + assert.ok(!node, `Expected comment "${label}" to NOT appear in the response`); + }, +); + +Then( + 'comment {string} in the response should have property {string} equal to {string}', + function (this: ICustomWorld, label: string, property: string, expected: string) { + const body = JSON.parse(this.context.latestResponse!.body); + const node = findCommentNode(body, commentId.call(this, label)); + assert.ok(node, `Expected comment "${label}" to appear in the response`); + assert.strictEqual(String(node![property]), expected); + }, +); + +Then( + 'comment {string} should have replies count {int}', + function (this: ICustomWorld, label: string, expected: number) { + const body = JSON.parse(this.context.latestResponse!.body); + const node = findCommentNode(body, commentId.call(this, label)); + assert.ok(node, `Expected comment "${label}" to appear in the response`); + const replies = node!['replies'] as { count?: number } | undefined; + assert.strictEqual(replies?.count, expected); + }, +); + +Then( + 'comment {string} should have {int} embedded replies', + function (this: ICustomWorld, label: string, expected: number) { + const body = JSON.parse(this.context.latestResponse!.body); + const node = findCommentNode(body, commentId.call(this, label)); + assert.ok(node, `Expected comment "${label}" to appear in the response`); + const replies = node!['replies'] as { data?: unknown[] } | undefined; + assert.strictEqual(replies?.data?.length, expected); + }, +); + +// --- Mail capture ----------------------------------------------------------- +// The comment service fires `notifyOnNewComment` fire-and-forget (`void`) after +// the write transaction commits, so the HTTP response can return before mail +// dispatch runs. Rather than sending through a real SMTP/Mailpit round trip, +// `mailService.sendMail` (a DI singleton) is monkey-patched with a capturing +// stub, and mail assertions poll briefly for the expected number of calls. + +function installMailSpy(server: FastifyInstance): MailCall[] { + const calls: MailCall[] = []; + const mailService = server.diContainer.cradle.mailService as { + sendMail: (content: unknown) => Promise; + }; + mailService.sendMail = async (content: unknown) => { + const { to } = content as { to?: string }; + calls.push({ to: to ?? '' }); + }; + return calls; +} + +async function waitForMailCalls(calls: MailCall[], expected: number, timeoutMs = 3000): Promise { + const start = Date.now(); + while (calls.length < expected && Date.now() - start < timeoutMs) { + await new Promise((resolve) => setTimeout(resolve, 25)); + } +} + +Given('mail delivery is captured', function (this: ICustomWorld) { + this.context['mailCalls'] = installMailSpy(this.server); +}); + +Then( + 'mail should have been sent to {int} recipients', + async function (this: ICustomWorld, count: number) { + const calls = this.context['mailCalls'] as MailCall[]; + await waitForMailCalls(calls, count); + assert.strictEqual(calls.length, count); + }, +); + +Then( + 'mail should have been sent to {string}', + function (this: ICustomWorld, actorName: string) { + const calls = this.context['mailCalls'] as MailCall[]; + const actor = getUsers(this.context).get(actorName)!; + assert.ok( + calls.some((call) => call.to === actor.email), + `Expected an email to be sent to ${actor.email}`, + ); + }, +); + +Then( + 'mail should not have been sent to {string}', + function (this: ICustomWorld, actorName: string) { + const calls = this.context['mailCalls'] as MailCall[]; + const actor = getUsers(this.context).get(actorName)!; + assert.ok( + !calls.some((call) => call.to === actor.email), + `Expected no email to be sent to ${actor.email}`, + ); + }, +); + +Then('no mail should have been sent', async function (this: ICustomWorld) { + // Give the fire-and-forget notifier a brief window before asserting absence. + await new Promise((resolve) => setTimeout(resolve, 200)); + const calls = this.context['mailCalls'] as MailCall[]; + assert.strictEqual(calls.length, 0); +}); From 2a458eca291e94f29cf803b38b71f2e72da45e86 Mon Sep 17 00:00:00 2001 From: Omar Ibrahim Date: Mon, 20 Jul 2026 14:47:06 -0500 Subject: [PATCH 22/51] Bugfix(commons): make model-comment addLikeTx idempotent under concurrency --- .../database/model-comment.repository.ts | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/apps/modeling-commons-backend/src/modules/model-comment/database/model-comment.repository.ts b/apps/modeling-commons-backend/src/modules/model-comment/database/model-comment.repository.ts index 4d805f0e..1b26a3a0 100644 --- a/apps/modeling-commons-backend/src/modules/model-comment/database/model-comment.repository.ts +++ b/apps/modeling-commons-backend/src/modules/model-comment/database/model-comment.repository.ts @@ -1,4 +1,4 @@ -import type { Prisma } from '#prisma/index'; +import { Prisma } from '#prisma/index'; import { modelCommentInclude, type ModelCommentRecord, @@ -86,7 +86,7 @@ export default function modelCommentRepository({ db }: Dependencies): ModelComme return paginate(records.map(toEntity), params, count); }, - async countRepliesByParent(parentIds: string[]): Promise> { + async countRepliesByParent(parentIds: Array): Promise> { const result = new Map(); if (parentIds.length === 0) return result; @@ -130,12 +130,15 @@ export default function modelCommentRepository({ db }: Dependencies): ModelComme async addLikeTx(ctx: TransactionContext, commentId: string, userId: string): Promise { const client = resolveTransaction(ctx); - const existing = await client.modelCommentLike.findUnique({ - where: { modelCommentId_userId: { modelCommentId: commentId, userId } }, - }); - if (existing) return false; + try { + await client.modelCommentLike.create({ data: { modelCommentId: commentId, userId } }); + } catch (error) { + if (error instanceof Prisma.PrismaClientKnownRequestError && error.code === 'P2002') { + return false; + } + throw error; + } - await client.modelCommentLike.create({ data: { modelCommentId: commentId, userId } }); await client.modelComment.update({ where: { id: commentId }, data: { likesCount: { increment: 1 } }, From 8ac256de574a370085f8bd72ebe8611a8a7ecf71 Mon Sep 17 00:00:00 2001 From: Omar Ibrahim Date: Mon, 20 Jul 2026 14:47:24 -0500 Subject: [PATCH 23/51] Fix(commons): guard model-comment routes against cross-model comment access Add resolveComment preHandler hook that 404s when :commentId doesn't belong to the :id model, closing an IDOR where a comment from a private model could be read via a public model's path. --- .../model-comment/model-comment.route.ts | 11 ++-- .../src/shared/hooks/resolve-comment.spec.ts | 60 +++++++++++++++++++ .../src/shared/hooks/resolve-comment.ts | 27 +++++++++ .../tests/api/model-comment.feature | 8 +++ 4 files changed, 101 insertions(+), 5 deletions(-) create mode 100644 apps/modeling-commons-backend/src/shared/hooks/resolve-comment.spec.ts create mode 100644 apps/modeling-commons-backend/src/shared/hooks/resolve-comment.ts diff --git a/apps/modeling-commons-backend/src/modules/model-comment/model-comment.route.ts b/apps/modeling-commons-backend/src/modules/model-comment/model-comment.route.ts index 50a9dfcd..a50f8ea4 100644 --- a/apps/modeling-commons-backend/src/modules/model-comment/model-comment.route.ts +++ b/apps/modeling-commons-backend/src/modules/model-comment/model-comment.route.ts @@ -1,4 +1,5 @@ import { requireAuth } from '#src/shared/hooks/require-auth.ts'; +import { resolveComment } from '#src/shared/hooks/resolve-comment.ts'; import { resolveModel } from '#src/shared/hooks/resolve-model.ts'; import type { FastifyInstance } from 'fastify'; import { idDtoSchema } from '#src/shared/api/id.response.dto.ts'; @@ -78,7 +79,7 @@ export default async function modelCommentRoutes(fastify: FastifyInstance) { response: { 200: commentResponseDtoSchema }, tags: ['Comment'], }, - preHandler: [resolveModel('read')], + preHandler: [resolveModel('read'), resolveComment()], }, async (request) => { return getCommentQuery.execute(request.params.commentId, request.query, { @@ -96,7 +97,7 @@ export default async function modelCommentRoutes(fastify: FastifyInstance) { body: updateCommentRequestDtoSchema, tags: ['Comment'], }, - preHandler: [requireAuth, resolveModel('read')], + preHandler: [requireAuth, resolveModel('read'), resolveComment()], }, async (request, reply) => { await modelCommentService.updateContent({ @@ -114,7 +115,7 @@ export default async function modelCommentRoutes(fastify: FastifyInstance) { '/v1/models/:id/comments/:commentId', { schema: { params: modelCommentDetailParamsSchema, tags: ['Comment'] }, - preHandler: [requireAuth, resolveModel('read')], + preHandler: [requireAuth, resolveModel('read'), resolveComment()], }, async (request, reply) => { await modelCommentService.softDelete({ @@ -131,7 +132,7 @@ export default async function modelCommentRoutes(fastify: FastifyInstance) { '/v1/models/:id/comments/:commentId/like', { schema: { params: modelCommentDetailParamsSchema, tags: ['Comment'] }, - preHandler: [requireAuth, resolveModel('read')], + preHandler: [requireAuth, resolveModel('read'), resolveComment()], }, async (request, reply) => { await modelCommentService.like({ @@ -148,7 +149,7 @@ export default async function modelCommentRoutes(fastify: FastifyInstance) { '/v1/models/:id/comments/:commentId/like', { schema: { params: modelCommentDetailParamsSchema, tags: ['Comment'] }, - preHandler: [requireAuth, resolveModel('read')], + preHandler: [requireAuth, resolveModel('read'), resolveComment()], }, async (request, reply) => { await modelCommentService.unlike({ diff --git a/apps/modeling-commons-backend/src/shared/hooks/resolve-comment.spec.ts b/apps/modeling-commons-backend/src/shared/hooks/resolve-comment.spec.ts new file mode 100644 index 00000000..8fa60da3 --- /dev/null +++ b/apps/modeling-commons-backend/src/shared/hooks/resolve-comment.spec.ts @@ -0,0 +1,60 @@ +import { CommentNotFoundError } from '#src/modules/model-comment/domain/model-comment.errors.ts'; +import { mockModelCommentRepository } from '#src/modules/model-comment/database/model-comment.repository.mock.ts'; +import { resolveComment } from '#src/shared/hooks/resolve-comment.ts'; +import type { FastifyReply, FastifyRequest } from 'fastify'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +const reply = {} as FastifyReply; + +function makeRequest(opts: { + modelId: string; + commentId: string; + modelCommentRepository: ReturnType; +}): FastifyRequest { + return { + params: { id: opts.modelId, commentId: opts.commentId }, + server: { + diContainer: { cradle: { modelCommentRepository: opts.modelCommentRepository } }, + }, + } as unknown as FastifyRequest; +} + +describe('resolveComment', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('throws CommentNotFoundError when the comment does not exist', async () => { + const modelCommentRepository = mockModelCommentRepository(); + modelCommentRepository.findById.mockResolvedValue(undefined); + + const request = makeRequest({ modelId: 'm1', commentId: 'c1', modelCommentRepository }); + + // @ts-expect-error - no need for done callback + await expect(resolveComment()(request, reply)).rejects.toThrow(CommentNotFoundError); + }); + + it('throws CommentNotFoundError when the comment belongs to a different model', async () => { + const modelCommentRepository = mockModelCommentRepository(); + modelCommentRepository.findById.mockResolvedValue({ id: 'c1', modelId: 'other' }); + + const request = makeRequest({ modelId: 'm1', commentId: 'c1', modelCommentRepository }); + + // @ts-expect-error - no need for done callback + await expect(resolveComment()(request, reply)).rejects.toThrow(CommentNotFoundError); + expect(modelCommentRepository.findById).toHaveBeenCalledWith('c1'); + }); + + it('attaches the comment to the request when the model id matches', async () => { + const comment = { id: 'c1', modelId: 'm1', content: 'hello' }; + const modelCommentRepository = mockModelCommentRepository(); + modelCommentRepository.findById.mockResolvedValue(comment); + + const request = makeRequest({ modelId: 'm1', commentId: 'c1', modelCommentRepository }); + + // @ts-expect-error - no need for done callback + await resolveComment()(request, reply); + + expect((request as unknown as { comment: unknown }).comment).toBe(comment); + }); +}); diff --git a/apps/modeling-commons-backend/src/shared/hooks/resolve-comment.ts b/apps/modeling-commons-backend/src/shared/hooks/resolve-comment.ts new file mode 100644 index 00000000..8d4a6f9c --- /dev/null +++ b/apps/modeling-commons-backend/src/shared/hooks/resolve-comment.ts @@ -0,0 +1,27 @@ +import { CommentNotFoundError } from '#src/modules/model-comment/domain/model-comment.errors.ts'; +import type { ModelCommentEntity } from '#src/modules/model-comment/domain/model-comment.types.ts'; +import type { FastifyReply, FastifyRequest, preHandlerHookHandler } from 'fastify'; + +// `resolveModel` only authorizes the path model (`:id`); it never checks that +// `:commentId` actually belongs to it. Without this hook, a caller with read +// access to model P could fetch/mutate a comment that belongs to a different +// model M by pointing `:id` at P and `:commentId` at M's comment (IDOR). +export function resolveComment(): preHandlerHookHandler { + return async (request: FastifyRequest, _reply: FastifyReply) => { + const { id, commentId } = request.params as { id: string; commentId: string }; + const { modelCommentRepository } = request.server.diContainer.cradle; + + const comment = await modelCommentRepository.findById(commentId); + if (!comment || comment.modelId !== id) { + throw new CommentNotFoundError(commentId); + } + + request.comment = comment; + }; +} + +declare module 'fastify' { + interface FastifyRequest { + comment: ModelCommentEntity; + } +} diff --git a/apps/modeling-commons-backend/tests/api/model-comment.feature b/apps/modeling-commons-backend/tests/api/model-comment.feature index 27b227cf..cafc089b 100644 --- a/apps/modeling-commons-backend/tests/api/model-comment.feature +++ b/apps/modeling-commons-backend/tests/api/model-comment.feature @@ -31,6 +31,14 @@ Feature: Model Comments When "owner" replies "Cross-model reply" to comment "rootA" on "Model B" Then the response status should be 400 + Scenario: A comment cannot be fetched through a different model's path + Given an authenticated user "owner" + And a public model "Model A" created by "owner" + And a public model "Model B" created by "owner" + And "owner" has commented "Root in A" on "Model A" as "rootA" + When "owner" gets comment "rootA" on "Model B" + Then the response status should be 404 + Scenario: A page of top-level comments embeds at most 2 replies per node Given an authenticated user "owner" And a public model "Popular" created by "owner" From 92259bf6a1bc8392b64df802f6b94d7117e4b920 Mon Sep 17 00:00:00 2001 From: Omar Ibrahim Date: Mon, 20 Jul 2026 14:47:48 -0500 Subject: [PATCH 24/51] Fix(commons): drop unsafe mail cast and fake unsubscribe URL sendMail's param type was accidentally the nodemailer Mail class (instance methods/props) instead of Mail.Options, so no plain content object could ever satisfy it structurally, forcing the `as never` cast. Point unsubscribeUrl at the real support inbox instead of a non-existent /unsubscribe route. --- .../src/modules/mail/mail.service.ts | 2 +- .../modules/model-comment/model-comment.service.ts | 11 ++++------- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/apps/modeling-commons-backend/src/modules/mail/mail.service.ts b/apps/modeling-commons-backend/src/modules/mail/mail.service.ts index 109f9369..811119ee 100644 --- a/apps/modeling-commons-backend/src/modules/mail/mail.service.ts +++ b/apps/modeling-commons-backend/src/modules/mail/mail.service.ts @@ -3,7 +3,7 @@ import type Mail from 'nodemailer/lib/mailer/index.js'; export default function makeMailService({ logger }: Dependencies) { return { - async sendMail(content: Mail) { + async sendMail(content: Mail.Options) { transporter.sendMail(content, (error, info) => { if (error) { logger.error({ name: 'Mail Service', message: 'Failed to send email', error, info }); diff --git a/apps/modeling-commons-backend/src/modules/model-comment/model-comment.service.ts b/apps/modeling-commons-backend/src/modules/model-comment/model-comment.service.ts index c8cb1eae..ce49950f 100644 --- a/apps/modeling-commons-backend/src/modules/model-comment/model-comment.service.ts +++ b/apps/modeling-commons-backend/src/modules/model-comment/model-comment.service.ts @@ -43,8 +43,8 @@ export default function makeModelCommentService({ mailDomain, logger, }: Dependencies) { - // No dedicated "new comment" template yet (plan §15 item 5 is polish); the - // unsubscribe link is a placeholder until a real preferences endpoint exists. + // No dedicated "new comment" template yet (plan §15 item 5 is polish); until a + // real unsubscribe/preferences endpoint exists, point at the support inbox. async function notifyOnNewComment(entity: ModelCommentEntity, parent?: ModelCommentEntity) { try { const authors = await modelAuthorRepository.findAllByModel(entity.modelId); @@ -65,12 +65,9 @@ export default function makeModelCommentService({ recipient.email, recipient.name ?? 'there', 'Someone commented on a model you are involved with.', - `${env.product.website}/unsubscribe`, + `mailto:${env.product.supportEmail}`, ); - // mailDomain returns a plain { from, to, subject, html, text } object; mailService's - // param type comes straight from nodemailer's Mail class, which no other caller in - // this codebase satisfies structurally either — cast rather than widen that signature here. - await mailService.sendMail(content as never); + await mailService.sendMail(content); }), ); From 662127e6fa214fd25b6f1d47578f54bab82b45f8 Mon Sep 17 00:00:00 2001 From: Omar Ibrahim Date: Mon, 20 Jul 2026 14:48:20 -0500 Subject: [PATCH 25/51] fix(commons): drop tombstone comments with only filtered-out replies expandCommentTree exposed replies.count from the raw DB page.count even after childless tombstone replies were filtered from replies.data. A deleted node whose only reply was itself a childless tombstone kept replies.count:1 with an empty data array, so the top-level filter (which keys off replies !== undefined) wrongly kept the tombstone, and the frontend rendered a "Continue this thread (1)" that fetched nothing. Now the count is corrected for replies dropped from the fetched page, and replies is left unset when the corrected count is 0 - consistent for both deleted and live nodes, and shared by get-comment.query.ts via the same expandCommentTree. --- .../queries/get-comment.query.spec.ts | 28 ++++++++++++ .../queries/list-comments.query.spec.ts | 44 +++++++++++++++++++ .../queries/list-comments.query.ts | 41 ++++++++++------- 3 files changed, 98 insertions(+), 15 deletions(-) diff --git a/apps/modeling-commons-backend/src/modules/model-comment/queries/get-comment.query.spec.ts b/apps/modeling-commons-backend/src/modules/model-comment/queries/get-comment.query.spec.ts index 4cf38e06..c63de4a5 100644 --- a/apps/modeling-commons-backend/src/modules/model-comment/queries/get-comment.query.spec.ts +++ b/apps/modeling-commons-backend/src/modules/model-comment/queries/get-comment.query.spec.ts @@ -117,6 +117,34 @@ describe('getCommentQuery', () => { expect(result.permissions).toEqual({ canEdit: true, canDelete: true }); }); + it('drops a deleted child whose only reply is itself a childless tombstone, from the re-rooted comment', async () => { + const { query, modelCommentRepository } = buildQuery(); + const target = makeEntity({ id: 'target-1' }); + const tombstoneChild = makeEntity({ + id: 'dead-child', + parentId: 'target-1', + content: null, + deletedAt: new Date(), + }); + const tombstoneGrandchild = makeEntity({ + id: 'dead-grandchild', + parentId: 'dead-child', + content: null, + deletedAt: new Date(), + }); + + modelCommentRepository.findById.mockResolvedValue(target); + modelCommentRepository.listReplies.mockImplementation(async (parentId: string) => { + if (parentId === 'target-1') return page([tombstoneChild], 1, 20, 0); + if (parentId === 'dead-child') return page([tombstoneGrandchild], 1); + return page([], 0); + }); + + const result = await query.execute('target-1', {}); + + expect(result.replies).toBeUndefined(); + }); + it('bounds deeper levels to the standard 2-per-level embed even though the root page uses a bigger limit', async () => { const { query, modelCommentRepository } = buildQuery(); const target = makeEntity({ id: 'target-1' }); diff --git a/apps/modeling-commons-backend/src/modules/model-comment/queries/list-comments.query.spec.ts b/apps/modeling-commons-backend/src/modules/model-comment/queries/list-comments.query.spec.ts index 255bbe4e..8d092188 100644 --- a/apps/modeling-commons-backend/src/modules/model-comment/queries/list-comments.query.spec.ts +++ b/apps/modeling-commons-backend/src/modules/model-comment/queries/list-comments.query.spec.ts @@ -216,6 +216,50 @@ describe('listCommentsQuery', () => { expect(d2Dto.replies!.data[0]!.replies).toEqual({ count: 3, limit: 2, page: 0, data: [] }); }); + it('drops a deleted top-level comment whose only reply is itself a childless tombstone', async () => { + const { query, modelCommentRepository } = buildQuery(); + const tombstoneRoot = makeEntity({ id: 'dead-root', content: null, deletedAt: new Date() }); + const tombstoneReply = makeEntity({ + id: 'dead-reply', + parentId: 'dead-root', + content: null, + deletedAt: new Date(), + }); + + modelCommentRepository.listTopLevel.mockResolvedValue(page([tombstoneRoot], 1)); + modelCommentRepository.listReplies.mockImplementation(async (parentId: string) => + parentId === 'dead-root' ? page([tombstoneReply], 1) : page([], 0), + ); + + const result = await query.execute('model-1', {}); + + expect(result.data).toHaveLength(0); + }); + + it('keeps a deleted top-level comment whose reply is a live comment, reporting the corrected count', async () => { + const { query, modelCommentRepository } = buildQuery(); + const tombstoneRoot = makeEntity({ id: 'dead-root', content: null, deletedAt: new Date() }); + const liveReply = makeEntity({ id: 'live-reply', parentId: 'dead-root' }); + const tombstoneReply = makeEntity({ + id: 'dead-reply', + parentId: 'dead-root', + content: null, + deletedAt: new Date(), + }); + + modelCommentRepository.listTopLevel.mockResolvedValue(page([tombstoneRoot], 1)); + modelCommentRepository.listReplies.mockImplementation(async (parentId: string) => + parentId === 'dead-root' ? page([liveReply, tombstoneReply], 2) : page([], 0), + ); + + const result = await query.execute('model-1', {}); + + expect(result.data).toHaveLength(1); + expect(result.data[0]!.id).toBe('dead-root'); + expect(result.data[0]!.replies!.data.map((c) => c.id)).toEqual(['live-reply']); + expect(result.data[0]!.replies!.count).toBe(1); + }); + it('drops a childless deleted reply from a parent’s embedded replies (nested, not just top-level)', async () => { const { query, modelCommentRepository } = buildQuery(); const root = makeEntity({ id: 'root' }); diff --git a/apps/modeling-commons-backend/src/modules/model-comment/queries/list-comments.query.ts b/apps/modeling-commons-backend/src/modules/model-comment/queries/list-comments.query.ts index d71d88df..7e44af16 100644 --- a/apps/modeling-commons-backend/src/modules/model-comment/queries/list-comments.query.ts +++ b/apps/modeling-commons-backend/src/modules/model-comment/queries/list-comments.query.ts @@ -43,7 +43,12 @@ type CommentTreeDeps = { // The deepest expanded level's own children aren't fetched, only counted // (`countRepliesByParent`), so the UI can offer "continue this thread (N)". // A childless deleted node is dropped from its parent's `replies.data` -// (decision #9); a deleted node with replies is kept. +// (decision #9); a deleted node with replies is kept. `page.count` is the raw +// DB total for this node's replies, which can include childless tombstones +// that get dropped from the fetched page — `visibleCount` corrects for those +// so a node whose only replies were dropped tombstones reports 0 (and thus +// isn't itself kept as a "has visible replies" tombstone), instead of leaking +// a `replies.count` that promises data a follow-up fetch can't produce. export async function expandCommentTree( deps: CommentTreeDeps, entity: ModelCommentEntity, @@ -57,6 +62,7 @@ export async function expandCommentTree( const nextDepth = depth + 1; let data: Array; + let droppedInPage: number; if (nextDepth < COMMENT_TREE_DEFAULTS.maximumNested) { const expandedPairs = await Promise.all( @@ -65,25 +71,30 @@ export async function expandCommentTree( childDto: await expandCommentTree(deps, child, nextDepth, ctx, EMBED_PARAMS), })), ); - data = expandedPairs - .filter(({ child, childDto }) => !(child.deletedAt !== null && childDto.replies === undefined)) - .map(({ childDto }) => childDto); + const kept = expandedPairs.filter( + ({ child, childDto }) => !(child.deletedAt !== null && childDto.replies === undefined), + ); + data = kept.map(({ childDto }) => childDto); + droppedInPage = expandedPairs.length - kept.length; } else { const ids = page.data.map((child) => child.id); const counts = await deps.modelCommentRepository.countRepliesByParent(ids); - data = page.data - .filter((child) => !(child.deletedAt !== null && (counts.get(child.id) ?? 0) === 0)) - .map((child) => { - const childDto = deps.modelCommentMapper.toResponse(child, ctx); - const count = counts.get(child.id) ?? 0; - if (count > 0) { - childDto.replies = { count, limit: EMBED_LIMIT, page: 0, data: [] }; - } - return childDto; - }); + const kept = page.data.filter((child) => !(child.deletedAt !== null && (counts.get(child.id) ?? 0) === 0)); + data = kept.map((child) => { + const childDto = deps.modelCommentMapper.toResponse(child, ctx); + const count = counts.get(child.id) ?? 0; + if (count > 0) { + childDto.replies = { count, limit: EMBED_LIMIT, page: 0, data: [] }; + } + return childDto; + }); + droppedInPage = page.data.length - kept.length; } - dto.replies = { count: page.count, limit: page.limit, page: page.page, data }; + const visibleCount = page.count - droppedInPage; + if (visibleCount === 0) return dto; + + dto.replies = { count: visibleCount, limit: page.limit, page: page.page, data }; return dto; } From 1b6a9319ecff7fa199c3de99b4c1e9fa9c0e07ab Mon Sep 17 00:00:00 2001 From: Omar Ibrahim Date: Mon, 20 Jul 2026 14:50:14 -0500 Subject: [PATCH 26/51] docs(commons): record model-comment backend decisions --- .claude/DECISIONS.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.claude/DECISIONS.md b/.claude/DECISIONS.md index 69ad4ab5..65e2ca07 100644 --- a/.claude/DECISIONS.md +++ b/.claude/DECISIONS.md @@ -147,3 +147,14 @@ Critical, non-obvious decisions made while working in this repo. Newest first. 2. **The avatar's profile link wrapper is `class="contents"`** so the anchor is layout-inert: the `UAvatar` keeps its own positioning classes and continues to anchor the spine/elbow drawing exactly as before. 3. **Fixture authors carry `/users/user-` profile urls** matching the `/users/:id` route convention; the metadata bar's pre-existing name link lights up from the same `author.url`. 4. **The continue-thread button now carries `data-testid="continue-thread-link"`** — date links share the `/models//comments/` href shape, so href-only selectors no longer disambiguate it in tests. + +## 2026-07-20 — Comments backend: `model-comment` module (greenfield) + +**Context:** New DDD module `apps/modeling-commons-backend/src/modules/model-comment/` implementing the plan in `apps/modeling-commons-backend/doc/discussion-plan.md`. Routes nested under `/v1/models/:id/comments`. + +**Decisions:** +1. **Cross-model authorization is a dedicated preHandler hook** `src/shared/hooks/resolve-comment.ts` (`resolveComment()`), applied to all five `:commentId` routes after `resolveModel('read')`. It loads the comment and 404s (`CommentNotFoundError`) when it's missing or `comment.modelId !== params.id`. This closes an IDOR on `GET .../comments/:commentId` (the read path previously loaded by id globally). The service's own `modelId` re-check on writes stays as defense-in-depth. A generic `resolveModelResource` exists but the bespoke hook was chosen to throw the module's own not-found error. +2. **Deliberate corrections to the plan (repo reality, not deviations to fix):** routes use `:id` not `:modelId` (`resolveModel` reads `request.params.id`); 400s use `ArgumentInvalidException` (repo has no `BadRequestException`); the recursive response DTO uses `Type.Cyclic`/`Type.Ref` (this repo's `typebox` v1.1.x has no `Type.Recursive`); private-model access returns **403** not 404 (`resolveModel` always throws `ForbiddenException`). +3. **`addLikeTx` is insert-and-catch-`P2002`**, not check-then-act, so concurrent duplicate likes are an idempotent no-op instead of a 500. +4. **`mailService.sendMail` param type corrected** from nodemailer's `Mail` class to `Mail.Options` (the real seam bug behind the former `as never` cast). New-comment notification reuses `createNotificationEmail`; a dedicated `createNewCommentEmail` template remains a polish follow-up. No unsubscribe route exists, so the email links `mailto:${env.product.supportEmail}`. +5. **Migration `20260720000000_add_model_comment` was hand-authored** (Docker/postgres unavailable in the authoring environment) and is **NOT yet applied**; the SQL was reviewed as faithful to `schema.prisma` but must be applied (`yarn db:migrate:dev`) and ideally sanity-checked with `prisma migrate diff` before the cucumber e2e (`tests/api/model-comment.*`) can run. From c6db513f357ad992bb96b41c3d3a30788dcc1917 Mon Sep 17 00:00:00 2001 From: Omar Ibrahim Date: Mon, 20 Jul 2026 15:14:20 -0500 Subject: [PATCH 27/51] feat(commons): wire model discussion tab to comments backend --- .claude/DECISIONS.md | 12 + .../comment/CommentsSection.test.ts | 112 +++++--- .../components/comment/CommentsSection.vue | 242 +++++++++++++----- .../app/components/comment/bugs.test.ts | 8 +- .../components/model/detail/ModelDetail.vue | 2 +- .../model/detail/ModelDiscussionTab.test.ts | 30 ++- .../model/detail/ModelDiscussionTab.vue | 33 ++- .../comments/useCommentMutations.ts | 40 +++ .../app/composables/comments/useComments.ts | 171 ++++++++++--- .../models/[id]/comments/[commentId].vue | 7 +- .../tests/helpers/comment.ts | 110 ++++++++ .../nuxt/composables/useComments.test.ts | 75 +++--- .../tests/nuxt/pages/comment-thread.test.ts | 9 +- 13 files changed, 654 insertions(+), 197 deletions(-) create mode 100644 apps/modeling-commons-frontend/app/composables/comments/useCommentMutations.ts diff --git a/.claude/DECISIONS.md b/.claude/DECISIONS.md index 65e2ca07..fb6e0278 100644 --- a/.claude/DECISIONS.md +++ b/.claude/DECISIONS.md @@ -158,3 +158,15 @@ Critical, non-obvious decisions made while working in this repo. Newest first. 3. **`addLikeTx` is insert-and-catch-`P2002`**, not check-then-act, so concurrent duplicate likes are an idempotent no-op instead of a 500. 4. **`mailService.sendMail` param type corrected** from nodemailer's `Mail` class to `Mail.Options` (the real seam bug behind the former `as never` cast). New-comment notification reuses `createNotificationEmail`; a dedicated `createNewCommentEmail` template remains a polish follow-up. No unsubscribe route exists, so the email links `mailto:${env.product.supportEmail}`. 5. **Migration `20260720000000_add_model_comment` was hand-authored** (Docker/postgres unavailable in the authoring environment) and is **NOT yet applied**; the SQL was reviewed as faithful to `schema.prisma` but must be applied (`yarn db:migrate:dev`) and ideally sanity-checked with `prisma migrate diff` before the cucumber e2e (`tests/api/model-comment.*`) can run. + +## 2026-07-20 — Comments frontend: wire `ModelDiscussionTab` to the live backend + +**Context:** `apps/modeling-commons-frontend`. The comment UI (`CommentsSection`/`CommentsPanel`/`useComments`) was built against fixtures with an optimistic-local-only mutation seam; `ModelDiscussionTab` showed a "coming soon" placeholder. Backend routes under `/api/v1/models/:id/comments[/:commentId][/like]` now exist, so the read/write layers were connected end-to-end. + +**Decisions:** +1. **Raw `fetch` (not the typed `useApi` client).** The comment routes aren't in `shared/types/api.d.ts` yet, so per the frontend CLAUDE.md convention the read fetchers (`fetchModelComments`/`fetchComment` in `useComments.ts`) and `useCommentMutations.ts` use `fetch(apiBase + path, { credentials: "include" })`. Migrate to `api.METHOD(...)` after `yarn generate:types`. +2. **`CommentsSource` now carries `modelId` always, `commentId` optional.** The backend thread endpoint is `GET /models/:id/comments/:commentId` (needs both). Rule: `commentId` present -> thread mode; else model-list. Dropped the old "exactly one of modelId/commentId" invariant; `CommentsSection` requires a `modelId` and the thread page passes both. +3. **Sort exposes only `Oldest` (`createdAt`) and `Most liked` (`likes`).** The backend hardcodes sort direction (`createdAt`/`updatedAt` ascending, `likes` descending), so "Newest first" (createdAt desc) is impossible without a backend change; `updatedAt` was omitted because ascending updatedAt has no meaningful user-facing label. `filterOptions` -> sort control, "Filter by" -> "Sort by". +4. **Reply paging keeps chronological (default) order, not the top-level sort.** `handleLoad` fetches the next reply page via the re-rooted get-comment endpoint with `limit = maximumShownRepliesPerLevel` and no `sort`, appending deduped replies; passing the top-level `likes` sort would interleave inconsistently with the createdAt-ascending embedded first page. +5. **Mutations are optimistic with snapshot rollback + error toast.** `runOptimistic(apply, persist)` snapshots `local.comments`/`local.pagination`, applies the local change, awaits the API call, and restores the snapshot + toasts on failure. Create/reply swap the temporary `local-*` id for the server id on success. A single `busy` gate serializes mutations (a dropped rapid re-entry is acceptable for v1). +6. **Tests mock `global.fetch` via `installCommentFetchMock` (tests/helpers/comment.ts).** A `toApiComment` reverse-mapper reuses the existing frontend fixtures as backend-shaped responses, so the fixture ids/tree structure still drive the read + mutation assertions. diff --git a/apps/modeling-commons-frontend/app/components/comment/CommentsSection.test.ts b/apps/modeling-commons-frontend/app/components/comment/CommentsSection.test.ts index 58e1c2a9..a1e0b470 100644 --- a/apps/modeling-commons-frontend/app/components/comment/CommentsSection.test.ts +++ b/apps/modeling-commons-frontend/app/components/comment/CommentsSection.test.ts @@ -1,6 +1,6 @@ import { mockNuxtImport } from "@nuxt/test-utils/runtime"; import { flushPromises } from "@vue/test-utils"; -import { beforeEach, describe, expect, it } from "vitest"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { nextTick } from "vue"; import CommentView from "./CommentView.vue"; import CommentsPanel from "./CommentsPanel.vue"; @@ -8,6 +8,8 @@ import { findCommentById } from "./comment-tree"; import { comments as fixtureComments } from "./fixtures"; import type { Comment, CommentPagination } from "./types"; import { + commentFetchCalls, + installCommentFetchMock, mountCommentsSection, resetCommentMocks, routerReplaceMock, @@ -29,6 +31,11 @@ mockNuxtImport("useRouter", () => () => useRouterMock()); beforeEach(() => { resetCommentMocks(); + installCommentFetchMock(); +}); + +afterEach(() => { + vi.unstubAllGlobals(); }); function panelComments(wrapper: Awaited>) { @@ -45,51 +52,49 @@ async function emitFromPanel( payload?: unknown, ) { wrapper.getComponent(CommentsPanel).vm.$emit(event, payload); + await flushPromises(); await nextTick(); } +function calls(method: string) { + return commentFetchCalls().filter((call) => call.method === method); +} + describe("CommentsSection sources", () => { - it("renders the panel with the fixture comments for a modelId", async () => { + it("renders the panel with the mapped comments for a modelId", async () => { const wrapper = await mountCommentsSection({ modelId: "model-1" }); const comments = panelComments(wrapper); expect(comments.map((comment) => comment.id)).toEqual( fixtureComments.map((comment) => comment.id), ); - expect(panelPagination(wrapper)).toEqual({ count: fixtureComments.length, lastPage: 1 }); + expect(panelPagination(wrapper)).toEqual({ count: fixtureComments.length, lastPage: 0 }); }); it("renders a single thread rooted at the given commentId", async () => { - const wrapper = await mountCommentsSection({ commentId: "100" }); + const wrapper = await mountCommentsSection({ modelId: "model-1", commentId: "100" }); const comments = panelComments(wrapper); expect(comments).toHaveLength(1); expect(comments[0]?.id).toBe("100"); expect(comments[0]?.replies?.length).toBeGreaterThan(0); - expect(panelPagination(wrapper)).toEqual({ count: 1, lastPage: 1 }); + expect(panelPagination(wrapper)).toEqual({ count: 1, lastPage: 0 }); }); it("roots the thread at a nested commentId", async () => { - const wrapper = await mountCommentsSection({ commentId: "103" }); + const wrapper = await mountCommentsSection({ modelId: "model-1", commentId: "103" }); - const comments = panelComments(wrapper); - expect(comments[0]?.id).toBe("103"); + expect(panelComments(wrapper)[0]?.id).toBe("103"); }); it("renders an empty panel for an unknown commentId", async () => { - const wrapper = await mountCommentsSection({ commentId: "does-not-exist" }); + const wrapper = await mountCommentsSection({ modelId: "model-1", commentId: "does-not-exist" }); expect(panelComments(wrapper)).toEqual([]); expect(panelPagination(wrapper)).toEqual({ count: 0, lastPage: 0 }); }); - it("prefers modelId when both sources are given", async () => { - const wrapper = await mountCommentsSection({ modelId: "model-1", commentId: "100" }); - - expect(panelComments(wrapper)).toHaveLength(fixtureComments.length); - }); - - it("renders nothing when neither source is given", async () => { + it("renders nothing when no modelId is given", async () => { const wrapper = await mountCommentsSection({}); expect(wrapper.findComponent(CommentsPanel).exists()).toBe(false); @@ -160,16 +165,6 @@ describe("CommentsSection highlight", () => { expect(routerReplaceMock).toHaveBeenCalledWith({ query: { page: "2" } }); }); - it("highlights nothing for a non-matching id", async () => { - setRouteQuery({ highlightedCommentId: "does-not-exist" }); - const wrapper = await mountCommentsSection({ modelId: "model-1" }); - - expect(wrapper.getComponent(CommentsPanel).props("highlightedCommentId")).toBe( - "does-not-exist", - ); - expect(wrapper.find('[tabindex="-1"]').exists()).toBe(false); - }); - it("ignores array and empty highlight query values", async () => { setRouteQuery({ highlightedCommentId: ["5", "7"] }); const fromArray = await mountCommentsSection({ modelId: "model-1" }); @@ -182,7 +177,7 @@ describe("CommentsSection highlight", () => { }); describe("CommentsSection optimistic mutations", () => { - it("prepends a created comment authored by the current profile", async () => { + it("prepends a created comment and swaps in the server id", async () => { const wrapper = await mountCommentsSection({ modelId: "model-1" }); await emitFromPanel(wrapper, "create", { content: "Fresh take" }); @@ -190,13 +185,16 @@ describe("CommentsSection optimistic mutations", () => { const comments = panelComments(wrapper); expect(comments).toHaveLength(fixtureComments.length + 1); expect(comments[0]).toMatchObject({ - id: "local-1", + id: "server-1", content: "Fresh take", likes: 0, author: { name: "Ada Lovelace" }, permissions: { canEdit: true, canDelete: true }, }); expect(panelPagination(wrapper).count).toBe(fixtureComments.length + 1); + + const post = calls("POST").find((call) => call.url.endsWith("/comments")); + expect(post?.body).toEqual({ content: "Fresh take" }); }); it("inserts a reply into the target comment and bumps its reply count", async () => { @@ -205,12 +203,15 @@ describe("CommentsSection optimistic mutations", () => { await emitFromPanel(wrapper, "reply", { commentId: "1", content: "Replying here" }); const parent = findCommentById(panelComments(wrapper), "1"); - expect(parent?.replies?.[0]).toMatchObject({ id: "local-1", content: "Replying here" }); + expect(parent?.replies?.[0]).toMatchObject({ id: "server-1", content: "Replying here" }); expect(parent?.replyPagination?.count).toBe(3); expect(panelPagination(wrapper).count).toBe(fixtureComments.length); + + const post = calls("POST").find((call) => call.url.endsWith("/comments")); + expect(post?.body).toEqual({ content: "Replying here", parentId: "1" }); }); - it("edits a comment's content and marks it edited", async () => { + it("edits a comment's content, marks it edited, and PATCHes it", async () => { const wrapper = await mountCommentsSection({ modelId: "model-1" }); await emitFromPanel(wrapper, "edit", { commentId: "5", content: "Nicer." }); @@ -218,21 +219,27 @@ describe("CommentsSection optimistic mutations", () => { const edited = findCommentById(panelComments(wrapper), "5"); expect(edited?.content).toBe("Nicer."); expect(edited?.edited).toBe(true); + + const patch = calls("PATCH")[0]; + expect(patch?.url).toContain("/comments/5"); + expect(patch?.body).toEqual({ content: "Nicer." }); }); - it("applies like and unlike to the target comment", async () => { + it("applies like and unlike to the target comment and hits the like endpoint", async () => { const wrapper = await mountCommentsSection({ modelId: "model-1" }); await emitFromPanel(wrapper, "like", { commentId: "5" }); let target = findCommentById(panelComments(wrapper), "5"); expect(target).toMatchObject({ likes: 2, likedByMe: true }); + expect(calls("POST").some((call) => call.url.endsWith("/comments/5/like"))).toBe(true); await emitFromPanel(wrapper, "unlike", { commentId: "5" }); target = findCommentById(panelComments(wrapper), "5"); expect(target).toMatchObject({ likes: 1, likedByMe: false }); + expect(calls("DELETE").some((call) => call.url.endsWith("/comments/5/like"))).toBe(true); }); - it("removes a deleted top-level comment and decrements the pagination count", async () => { + it("removes a deleted top-level comment, decrements pagination, and DELETEs it", async () => { const wrapper = await mountCommentsSection({ modelId: "model-1" }); await emitFromPanel(wrapper, "delete", { commentId: "5" }); @@ -240,6 +247,7 @@ describe("CommentsSection optimistic mutations", () => { expect(findCommentById(panelComments(wrapper), "5")).toBeNull(); expect(panelComments(wrapper)).toHaveLength(fixtureComments.length - 1); expect(panelPagination(wrapper).count).toBe(fixtureComments.length - 1); + expect(calls("DELETE").some((call) => call.url.endsWith("/comments/5"))).toBe(true); }); it("removes a nested comment and decrements only its parent's reply count", async () => { @@ -254,6 +262,19 @@ describe("CommentsSection optimistic mutations", () => { expect(panelPagination(wrapper).count).toBe(fixtureComments.length); }); + it("rolls back and toasts when the persist call fails", async () => { + installCommentFetchMock({ + fail: ({ method, path }) => method === "POST" && path.endsWith("/comments"), + }); + const wrapper = await mountCommentsSection({ modelId: "model-1" }); + + await emitFromPanel(wrapper, "create", { content: "Doomed" }); + + expect(panelComments(wrapper)).toHaveLength(fixtureComments.length); + expect(findCommentById(panelComments(wrapper), "server-1")).toBeNull(); + expect(toastAddMock).toHaveBeenCalledTimes(1); + }); + it("does not wedge after consecutive mutations", async () => { const wrapper = await mountCommentsSection({ modelId: "model-1" }); @@ -267,15 +288,15 @@ describe("CommentsSection optimistic mutations", () => { expect(comments[0]?.content).toBe("Still responsive"); }); - it("assigns unique local ids to successive creations", async () => { + it("assigns distinct server ids to successive creations", async () => { const wrapper = await mountCommentsSection({ modelId: "model-1" }); await emitFromPanel(wrapper, "create", { content: "First" }); await emitFromPanel(wrapper, "reply", { commentId: "7", content: "Second" }); const comments = panelComments(wrapper); - expect(comments[0]?.id).toBe("local-1"); - expect(findCommentById(comments, "7")?.replies?.[0]?.id).toBe("local-2"); + expect(comments[0]?.id).toBe("server-1"); + expect(findCommentById(comments, "7")?.replies?.[0]?.id).toBe("server-2"); }); it("resets local optimistic state when the source changes", async () => { @@ -289,15 +310,28 @@ describe("CommentsSection optimistic mutations", () => { await nextTick(); expect(panelComments(wrapper)).toHaveLength(fixtureComments.length); - expect(findCommentById(panelComments(wrapper), "local-1")).toBeNull(); + expect(findCommentById(panelComments(wrapper), "server-1")).toBeNull(); }); +}); - it("keeps load and load-more as inert seams", async () => { +describe("CommentsSection pagination seams", () => { + it("requests the next reply page for a comment on load", async () => { const wrapper = await mountCommentsSection({ modelId: "model-1" }); await emitFromPanel(wrapper, "load", { commentId: "1" }); - await emitFromPanel(wrapper, "load-more", panelPagination(wrapper)); - expect(panelComments(wrapper)).toHaveLength(fixtureComments.length); + const replyFetch = calls("GET").find((call) => call.url.includes("/comments/1?")); + expect(replyFetch?.url).toContain("page=1"); + expect(replyFetch?.url).toContain("limit=2"); + }); + + it("requests the next comment page on load-more", async () => { + const wrapper = await mountCommentsSection({ modelId: "model-1" }); + + await emitFromPanel(wrapper, "load-more", { count: fixtureComments.length, lastPage: 0 }); + + const pageFetch = calls("GET").find((call) => call.url.includes("page=1")); + expect(pageFetch?.url).toContain("/comments?"); + expect(panelPagination(wrapper).lastPage).toBe(1); }); }); diff --git a/apps/modeling-commons-frontend/app/components/comment/CommentsSection.vue b/apps/modeling-commons-frontend/app/components/comment/CommentsSection.vue index 40ae5bb6..a09859a7 100644 --- a/apps/modeling-commons-frontend/app/components/comment/CommentsSection.vue +++ b/apps/modeling-commons-frontend/app/components/comment/CommentsSection.vue @@ -16,9 +16,7 @@ icon="i-lucide-message-circle-off" title="Comments failed to load" description="Something went wrong while loading the discussion." - :actions="[ - { label: 'Retry', color: 'error', variant: 'outline', onClick: () => refresh() }, - ]" + :actions="[{ label: 'Retry', color: 'error', variant: 'outline', onClick: () => refresh() }]" /> + + Write a comment + diff --git a/apps/modeling-commons-frontend/app/components/comment/bugs.test.ts b/apps/modeling-commons-frontend/app/components/comment/bugs.test.ts index a7184a25..6f499fc9 100644 --- a/apps/modeling-commons-frontend/app/components/comment/bugs.test.ts +++ b/apps/modeling-commons-frontend/app/components/comment/bugs.test.ts @@ -2,7 +2,7 @@ * Regression suite for the nested-comments feature. */ import { mockNuxtImport, mountSuspended } from "@nuxt/test-utils/runtime"; -import { beforeEach, describe, expect, it } from "vitest"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { nextTick } from "vue"; import { UModal } from "#components"; import CommentActions from "./CommentActions.vue"; @@ -13,6 +13,7 @@ import CommentView from "./CommentView.vue"; import ConfirmDeleteCommentDialog from "./ConfirmDeleteCommentDialog.vue"; import { deepThread, editedComment, longComment, noRepliesComment, shortComment } from "./fixtures"; import { + installCommentFetchMock, mountCommentView, mountCommentsPanel, mountCommentsSection, @@ -30,6 +31,11 @@ const flatComments = [shortComment, editedComment, noRepliesComment]; beforeEach(() => { resetCommentMocks(); + installCommentFetchMock(); +}); + +afterEach(() => { + vi.unstubAllGlobals(); }); describe("CommentsPanel bugs", () => { diff --git a/apps/modeling-commons-frontend/app/components/model/detail/ModelDetail.vue b/apps/modeling-commons-frontend/app/components/model/detail/ModelDetail.vue index d54e1dac..d7f61975 100644 --- a/apps/modeling-commons-frontend/app/components/model/detail/ModelDetail.vue +++ b/apps/modeling-commons-frontend/app/components/model/detail/ModelDetail.vue @@ -86,7 +86,7 @@ @update:model-value="idx => onTabChange(idx)" > diff --git a/apps/modeling-commons-frontend/app/composables/comments/useCommentMutations.ts b/apps/modeling-commons-frontend/app/composables/comments/useCommentMutations.ts new file mode 100644 index 00000000..c9b23e0f --- /dev/null +++ b/apps/modeling-commons-frontend/app/composables/comments/useCommentMutations.ts @@ -0,0 +1,40 @@ +import type { MaybeRefOrGetter } from "vue"; +import { commentsApiBase } from "~/composables/comments/useComments"; + +type CreateInput = { content: string; parentId?: string; versionNumber?: number }; +type TargetInput = { commentId: string; content?: string }; + +export default function useCommentMutations(modelId: MaybeRefOrGetter) { + const base = commentsApiBase(); + const commentsPath = () => `${base}/api/v1/models/${toValue(modelId)}/comments`; + + async function send(url: string, method: string, body?: Record): Promise { + const response = await fetch(url, { + method, + credentials: "include", + headers: body ? { "Content-Type": "application/json" } : undefined, + body: body ? JSON.stringify(body) : undefined, + }); + if (!response.ok) throw new Error(`${method} ${url} failed with ${response.status}`); + return response; + } + + return { + async create({ content, parentId, versionNumber }: CreateInput): Promise<{ id: string }> { + const response = await send(commentsPath(), "POST", { content, parentId, versionNumber }); + return (await response.json()) as { id: string }; + }, + async edit({ commentId, content }: Required): Promise { + await send(`${commentsPath()}/${commentId}`, "PATCH", { content }); + }, + async like({ commentId }: TargetInput): Promise { + await send(`${commentsPath()}/${commentId}/like`, "POST"); + }, + async unlike({ commentId }: TargetInput): Promise { + await send(`${commentsPath()}/${commentId}/like`, "DELETE"); + }, + async remove({ commentId }: TargetInput): Promise { + await send(`${commentsPath()}/${commentId}`, "DELETE"); + }, + }; +} diff --git a/apps/modeling-commons-frontend/app/composables/comments/useComments.ts b/apps/modeling-commons-frontend/app/composables/comments/useComments.ts index a3dea7a3..52ab7b86 100644 --- a/apps/modeling-commons-frontend/app/composables/comments/useComments.ts +++ b/apps/modeling-commons-frontend/app/composables/comments/useComments.ts @@ -1,53 +1,164 @@ import type { MaybeRefOrGetter } from "vue"; -import { findCommentById } from "~/components/comment/comment-tree"; -import { comments as modelCommentsFixture, deepThread } from "~/components/comment/fixtures"; -import type { Comment, CommentPagination } from "~/components/comment/types"; +import type { Comment, CommentAuthor, CommentPagination } from "~/components/comment/types"; -type CommentsSource = { modelId: string } | { commentId: string }; +export type CommentSort = "createdAt" | "likes"; + +type CommentsSource = { modelId: string; commentId?: string }; type CommentsPayload = { comments: Array; pagination: CommentPagination; }; +type CommentQuery = { + page?: number; + limit?: number; + sort?: CommentSort; +}; + +type ApiCommentAuthor = { id: string; name: string; image: string }; + +type ApiRepliesPage = { + count: number; + limit: number; + page: number; + data: Array; +}; + +type ApiComment = { + id: string; + modelId: string; + parentId?: string; + versionNumber?: number; + legacyId?: number; + author: ApiCommentAuthor; + content: string; + createdAt: string; + edited?: boolean; + deleted?: boolean; + likes: number; + likedByMe?: boolean; + permissions?: { canEdit?: boolean; canDelete?: boolean }; + replies?: ApiRepliesPage; +}; + +type ApiPaginated = { + count: number; + limit: number; + page: number; + data: Array; +}; + const emptyPayload = (): CommentsPayload => ({ comments: [], pagination: { count: 0, lastPage: 0 }, }); -// TODO: PLACEHOLDER — there is no comments backend yet. Replace ONLY this -// function's body with the real calls once the endpoints exist: -// { modelId } -> GET /api/v1/models/{id}/comments -// { commentId } -> GET /api/v1/comments/{id}/thread -// Until those routes are in shared/types/api.d.ts, use the repo convention of -// raw `fetch(apiBase + path, { credentials: "include" })` (see -// composables/model/useModelInteractions.ts), then migrate to -// `api.GET(...)` after regenerating types with `yarn generate:types`. -async function fetchComments(source: CommentsSource): Promise { - const fixtureTrees = [...modelCommentsFixture, deepThread]; - - if ("modelId" in source) { - if (!source.modelId) return emptyPayload(); - const comments = structuredClone(modelCommentsFixture); - return { comments, pagination: { count: comments.length, lastPage: 1 } }; +// `lastPage` carries the last page index actually loaded (backend pages are +// 0-indexed); the pagination helpers derive "load N more" from `count` minus +// the number of items already in hand. +function toPagination(page: ApiRepliesPage | ApiPaginated): CommentPagination { + return { count: page.count, lastPage: page.page }; +} + +function mapAuthor(author: ApiCommentAuthor): CommentAuthor { + return { + name: author.name, + image: author.image, + url: author.id ? `/users/${author.id}` : undefined, + }; +} + +export function mapApiComment(dto: ApiComment): Comment { + return { + id: dto.id, + modelId: dto.modelId, + parentId: dto.parentId, + author: mapAuthor(dto.author), + content: dto.content, + createdAt: dto.createdAt, + edited: dto.edited, + likes: dto.likes, + likedByMe: dto.likedByMe ?? false, + permissions: dto.permissions, + replies: dto.replies ? dto.replies.data.map(mapApiComment) : [], + replyPagination: dto.replies ? toPagination(dto.replies) : { count: 0, lastPage: 0 }, + }; +} + +export function commentsApiBase(): string { + return useRuntimeConfig().public.apiBase as string; +} + +function buildQuery(query: CommentQuery): string { + const params = new URLSearchParams(); + if (query.page !== undefined) params.set("page", String(query.page)); + if (query.limit !== undefined) params.set("limit", String(query.limit)); + if (query.sort !== undefined) params.set("sort", query.sort); + const qs = params.toString(); + return qs ? `?${qs}` : ""; +} + +async function commentGet(base: string, path: string, query: CommentQuery): Promise { + const response = await fetch(`${base}${path}${buildQuery(query)}`, { credentials: "include" }); + if (!response.ok) throw new Error(`GET ${path} failed with ${response.status}`); + return (await response.json()) as T; +} + +export async function fetchModelComments( + base: string, + modelId: string, + query: CommentQuery, +): Promise { + const page = await commentGet(base, `/api/v1/models/${modelId}/comments`, query); + return { comments: page.data.map(mapApiComment), pagination: toPagination(page) }; +} + +// Re-rooted single comment with a bounded reply tree. Used both to render a +// standalone thread and to page a node's replies inline (append `.replies`). +export async function fetchComment( + base: string, + modelId: string, + commentId: string, + query: CommentQuery, +): Promise { + const response = await fetch( + `${base}/api/v1/models/${modelId}/comments/${commentId}${buildQuery(query)}`, + { credentials: "include" }, + ); + if (response.status === 404) return null; + if (!response.ok) throw new Error(`GET comment ${commentId} failed with ${response.status}`); + return mapApiComment((await response.json()) as ApiComment); +} + +async function fetchComments(source: CommentsSource, sort?: CommentSort): Promise { + if (!source.modelId) return emptyPayload(); + const base = commentsApiBase(); + + if (source.commentId) { + const root = await fetchComment(base, source.modelId, source.commentId, { sort }); + return root ? { comments: [root], pagination: { count: 1, lastPage: 0 } } : emptyPayload(); } - const root = source.commentId ? findCommentById(fixtureTrees, source.commentId) : null; - if (!root) return emptyPayload(); - return { comments: [structuredClone(root)], pagination: { count: 1, lastPage: 1 } }; + return fetchModelComments(base, source.modelId, { sort }); } -export default function useComments(source: MaybeRefOrGetter) { +export default function useComments( + source: MaybeRefOrGetter, + sort?: MaybeRefOrGetter, +) { const resolved = computed(() => toValue(source)); - const key = computed(() => - "modelId" in resolved.value - ? `comments:model:${resolved.value.modelId}` - : `comments:thread:${resolved.value.commentId}`, - ); + const resolvedSort = computed(() => toValue(sort)); + const key = computed(() => { + const value = resolved.value; + return value.commentId + ? `comments:thread:${value.modelId}:${value.commentId}` + : `comments:model:${value.modelId}:${resolvedSort.value ?? "createdAt"}`; + }); const { data, status, error, refresh } = useAsyncData( () => key.value, - () => fetchComments(resolved.value), + () => fetchComments(resolved.value, resolvedSort.value), { watch: [key] }, ); @@ -59,4 +170,4 @@ export default function useComments(source: MaybeRefOrGetter) { return { comments, pagination, status, error, refresh }; } -export type { CommentsPayload, CommentsSource }; +export type { CommentsPayload, CommentsSource, CommentQuery }; diff --git a/apps/modeling-commons-frontend/app/pages/models/[id]/comments/[commentId].vue b/apps/modeling-commons-frontend/app/pages/models/[id]/comments/[commentId].vue index 464bdc20..8561c7d9 100644 --- a/apps/modeling-commons-frontend/app/pages/models/[id]/comments/[commentId].vue +++ b/apps/modeling-commons-frontend/app/pages/models/[id]/comments/[commentId].vue @@ -29,7 +29,7 @@

Comment thread

{{ subtitle }}

- + @@ -43,7 +43,10 @@ const route = useRoute(); const modelId = computed(() => String(route.params.id ?? "")); const commentId = computed(() => String(route.params.commentId ?? "")); -const { comments } = useComments(() => ({ commentId: commentId.value })); +const { comments } = useComments(() => ({ + modelId: modelId.value, + commentId: commentId.value, +})); const threadRoot = computed(() => comments.value[0]); const parentThreadLink = computed(() => diff --git a/apps/modeling-commons-frontend/tests/helpers/comment.ts b/apps/modeling-commons-frontend/tests/helpers/comment.ts index 064edc85..50e6c33e 100644 --- a/apps/modeling-commons-frontend/tests/helpers/comment.ts +++ b/apps/modeling-commons-frontend/tests/helpers/comment.ts @@ -5,6 +5,8 @@ import { computed, nextTick, reactive, ref } from "vue"; import CommentView from "~/components/comment/CommentView.vue"; import CommentsPanel from "~/components/comment/CommentsPanel.vue"; import CommentsSection from "~/components/comment/CommentsSection.vue"; +import { findCommentById } from "~/components/comment/comment-tree"; +import { comments as fixtureComments, deepThread } from "~/components/comment/fixtures"; import type { Comment, CommentViewSettings, CommentsPanelProps } from "~/components/comment/types"; import { makeSession, makeUser } from "./fixtures"; @@ -97,9 +99,117 @@ export async function mountCommentsSection(props: { modelId?: string; commentId?: string; readOnly?: boolean; + sort?: "createdAt" | "likes"; }) { const wrapper = await mountSuspended(CommentsSection, { props }); await flushPromises(); await nextTick(); return wrapper; } + +type ApiComment = { + id: string; + modelId: string; + parentId?: string; + author: { id: string; name: string; image: string }; + content: string; + createdAt: string; + edited?: boolean; + likes: number; + likedByMe: boolean; + permissions?: { canEdit?: boolean; canDelete?: boolean }; + replies?: { count: number; limit: number; page: number; data: Array }; +}; + +export function toApiComment(comment: Comment): ApiComment { + const replies = comment.replies ?? []; + return { + id: comment.id, + modelId: comment.modelId ?? "model-demo", + parentId: comment.parentId, + author: { + id: comment.author.url ? comment.author.url.replace("/users/", "") : "", + name: comment.author.name, + image: comment.author.image, + }, + content: comment.content, + createdAt: comment.createdAt, + edited: comment.edited, + likes: comment.likes, + likedByMe: comment.likedByMe ?? false, + permissions: comment.permissions, + replies: replies.length + ? { + count: comment.replyPagination?.count ?? replies.length, + limit: 2, + page: 0, + data: replies.map(toApiComment), + } + : undefined, + }; +} + +function jsonResponse(data: unknown, status = 200): Response { + return new Response(JSON.stringify(data), { + status, + headers: { "Content-Type": "application/json" }, + }); +} + +type FetchCall = { url: string; method: string; body?: Record }; + +let fetchCallLog: Array = []; + +export function commentFetchCalls(): Array { + return fetchCallLog; +} + +type FetchMockConfig = { + listCount?: number; + fail?: (ctx: { method: string; path: string }) => boolean; +}; + +export function installCommentFetchMock(config: FetchMockConfig = {}) { + fetchCallLog = []; + let createSeq = 0; + const trees = [...fixtureComments, deepThread]; + + const mock = vi.fn(async (input: string, init?: RequestInit) => { + const method = init?.method ?? "GET"; + const url = new URL(input); + const path = url.pathname; + const body = init?.body ? JSON.parse(init.body as string) : undefined; + fetchCallLog.push({ url: input, method, body }); + + if (config.fail?.({ method, path })) return new Response(null, { status: 500 }); + + if (method === "PATCH") return new Response(null, { status: 204 }); + if (method === "DELETE") return new Response(null, { status: 204 }); + if (method === "POST" && path.endsWith("/like")) return new Response(null, { status: 204 }); + if (method === "POST" && path.endsWith("/comments")) { + return jsonResponse({ id: `server-${++createSeq}` }, 201); + } + + const threadMatch = path.match(/\/comments\/([^/]+)$/); + if (method === "GET" && threadMatch) { + const root = findCommentById(trees, threadMatch[1]!); + if (!root) return new Response(null, { status: 404 }); + return jsonResponse(toApiComment(root)); + } + + if (method === "GET") { + const data = fixtureComments.map(toApiComment); + return jsonResponse({ + count: config.listCount ?? data.length, + limit: 20, + page: Number(url.searchParams.get("page") ?? 0), + data, + }); + } + + return new Response(null, { status: 204 }); + }); + + vi.stubGlobal("fetch", mock); + return mock; +} diff --git a/apps/modeling-commons-frontend/tests/nuxt/composables/useComments.test.ts b/apps/modeling-commons-frontend/tests/nuxt/composables/useComments.test.ts index 634400dd..5cec8cad 100644 --- a/apps/modeling-commons-frontend/tests/nuxt/composables/useComments.test.ts +++ b/apps/modeling-commons-frontend/tests/nuxt/composables/useComments.test.ts @@ -1,34 +1,38 @@ -import { describe, expect, it, vi } from "vitest"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { nextTick, ref } from "vue"; -import { comments as fixtureComments, deepThread } from "~/components/comment/fixtures"; +import { comments as fixtureComments } from "~/components/comment/fixtures"; import useComments from "~/composables/comments/useComments"; import type { CommentsSource } from "~/composables/comments/useComments"; +import { commentFetchCalls, installCommentFetchMock } from "~~/tests/helpers"; + +beforeEach(() => { + installCommentFetchMock(); +}); + +afterEach(() => { + vi.unstubAllGlobals(); +}); describe("useComments with a modelId source", () => { - it("returns the fixture comments with pagination and a success status", async () => { + it("maps the paginated list response with a success status", async () => { const { comments, pagination, status, error, refresh } = useComments({ modelId: "m-1" }); await refresh(); - expect(comments.value).toEqual(fixtureComments); - expect(pagination.value).toEqual({ count: fixtureComments.length, lastPage: 1 }); + expect(comments.value.map((comment) => comment.id)).toEqual( + fixtureComments.map((comment) => comment.id), + ); + expect(pagination.value).toEqual({ count: fixtureComments.length, lastPage: 0 }); expect(status.value).toBe("success"); expect(error.value).toBeFalsy(); }); - it("returns deep clones so consumers cannot corrupt the fixtures", async () => { - const { comments, refresh } = useComments({ modelId: "m-2" }); + it("maps author ids into profile urls and defaults likedByMe", async () => { + const { comments, refresh } = useComments({ modelId: "m-1" }); await refresh(); const first = comments.value[0]!; - expect(first).not.toBe(fixtureComments[0]); - expect(first.replies?.[0]).not.toBe(fixtureComments[0]!.replies?.[0]); - - first.content = "mutated"; - first.replies?.splice(0); - await refresh(); - - expect(comments.value[0]?.content).toBe(fixtureComments[0]!.content); - expect(comments.value[0]?.replies).toHaveLength(fixtureComments[0]!.replies!.length); + expect(first.author.url).toBe("/users/user-omar"); + expect(typeof first.likedByMe).toBe("boolean"); }); it("returns roots without a parentId", async () => { @@ -39,45 +43,48 @@ describe("useComments with a modelId source", () => { expect(comments.value.every((comment) => comment.parentId === undefined)).toBe(true); }); - it("returns an empty payload for an empty modelId", async () => { + it("forwards the sort key as a query param", async () => { + const { refresh } = useComments({ modelId: "m-1" }, "likes"); + await refresh(); + + const listCall = commentFetchCalls().find((call) => call.method === "GET"); + expect(listCall?.url).toContain("sort=likes"); + }); + + it("returns an empty payload for an empty modelId without fetching", async () => { const { comments, pagination, refresh } = useComments({ modelId: "" }); await refresh(); expect(comments.value).toEqual([]); expect(pagination.value).toEqual({ count: 0, lastPage: 0 }); + expect(commentFetchCalls()).toHaveLength(0); }); }); describe("useComments with a commentId source", () => { it("wraps the found comment as the single root of the thread", async () => { - const { comments, pagination, refresh } = useComments({ commentId: "100" }); + const { comments, pagination, refresh } = useComments({ modelId: "m-1", commentId: "100" }); await refresh(); expect(comments.value).toHaveLength(1); - expect(comments.value[0]).toEqual(deepThread); - expect(comments.value[0]).not.toBe(deepThread); - expect(pagination.value).toEqual({ count: 1, lastPage: 1 }); + expect(comments.value[0]?.id).toBe("100"); + expect(comments.value[0]?.replies?.length).toBeGreaterThan(0); + expect(pagination.value).toEqual({ count: 1, lastPage: 0 }); }); it("roots the thread at a nested comment", async () => { - const { comments, refresh } = useComments({ commentId: "103" }); + const { comments, refresh } = useComments({ modelId: "m-1", commentId: "103" }); await refresh(); expect(comments.value[0]?.id).toBe("103"); expect(comments.value[0]?.replies?.map((reply) => reply.id)).toEqual(["104", "107"]); }); - it("carries the stamped parentId through the thread fetch", async () => { - const { comments, refresh } = useComments({ commentId: "103" }); - await refresh(); - - expect(comments.value[0]?.parentId).toBe("102"); - expect(comments.value[0]?.replies?.length).toBeGreaterThan(0); - expect(comments.value[0]?.replies?.every((reply) => reply.parentId === "103")).toBe(true); - }); - it("returns an empty payload for an unknown commentId", async () => { - const { comments, pagination, status, refresh } = useComments({ commentId: "nope" }); + const { comments, pagination, status, refresh } = useComments({ + modelId: "m-1", + commentId: "nope", + }); await refresh(); expect(comments.value).toEqual([]); @@ -94,7 +101,7 @@ describe("useComments source reactivity", () => { expect(comments.value).toHaveLength(fixtureComments.length); - source.value = { commentId: "100" }; + source.value = { modelId: "m-3", commentId: "100" }; await nextTick(); await vi.waitFor(() => { @@ -105,7 +112,7 @@ describe("useComments source reactivity", () => { it("accepts a getter source", async () => { const id = ref("5"); - const { comments, refresh } = useComments(() => ({ commentId: id.value })); + const { comments, refresh } = useComments(() => ({ modelId: "m-1", commentId: id.value })); await refresh(); expect(comments.value[0]?.id).toBe("5"); diff --git a/apps/modeling-commons-frontend/tests/nuxt/pages/comment-thread.test.ts b/apps/modeling-commons-frontend/tests/nuxt/pages/comment-thread.test.ts index 9be58517..fd8bbea0 100644 --- a/apps/modeling-commons-frontend/tests/nuxt/pages/comment-thread.test.ts +++ b/apps/modeling-commons-frontend/tests/nuxt/pages/comment-thread.test.ts @@ -1,11 +1,11 @@ import { mockNuxtImport, mountSuspended } from "@nuxt/test-utils/runtime"; import { flushPromises } from "@vue/test-utils"; -import { beforeEach, describe, expect, it, vi } from "vitest"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { nextTick } from "vue"; import CommentsSection from "~/components/comment/CommentsSection.vue"; import { findCommentById } from "~/components/comment/comment-tree"; import { deepThread } from "~/components/comment/fixtures"; -import { useProfileMock, useUserMock } from "~~/tests/helpers"; +import { installCommentFetchMock, useProfileMock, useUserMock } from "~~/tests/helpers"; mockNuxtImport("useUser", () => () => useUserMock()); mockNuxtImport("useProfile", () => () => useProfileMock()); @@ -25,6 +25,11 @@ mockNuxtImport("useRoute", () => () => ({ beforeEach(() => { routeState.commentId = "100"; + installCommentFetchMock(); +}); + +afterEach(() => { + vi.unstubAllGlobals(); }); async function mountThreadPage(commentId: string) { From 70b92dd5d6defeb49401007cba07a350a89785c9 Mon Sep 17 00:00:00 2001 From: Omar Ibrahim Date: Mon, 20 Jul 2026 15:17:31 -0500 Subject: [PATCH 28/51] feat(commons): seed model discussion comment threads --- .../prisma/seed/index.ts | 7 +- .../prisma/seed/loaders.ts | 70 ++++++++ .../prisma/seed/manifest/comments.ts | 157 ++++++++++++++++++ .../prisma/seed/manifest/index.ts | 1 + .../prisma/seed/manifest/types.ts | 23 +++ 5 files changed, 257 insertions(+), 1 deletion(-) create mode 100644 apps/modeling-commons-backend/prisma/seed/manifest/comments.ts diff --git a/apps/modeling-commons-backend/prisma/seed/index.ts b/apps/modeling-commons-backend/prisma/seed/index.ts index 27434c41..4c3340dd 100644 --- a/apps/modeling-commons-backend/prisma/seed/index.ts +++ b/apps/modeling-commons-backend/prisma/seed/index.ts @@ -1,11 +1,12 @@ import { prisma } from './providers.js'; import { AssetUploader } from './assets.js'; -import { USERS, TAGS, MODELS, DRAFTS } from './manifest/index.js'; +import { USERS, TAGS, MODELS, COMMENTS, DRAFTS } from './manifest/index.js'; import { loadUsers, loadTags, loadModels, loadEngagement, + loadComments, loadDrafts, type IdMap, } from './loaders.js'; @@ -30,6 +31,10 @@ async function main() { ); const modelIds: IdMap = new Map(models.map((m) => [m.key, m.id])); + + const comments = await loadComments(COMMENTS, users, modelIds); + console.log(` ✓ ${comments.comments} comments, ${comments.likes} comment likes`); + const draftCount = await loadDrafts(DRAFTS, users, modelIds, uploader); console.log(` ✓ ${draftCount} model drafts`); diff --git a/apps/modeling-commons-backend/prisma/seed/loaders.ts b/apps/modeling-commons-backend/prisma/seed/loaders.ts index d8995936..75a4805d 100644 --- a/apps/modeling-commons-backend/prisma/seed/loaders.ts +++ b/apps/modeling-commons-backend/prisma/seed/loaders.ts @@ -4,6 +4,8 @@ import { seedId, seededRandom } from './id.js'; import { AssetUploader, loadNlogox, fakeNlogox, textAsset, type NlogoxAsset } from './assets.js'; import { isRealFile, + type CommentSeed, + type CommentThreadSeed, type DraftSeed, type ModelFileSeed, type ModelSeed, @@ -436,6 +438,74 @@ function buildEvents(models: LoadedModel[], users: IdMap) { return events; } +export async function loadComments( + threads: CommentThreadSeed[], + users: IdMap, + modelIds: IdMap, +): Promise<{ comments: number; likes: number }> { + const HOUR = DAY / 24; + let commentCount = 0; + const likeRows: Array<{ modelCommentId: string; userId: string; createdAt: Date }> = []; + + async function upsertNode(node: CommentSeed, modelId: string, parentId: string | null) { + const id = seedId('comment', node.key); + const createdAt = daysAgo(node.createdDaysAgo ?? 30); + const deleted = node.deleted ?? false; + const likers = deleted ? [] : (node.likedBy ?? []); + + // Reconcile the mutable fields on rerun so manifest edits propagate; the + // parent link and createdAt are fixed on first insert. + const mutable = { + content: deleted ? null : node.content, + likesCount: likers.length, + editedAt: !deleted && node.edited ? new Date(createdAt.getTime() + HOUR) : null, + deletedAt: deleted ? new Date(createdAt.getTime() + 2 * HOUR) : null, + }; + + await prisma.modelComment.upsert({ + where: { id }, + update: mutable, + create: { + id, + legacyId: node.legacyId ?? null, + parentId, + userId: mustGet(users, node.user, 'comment author'), + modelId, + versionNumber: node.versionNumber ?? null, + createdAt, + ...mutable, + }, + }); + commentCount += 1; + + const likeRng = seededRandom(`comment-likes:${node.key}`); + for (const userKey of likers) { + likeRows.push({ + modelCommentId: id, + userId: mustGet(users, userKey, 'comment liker'), + createdAt: new Date(createdAt.getTime() + Math.floor(likeRng() * DAY)), + }); + } + + for (const reply of node.replies ?? []) { + await upsertNode(reply, modelId, id); + } + } + + for (const thread of threads) { + const modelId = mustGet(modelIds, thread.model, 'comment model'); + for (const c of thread.comments) { + await upsertNode(c, modelId, null); + } + } + + if (likeRows.length) { + await prisma.modelCommentLike.createMany({ data: likeRows, skipDuplicates: true }); + } + + return { comments: commentCount, likes: likeRows.length }; +} + export async function loadDrafts( drafts: DraftSeed[], users: IdMap, diff --git a/apps/modeling-commons-backend/prisma/seed/manifest/comments.ts b/apps/modeling-commons-backend/prisma/seed/manifest/comments.ts new file mode 100644 index 00000000..2d231d8c --- /dev/null +++ b/apps/modeling-commons-backend/prisma/seed/manifest/comments.ts @@ -0,0 +1,157 @@ +import type { CommentThreadSeed } from './types.js'; + +// Discussion threads hung off a handful of the most-visited models. The shapes +// deliberately exercise the read UI: a node with more than +// `maximumShownRepliesPerLevel` replies (see-more), a chain deeper than +// `maximumNested` (continue-thread link), an edited comment, likes, a +// version-scoped comment, and a soft-deleted comment kept alive by its replies. +export const COMMENTS: CommentThreadSeed[] = [ + { + model: 'wolf-sheep', + comments: [ + { + key: 'ws-1', + user: 'maria', + content: + 'Is the sheep reproduction rate tuned to any real ecosystem, or are the defaults just illustrative?', + edited: true, + likedBy: ['uri', 'kenji', 'seth'], + createdDaysAgo: 40, + replies: [ + { + key: 'ws-1-1', + user: 'uri', + content: + "They're illustrative — the point is the qualitative predator-prey cycle, not calibrated demographics.", + likedBy: ['maria', 'amara'], + createdDaysAgo: 39, + replies: [ + { + key: 'ws-1-1-1', + user: 'maria', + content: 'That makes sense, thanks for the quick reply!', + createdDaysAgo: 39, + }, + ], + }, + { + key: 'ws-1-2', + user: 'kenji', + content: + 'If you do want realism, swap the constant birth rate for a density-dependent one — it stabilises the oscillation.', + likedBy: ['maria'], + createdDaysAgo: 38, + }, + { + key: 'ws-1-3', + user: 'diego', + content: '+1, ran into the exact same question when I taught with this.', + createdDaysAgo: 37, + }, + ], + }, + { + key: 'ws-2', + user: 'amara', + content: 'Great teaching model. I used it in an intro complexity course and students loved it.', + likedBy: ['uri', 'seth', 'maria', 'priya'], + createdDaysAgo: 20, + }, + { + key: 'ws-3', + user: 'liam', + content: '(removed)', + deleted: true, + createdDaysAgo: 15, + replies: [ + { + key: 'ws-3-1', + user: 'uri', + content: "Not sure what the parent said, but the grass-regrowth variant might be what you're after.", + createdDaysAgo: 14, + }, + ], + }, + ], + }, + { + model: 'fire', + comments: [ + { + key: 'fire-1', + user: 'uri', + content: 'The density slider makes a lovely phase transition right around 59%.', + likedBy: ['seth', 'chen', 'maria', 'kenji', 'amara'], + createdDaysAgo: 60, + replies: [ + { + key: 'fire-1-1', + user: 'seth', + content: 'That percolation threshold is the whole reason I ship this in week one.', + likedBy: ['uri'], + createdDaysAgo: 59, + replies: [ + { + key: 'fire-1-1-1', + user: 'chen', + content: 'Is 59% specific to the von Neumann neighbourhood here?', + createdDaysAgo: 58, + replies: [ + { + key: 'fire-1-1-1-1', + user: 'seth', + content: + 'Yes — go to a Moore neighbourhood and the threshold drops noticeably. Worth a follow-up thread.', + likedBy: ['chen'], + createdDaysAgo: 57, + }, + ], + }, + ], + }, + ], + }, + ], + }, + { + model: 'flocking', + comments: [ + { + key: 'flock-1', + user: 'priya', + content: 'The alignment-vs-cohesion balance is mesmerising to tune. Nice work.', + versionNumber: 1, + likedBy: ['uri'], + createdDaysAgo: 12, + }, + { + key: 'flock-2', + user: 'chen', + content: 'Any appetite for a 3D port? The emergent lanes would be gorgeous in three dimensions.', + createdDaysAgo: 8, + }, + ], + }, + { + model: 'virus-network', + comments: [ + { + key: 'vn-1', + user: 'fatima', + content: 'How does this compare to the compartmental virus model for the same R0?', + likedBy: ['amara'], + createdDaysAgo: 25, + replies: [ + { + key: 'vn-1-1', + user: 'amara', + content: + 'The network version captures super-spreader structure the well-mixed model averages away, so tails are fatter.', + likedBy: ['fatima', 'uri'], + createdDaysAgo: 24, + }, + ], + }, + ], + }, +]; diff --git a/apps/modeling-commons-backend/prisma/seed/manifest/index.ts b/apps/modeling-commons-backend/prisma/seed/manifest/index.ts index c6bbed09..06631f7a 100644 --- a/apps/modeling-commons-backend/prisma/seed/manifest/index.ts +++ b/apps/modeling-commons-backend/prisma/seed/manifest/index.ts @@ -2,4 +2,5 @@ export * from './types.js'; export { USERS } from './users.js'; export { TAGS } from './tags.js'; export { MODELS } from './models.js'; +export { COMMENTS } from './comments.js'; export { DRAFTS } from './drafts.js'; diff --git a/apps/modeling-commons-backend/prisma/seed/manifest/types.ts b/apps/modeling-commons-backend/prisma/seed/manifest/types.ts index 6e8e279d..12b55698 100644 --- a/apps/modeling-commons-backend/prisma/seed/manifest/types.ts +++ b/apps/modeling-commons-backend/prisma/seed/manifest/types.ts @@ -112,6 +112,29 @@ export interface ModelSeed { popularity?: PopularitySeed; } +export interface CommentSeed { + /** Stable slug used to derive the comment id and link replies to parents. */ + key: string; + legacyId?: number; + /** Author user key. Retained even when `deleted` (the read layer hides it). */ + user: string; + content: string; + /** Scopes the comment to a specific model version; omit for general discussion. */ + versionNumber?: number; + edited?: boolean; + /** Soft-delete tombstone: content is nulled and `deletedAt` set. */ + deleted?: boolean; + likedBy?: string[]; + createdDaysAgo?: number; + replies?: CommentSeed[]; +} + +export interface CommentThreadSeed { + /** Model key the thread hangs off. */ + model: string; + comments: CommentSeed[]; +} + export interface DraftSeed { key: string; user: string; From 380390a32f838f9cc003a44635aa89ad059f6394 Mon Sep 17 00:00:00 2001 From: Omar Ibrahim Date: Mon, 20 Jul 2026 15:20:41 -0500 Subject: [PATCH 29/51] chore: gitignore .env.prod to keep its credentials out of git --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index d0f5807f..c8f9403a 100644 --- a/.gitignore +++ b/.gitignore @@ -41,6 +41,7 @@ GITIGNORE_*/**/* .env.local .env.development .env.production +.env.prod apps/modeling-commons-backend/prisma/archive-output apps/modeling-commons-backend/prisma/avatars From 5fe18bc182927852a34d17997134f3671504ef51 Mon Sep 17 00:00:00 2001 From: Omar Ibrahim Date: Mon, 20 Jul 2026 15:20:41 -0500 Subject: [PATCH 30/51] fix(commons): refine discussion empty state and comment avatar size --- .../app/components/comment/CommentView.vue | 4 ++-- .../app/components/comment/CommentsSection.vue | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/modeling-commons-frontend/app/components/comment/CommentView.vue b/apps/modeling-commons-frontend/app/components/comment/CommentView.vue index 281ec5c4..f0fae145 100644 --- a/apps/modeling-commons-frontend/app/components/comment/CommentView.vue +++ b/apps/modeling-commons-frontend/app/components/comment/CommentView.vue @@ -21,7 +21,7 @@ @@ -29,7 +29,7 @@ v-else :src="comment.author.image" :alt="comment.author.name" - size="lg" + size="md" class="self-start my-1" /> diff --git a/apps/modeling-commons-frontend/app/components/comment/CommentsSection.vue b/apps/modeling-commons-frontend/app/components/comment/CommentsSection.vue index a09859a7..a01c4afb 100644 --- a/apps/modeling-commons-frontend/app/components/comment/CommentsSection.vue +++ b/apps/modeling-commons-frontend/app/components/comment/CommentsSection.vue @@ -36,7 +36,7 @@ @highlight-dismiss="dismissHighlight" /> Date: Mon, 20 Jul 2026 15:20:41 -0500 Subject: [PATCH 31/51] chore(commons): add country code lookup for archive importer --- .../prisma/archive/countries.ts | 182 ++++++++++++++++++ 1 file changed, 182 insertions(+) create mode 100644 apps/modeling-commons-backend/prisma/archive/countries.ts diff --git a/apps/modeling-commons-backend/prisma/archive/countries.ts b/apps/modeling-commons-backend/prisma/archive/countries.ts new file mode 100644 index 00000000..aaa6e7c8 --- /dev/null +++ b/apps/modeling-commons-backend/prisma/archive/countries.ts @@ -0,0 +1,182 @@ +const countries: Record = { + 'Costa Rica': 'CR', + Cambodia: 'KH', + Eritrea: 'ER', + Turkey: 'TR', + 'Tanzania, United Republic of': 'TZ', + Cyprus: 'CY', + Slovenia: 'SI', + "Korea, Democratic People's Republic of": 'KP', + 'Cayman Islands': 'KY', + Kuwait: 'KW', + Jamaica: 'JM', + Cameroon: 'CM', + 'Saudi Arabia': 'SA', + 'Cape Verde': 'CV', + Netherlands: 'NL', + Nigeria: 'NG', + Lebanon: 'LB', + 'Taiwan, Province of China': 'TW', + 'Cook Islands': 'CK', + Australia: 'AU', + Uganda: 'UG', + Argentina: 'AR', + Slovakia: 'SK', + Belize: 'BZ', + 'Burkina Faso': 'BF', + 'Russian Federation': 'RU', + 'Syrian Arab Republic': 'SY', + Cuba: 'CU', + Azerbaijan: 'AZ', + Brazil: 'BR', + Mongolia: 'MN', + 'Marshall Islands': 'MH', + Senegal: 'SN', + Hungary: 'HU', + Uzbekistan: 'UZ', + Mozambique: 'MZ', + Mexico: 'MX', + 'Sri Lanka': 'LK', + 'Korea, Republic of': 'KR', + Estonia: 'EE', + Peru: 'PE', + 'Macedonia, The Former Yugoslav Republic Of': 'MK', + Anguilla: 'AI', + Algeria: 'DZ', + Kyrgyzstan: 'KG', + 'Central African Republic': 'CF', + Malaysia: 'MY', + Spain: 'ES', + Serbia: 'RS', + Belarus: 'BY', + Jordan: 'JO', + Zimbabwe: 'ZW', + Ireland: 'IE', + Haiti: 'HT', + 'Bosnia and Herzegowina': 'BA', + Malawi: 'MW', + Iceland: 'IS', + Dominica: 'DM', + 'Puerto Rico': 'PR', + 'Trinidad and Tobago': 'TT', + Austria: 'AT', + Panama: 'PA', + 'South Africa': 'ZA', + 'Bolivia, Plurinational State of': 'BO', + 'Palestinian Territory, Occupied': 'PS', + Somalia: 'SO', + Poland: 'PL', + Antarctica: 'AQ', + Honduras: 'HN', + Albania: 'AL', + Tunisia: 'TN', + Croatia: 'HR', + Luxembourg: 'LU', + Italy: 'IT', + Sweden: 'SE', + 'Cocos (Keeling) Islands': 'CC', + Malta: 'MT', + India: 'IN', + Paraguay: 'PY', + Morocco: 'MA', + Philippines: 'PH', + Qatar: 'QA', + 'Virgin Islands, British': 'VG', + Sudan: 'SD', + 'United Arab Emirates': 'AE', + Bulgaria: 'BG', + Norway: 'NO', + Namibia: 'NA', + Fiji: 'FJ', + Guatemala: 'GT', + Germany: 'DE', + Singapore: 'SG', + Madagascar: 'MG', + Iraq: 'IQ', + France: 'FR', + Colombia: 'CO', + Japan: 'JP', + Reunion: 'RE', + Ecuador: 'EC', + Nepal: 'NP', + 'Viet Nam': 'VN', + Denmark: 'DK', + Bangladesh: 'BD', + Lithuania: 'LT', + Barbados: 'BB', + 'Dominican Republic': 'DO', + Togo: 'TG', + Egypt: 'EG', + Zambia: 'ZM', + 'United States Minor Outlying Islands': 'UM', + Guyana: 'GY', + 'Sierra Leone': 'SL', + Suriname: 'SR', + Bermuda: 'BM', + 'United States': 'US', + Georgia: 'GE', + 'Equatorial Guinea': 'GQ', + Benin: 'BJ', + Angola: 'AO', + Oman: 'OM', + Nicaragua: 'NI', + Turkmenistan: 'TM', + Afghanistan: 'AF', + 'Czech Republic': 'CZ', + Ghana: 'GH', + Gibraltar: 'GI', + Ethiopia: 'ET', + 'Libyan Arab Jamahiriya': 'LY', + Israel: 'IL', + Chile: 'CL', + 'New Zealand': 'NZ', + 'El Salvador': 'SV', + Botswana: 'BW', + Finland: 'FI', + 'Papua New Guinea': 'PG', + 'Antigua And Barbuda': 'AG', + Thailand: 'TH', + Latvia: 'LV', + Montenegro: 'ME', + Venezuela: 'VE', + Bahrain: 'BH', + 'Iran, Islamic Republic of': 'IR', + 'British Indian Ocean Territory': 'IO', + Aruba: 'AW', + Uruguay: 'UY', + Macao: 'MO', + Lesotho: 'LS', + Ukraine: 'UA', + 'Hong Kong': 'HK', + Portugal: 'PT', + 'Norfolk Island': 'NF', + Romania: 'RO', + Liberia: 'LR', + Canada: 'CA', + China: 'CN', + 'Aland Islands': 'AX', + Tajikistan: 'TJ', + Kenya: 'KE', + Mauritius: 'MU', + 'United Kingdom': 'GB', + 'Brunei Darussalam': 'BN', + Kazakhstan: 'KZ', + 'Moldova, Republic of': 'MD', + 'Saint Kitts and Nevis': 'KN', + Pakistan: 'PK', + Armenia: 'AM', + Greece: 'GR', + Myanmar: 'MM', + Guam: 'GU', + Yemen: 'YE', + Andorra: 'AD', + Greenland: 'GL', + Switzerland: 'CH', + Indonesia: 'ID', + Belgium: 'BE', + Bolivia: 'BO', + Rwanda: 'RW', + Mali: 'ML', +}; + +export default countries; From 9b14eba24250a4ae98705034a373053cee9807cf Mon Sep 17 00:00:00 2001 From: Omar Ibrahim Date: Mon, 20 Jul 2026 15:22:28 -0500 Subject: [PATCH 32/51] bugfix(commons): adjust visibility of thread spine on top of another component --- .../app/components/comment/CommentElbowSvg.vue | 2 +- .../app/components/comment/CommentSpine.vue | 2 +- .../app/components/comment/CommentSpineEraser.vue | 2 +- .../app/components/comment/CommentView.vue | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/modeling-commons-frontend/app/components/comment/CommentElbowSvg.vue b/apps/modeling-commons-frontend/app/components/comment/CommentElbowSvg.vue index ae533ea4..cbd6a921 100644 --- a/apps/modeling-commons-frontend/app/components/comment/CommentElbowSvg.vue +++ b/apps/modeling-commons-frontend/app/components/comment/CommentElbowSvg.vue @@ -5,7 +5,7 @@ viewBox="0 0 30 50" fill="none" xmlns="http://www.w3.org/2000/svg" - class="select-none" + class="select-none z-3" > diff --git a/apps/modeling-commons-frontend/app/components/comment/CommentSpine.vue b/apps/modeling-commons-frontend/app/components/comment/CommentSpine.vue index a6592e2a..a4785dc0 100644 --- a/apps/modeling-commons-frontend/app/components/comment/CommentSpine.vue +++ b/apps/modeling-commons-frontend/app/components/comment/CommentSpine.vue @@ -1,3 +1,3 @@ diff --git a/apps/modeling-commons-frontend/app/components/comment/CommentSpineEraser.vue b/apps/modeling-commons-frontend/app/components/comment/CommentSpineEraser.vue index 7d4c562a..441ef9e8 100644 --- a/apps/modeling-commons-frontend/app/components/comment/CommentSpineEraser.vue +++ b/apps/modeling-commons-frontend/app/components/comment/CommentSpineEraser.vue @@ -13,6 +13,6 @@ const props = withDefaults(defineProps<{ class?: string }>(), { class: "", }); const className = computed(() => { - return cn("absolute -left-1/2 -translate-x-9 top-3 w-full -z-1 bg-background h-[calc(100%-1rem)]", props.class); + return cn("absolute -left-1/2 -translate-x-9 top-3 w-full z-2 bg-background h-[calc(100%-1rem)]", props.class); }); \ No newline at end of file diff --git a/apps/modeling-commons-frontend/app/components/comment/CommentView.vue b/apps/modeling-commons-frontend/app/components/comment/CommentView.vue index f0fae145..281ec5c4 100644 --- a/apps/modeling-commons-frontend/app/components/comment/CommentView.vue +++ b/apps/modeling-commons-frontend/app/components/comment/CommentView.vue @@ -21,7 +21,7 @@ @@ -29,7 +29,7 @@ v-else :src="comment.author.image" :alt="comment.author.name" - size="md" + size="lg" class="self-start my-1" /> From fc6dcc4bf76370d797b722d428a9a93beb2c16d1 Mon Sep 17 00:00:00 2001 From: Omar Ibrahim Date: Mon, 20 Jul 2026 15:33:32 -0500 Subject: [PATCH 33/51] minor(commons): add hash routing to comments --- .../app/components/comment/CommentInput.vue | 4 ++-- .../app/components/comment/CommentView.vue | 4 ++-- .../components/model/detail/ModelDetail.vue | 18 ++++++++++++++++++ .../model/detail/ModelDiscussionTab.vue | 2 +- .../components/model/detail/ModelFamilyTab.vue | 2 +- .../components/model/detail/ModelFilesTab.vue | 2 +- .../model/detail/ModelVersionsTab.vue | 2 +- 7 files changed, 26 insertions(+), 8 deletions(-) diff --git a/apps/modeling-commons-frontend/app/components/comment/CommentInput.vue b/apps/modeling-commons-frontend/app/components/comment/CommentInput.vue index 03af477e..de4e811b 100644 --- a/apps/modeling-commons-frontend/app/components/comment/CommentInput.vue +++ b/apps/modeling-commons-frontend/app/components/comment/CommentInput.vue @@ -9,9 +9,9 @@ diff --git a/apps/modeling-commons-frontend/app/components/model/detail/ModelDetail.vue b/apps/modeling-commons-frontend/app/components/model/detail/ModelDetail.vue index d7f61975..bc86885c 100644 --- a/apps/modeling-commons-frontend/app/components/model/detail/ModelDetail.vue +++ b/apps/modeling-commons-frontend/app/components/model/detail/ModelDetail.vue @@ -118,6 +118,7 @@ import type { AttachedFile } from "./types"; import { getAuthorUrl, getPrimaryAuthor } from "~/components/model/ModelAuthors.vue"; const props = defineProps<{ card: ModelCard; permissions: UserModelPermissions }>(); +const router = useRouter(); type TabKey = "discussion" | "files" | "versions" | "family"; @@ -203,8 +204,25 @@ function onTabChange(idx: string | number) { if (key === "files" && filesStatus.value === "idle") { void loadFiles(); } + + router.replace({ + hash: "#" + key, + }) } +onMounted(() => { + const hash = router.currentRoute.value.hash.replace(/^#/, ""); + const tabIndex = tabs.value.findIndex((tab) => tab.slot === hash); + if (tabIndex >= 0) { + activeTab.value = String(tabIndex); + } + nextTick(() => + setTimeout(() => { + document.getElementById(hash)?.scrollIntoView({ behavior: "instant" }); + }, 100) + ); +}); + const interactions = useModelInteractions(); const toast = useToast(); diff --git a/apps/modeling-commons-frontend/app/components/model/detail/ModelDiscussionTab.vue b/apps/modeling-commons-frontend/app/components/model/detail/ModelDiscussionTab.vue index 84eb3304..600a7f5e 100644 --- a/apps/modeling-commons-frontend/app/components/model/detail/ModelDiscussionTab.vue +++ b/apps/modeling-commons-frontend/app/components/model/detail/ModelDiscussionTab.vue @@ -1,5 +1,5 @@