@commonpub/layer 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +661 -0
- package/app.vue +7 -0
- package/components/AnnouncementBand.vue +117 -0
- package/components/AppToast.vue +108 -0
- package/components/AuthorCard.vue +119 -0
- package/components/AuthorRow.vue +81 -0
- package/components/CommentSection.vue +330 -0
- package/components/ContentCard.vue +340 -0
- package/components/ContentPicker.vue +240 -0
- package/components/ContentStarterForm.vue +214 -0
- package/components/ContentTypeBadge.vue +46 -0
- package/components/CountdownTimer.vue +68 -0
- package/components/CpubEditor.vue +87 -0
- package/components/DiscussionItem.vue +191 -0
- package/components/EditorPropertiesPanel.vue +393 -0
- package/components/EngagementBar.vue +131 -0
- package/components/FederatedContentCard.vue +291 -0
- package/components/FeedItem.vue +283 -0
- package/components/FilterChip.vue +21 -0
- package/components/HeatmapGrid.vue +92 -0
- package/components/ImageUpload.vue +219 -0
- package/components/MemberCard.vue +163 -0
- package/components/MessageThread.vue +120 -0
- package/components/NotificationItem.vue +103 -0
- package/components/ProgressTracker.vue +41 -0
- package/components/PublishErrorsModal.vue +116 -0
- package/components/RemoteActorCard.vue +206 -0
- package/components/RemoteUserSearch.vue +117 -0
- package/components/SearchFilters.vue +188 -0
- package/components/SearchSidebar.vue +181 -0
- package/components/SectionHeader.vue +17 -0
- package/components/ShareToHubModal.vue +189 -0
- package/components/SiteLogo.vue +21 -0
- package/components/SkillBar.vue +57 -0
- package/components/SortSelect.vue +30 -0
- package/components/StatBar.vue +14 -0
- package/components/TOCNav.vue +69 -0
- package/components/TimelineItem.vue +82 -0
- package/components/VideoCard.vue +106 -0
- package/components/blocks/BlockBuildStepView.vue +92 -0
- package/components/blocks/BlockCalloutView.vue +82 -0
- package/components/blocks/BlockCheckpointView.vue +50 -0
- package/components/blocks/BlockCodeView.vue +212 -0
- package/components/blocks/BlockContentRenderer.vue +143 -0
- package/components/blocks/BlockDividerView.vue +11 -0
- package/components/blocks/BlockDownloadsView.vue +126 -0
- package/components/blocks/BlockEmbedView.vue +61 -0
- package/components/blocks/BlockGalleryView.vue +57 -0
- package/components/blocks/BlockHeadingView.vue +29 -0
- package/components/blocks/BlockImageView.vue +34 -0
- package/components/blocks/BlockMarkdownView.vue +118 -0
- package/components/blocks/BlockMathView.vue +45 -0
- package/components/blocks/BlockPartsListView.vue +104 -0
- package/components/blocks/BlockQuizView.vue +239 -0
- package/components/blocks/BlockQuoteView.vue +41 -0
- package/components/blocks/BlockSectionHeaderView.vue +58 -0
- package/components/blocks/BlockSliderView.vue +236 -0
- package/components/blocks/BlockTextView.vue +41 -0
- package/components/blocks/BlockToolListView.vue +87 -0
- package/components/blocks/BlockVideoView.vue +89 -0
- package/components/editors/ArticleEditor.vue +545 -0
- package/components/editors/BlockCanvas.vue +487 -0
- package/components/editors/BlockInsertZone.vue +84 -0
- package/components/editors/BlockPicker.vue +285 -0
- package/components/editors/BlockWrapper.vue +192 -0
- package/components/editors/BlogEditor.vue +567 -0
- package/components/editors/EditorBlocks.vue +248 -0
- package/components/editors/EditorSection.vue +81 -0
- package/components/editors/EditorShell.vue +168 -0
- package/components/editors/EditorTagInput.vue +114 -0
- package/components/editors/EditorVisibility.vue +110 -0
- package/components/editors/ExplainerEditor.vue +503 -0
- package/components/editors/MarkdownImportDialog.vue +249 -0
- package/components/editors/ProjectEditor.vue +446 -0
- package/components/editors/blocks/BuildStepBlock.vue +102 -0
- package/components/editors/blocks/CalloutBlock.vue +122 -0
- package/components/editors/blocks/CheckpointBlock.vue +27 -0
- package/components/editors/blocks/CodeBlock.vue +177 -0
- package/components/editors/blocks/DividerBlock.vue +22 -0
- package/components/editors/blocks/DownloadsBlock.vue +41 -0
- package/components/editors/blocks/EmbedBlock.vue +20 -0
- package/components/editors/blocks/GalleryBlock.vue +236 -0
- package/components/editors/blocks/HeadingBlock.vue +96 -0
- package/components/editors/blocks/ImageBlock.vue +271 -0
- package/components/editors/blocks/MarkdownBlock.vue +258 -0
- package/components/editors/blocks/MathBlock.vue +37 -0
- package/components/editors/blocks/PartsListBlock.vue +358 -0
- package/components/editors/blocks/QuizBlock.vue +47 -0
- package/components/editors/blocks/QuoteBlock.vue +101 -0
- package/components/editors/blocks/SectionHeaderBlock.vue +130 -0
- package/components/editors/blocks/SliderBlock.vue +318 -0
- package/components/editors/blocks/TextBlock.vue +201 -0
- package/components/editors/blocks/ToolListBlock.vue +70 -0
- package/components/editors/blocks/VideoBlock.vue +22 -0
- package/components/hub/HubDiscussions.vue +47 -0
- package/components/hub/HubFeed.vue +199 -0
- package/components/hub/HubHero.vue +185 -0
- package/components/hub/HubLayout.vue +103 -0
- package/components/hub/HubMembers.vue +40 -0
- package/components/hub/HubProducts.vue +93 -0
- package/components/hub/HubProjects.vue +207 -0
- package/components/hub/HubSidebar.vue +12 -0
- package/components/hub/HubSidebarCard.vue +35 -0
- package/components/views/ArticleView.vue +771 -0
- package/components/views/BlogView.vue +667 -0
- package/components/views/ExplainerView.vue +688 -0
- package/components/views/ProjectView.vue +1500 -0
- package/composables/useApiError.ts +39 -0
- package/composables/useAuth.ts +87 -0
- package/composables/useBlockEditor.ts +187 -0
- package/composables/useContentSave.ts +253 -0
- package/composables/useContentTypes.ts +37 -0
- package/composables/useEngagement.ts +196 -0
- package/composables/useFeatures.ts +33 -0
- package/composables/useFederation.ts +72 -0
- package/composables/useJsonLd.ts +183 -0
- package/composables/useMarkdownImport.ts +77 -0
- package/composables/useMirrorContent.ts +105 -0
- package/composables/useNotifications.ts +73 -0
- package/composables/usePublishValidation.ts +65 -0
- package/composables/useSanitize.ts +34 -0
- package/composables/useSiteName.ts +4 -0
- package/composables/useTheme.ts +34 -0
- package/composables/useToast.ts +35 -0
- package/error.vue +129 -0
- package/layouts/admin.vue +213 -0
- package/layouts/auth.vue +63 -0
- package/layouts/default.vue +269 -0
- package/layouts/editor.vue +129 -0
- package/middleware/auth.ts +6 -0
- package/nuxt.config.ts +83 -0
- package/package.json +59 -0
- package/pages/[type]/[slug]/edit.vue +676 -0
- package/pages/[type]/[slug]/index.vue +313 -0
- package/pages/[type]/index.vue +118 -0
- package/pages/about.vue +100 -0
- package/pages/admin/audit.vue +66 -0
- package/pages/admin/content.vue +116 -0
- package/pages/admin/federation.vue +446 -0
- package/pages/admin/index.vue +62 -0
- package/pages/admin/reports.vue +88 -0
- package/pages/admin/settings.vue +167 -0
- package/pages/admin/users.vue +145 -0
- package/pages/auth/forgot-password.vue +103 -0
- package/pages/auth/login.vue +216 -0
- package/pages/auth/oauth/authorize.vue +178 -0
- package/pages/auth/register.vue +246 -0
- package/pages/auth/reset-password.vue +124 -0
- package/pages/auth/verify-email.vue +80 -0
- package/pages/cert/[code].vue +154 -0
- package/pages/contests/[slug]/edit.vue +153 -0
- package/pages/contests/[slug]/index.vue +556 -0
- package/pages/contests/[slug]/judge.vue +160 -0
- package/pages/contests/create.vue +192 -0
- package/pages/contests/index.vue +69 -0
- package/pages/create.vue +219 -0
- package/pages/dashboard.vue +521 -0
- package/pages/docs/[siteSlug]/[...pagePath].vue +704 -0
- package/pages/docs/[siteSlug]/edit.vue +480 -0
- package/pages/docs/[siteSlug]/index.vue +380 -0
- package/pages/docs/create.vue +60 -0
- package/pages/docs/index.vue +181 -0
- package/pages/explore.vue +422 -0
- package/pages/federated-hubs/[id]/index.vue +385 -0
- package/pages/federated-hubs/[id]/posts/[postId].vue +309 -0
- package/pages/federation/index.vue +157 -0
- package/pages/federation/search.vue +43 -0
- package/pages/federation/users/[handle].vue +221 -0
- package/pages/feed.vue +135 -0
- package/pages/hubs/[slug]/index.vue +513 -0
- package/pages/hubs/[slug]/members.vue +134 -0
- package/pages/hubs/[slug]/posts/[postId].vue +352 -0
- package/pages/hubs/[slug]/settings.vue +254 -0
- package/pages/hubs/create.vue +201 -0
- package/pages/hubs/index.vue +207 -0
- package/pages/index.vue +1005 -0
- package/pages/learn/[slug]/[lessonSlug]/edit.vue +413 -0
- package/pages/learn/[slug]/[lessonSlug]/index.vue +438 -0
- package/pages/learn/[slug]/edit.vue +414 -0
- package/pages/learn/[slug]/index.vue +341 -0
- package/pages/learn/create.vue +71 -0
- package/pages/learn/index.vue +360 -0
- package/pages/messages/[conversationId].vue +113 -0
- package/pages/messages/index.vue +303 -0
- package/pages/mirror/[id].vue +115 -0
- package/pages/notifications.vue +91 -0
- package/pages/products/[slug].vue +128 -0
- package/pages/products/index.vue +122 -0
- package/pages/search.vue +692 -0
- package/pages/settings/account.vue +170 -0
- package/pages/settings/appearance.vue +80 -0
- package/pages/settings/index.vue +81 -0
- package/pages/settings/notifications.vue +68 -0
- package/pages/settings/profile.vue +838 -0
- package/pages/tags/[slug].vue +111 -0
- package/pages/tags/index.vue +73 -0
- package/pages/u/[username]/followers.vue +86 -0
- package/pages/u/[username]/following.vue +94 -0
- package/pages/u/[username]/index.vue +837 -0
- package/pages/videos/[id].vue +212 -0
- package/pages/videos/index.vue +327 -0
- package/pages/videos/submit.vue +112 -0
- package/plugins/auth.ts +23 -0
- package/server/api/admin/audit.get.ts +17 -0
- package/server/api/admin/content/[id].delete.ts +15 -0
- package/server/api/admin/content/[id].patch.ts +37 -0
- package/server/api/admin/federation/activity.get.ts +31 -0
- package/server/api/admin/federation/clients.get.ts +9 -0
- package/server/api/admin/federation/clients.post.ts +16 -0
- package/server/api/admin/federation/hub-mirrors/index.get.ts +10 -0
- package/server/api/admin/federation/hub-mirrors/index.post.ts +42 -0
- package/server/api/admin/federation/mirrors/[id]/backfill.post.ts +39 -0
- package/server/api/admin/federation/mirrors/[id].delete.ts +11 -0
- package/server/api/admin/federation/mirrors/[id].get.ts +15 -0
- package/server/api/admin/federation/mirrors/[id].put.ts +22 -0
- package/server/api/admin/federation/mirrors/index.get.ts +9 -0
- package/server/api/admin/federation/mirrors/index.post.ts +30 -0
- package/server/api/admin/federation/pending.get.ts +22 -0
- package/server/api/admin/federation/refederate.post.ts +92 -0
- package/server/api/admin/federation/repair-types.post.ts +57 -0
- package/server/api/admin/federation/retry.post.ts +41 -0
- package/server/api/admin/federation/stats.get.ts +26 -0
- package/server/api/admin/reports/[id]/resolve.post.ts +12 -0
- package/server/api/admin/reports.get.ts +17 -0
- package/server/api/admin/settings.get.ts +22 -0
- package/server/api/admin/settings.put.ts +11 -0
- package/server/api/admin/stats.get.ts +9 -0
- package/server/api/admin/users/[id]/role.put.ts +12 -0
- package/server/api/admin/users/[id]/status.put.ts +12 -0
- package/server/api/admin/users/[id].delete.ts +10 -0
- package/server/api/admin/users.get.ts +18 -0
- package/server/api/auth/federated/callback.get.ts +67 -0
- package/server/api/auth/federated/login.post.ts +60 -0
- package/server/api/auth/oauth2/authorize.get.ts +30 -0
- package/server/api/auth/oauth2/authorize.post.ts +51 -0
- package/server/api/auth/oauth2/register.post.ts +41 -0
- package/server/api/auth/oauth2/token.post.ts +48 -0
- package/server/api/cert/[code].get.ts +13 -0
- package/server/api/content/[id]/build.post.ts +9 -0
- package/server/api/content/[id]/fork.post.ts +10 -0
- package/server/api/content/[id]/index.delete.ts +18 -0
- package/server/api/content/[id]/index.get.ts +15 -0
- package/server/api/content/[id]/index.put.ts +23 -0
- package/server/api/content/[id]/products/[productId].delete.ts +28 -0
- package/server/api/content/[id]/products-sync.post.ts +29 -0
- package/server/api/content/[id]/products.get.ts +9 -0
- package/server/api/content/[id]/products.post.ts +31 -0
- package/server/api/content/[id]/publish.post.ts +17 -0
- package/server/api/content/[id]/report.post.ts +17 -0
- package/server/api/content/[id]/versions.get.ts +9 -0
- package/server/api/content/[id]/view.post.ts +34 -0
- package/server/api/content/index.get.ts +23 -0
- package/server/api/content/index.post.ts +11 -0
- package/server/api/contests/[slug]/entries.get.ts +18 -0
- package/server/api/contests/[slug]/entries.post.ts +23 -0
- package/server/api/contests/[slug]/index.delete.ts +21 -0
- package/server/api/contests/[slug]/index.get.ts +11 -0
- package/server/api/contests/[slug]/index.put.ts +15 -0
- package/server/api/contests/[slug]/judge.post.ts +12 -0
- package/server/api/contests/[slug]/transition.post.ts +24 -0
- package/server/api/contests/index.get.ts +10 -0
- package/server/api/contests/index.post.ts +28 -0
- package/server/api/docs/[siteSlug]/index.delete.ts +14 -0
- package/server/api/docs/[siteSlug]/index.get.ts +17 -0
- package/server/api/docs/[siteSlug]/index.put.ts +20 -0
- package/server/api/docs/[siteSlug]/nav.get.ts +26 -0
- package/server/api/docs/[siteSlug]/pages/[pageId].delete.ts +14 -0
- package/server/api/docs/[siteSlug]/pages/[pageId].get.ts +31 -0
- package/server/api/docs/[siteSlug]/pages/[pageId].put.ts +15 -0
- package/server/api/docs/[siteSlug]/pages/index.get.ts +34 -0
- package/server/api/docs/[siteSlug]/pages/index.post.ts +28 -0
- package/server/api/docs/[siteSlug]/pages/reorder.post.ts +26 -0
- package/server/api/docs/[siteSlug]/search.get.ts +20 -0
- package/server/api/docs/[siteSlug]/versions.post.ts +11 -0
- package/server/api/docs/index.get.ts +6 -0
- package/server/api/docs/index.post.ts +10 -0
- package/server/api/federated-hubs/[id]/posts/[postId].get.ts +16 -0
- package/server/api/federated-hubs/[id]/posts.get.ts +15 -0
- package/server/api/federated-hubs/[id].get.ts +16 -0
- package/server/api/federation/boost.post.ts +21 -0
- package/server/api/federation/content/[id].get.ts +15 -0
- package/server/api/federation/follow.post.ts +16 -0
- package/server/api/federation/health.get.ts +56 -0
- package/server/api/federation/hub-follow.post.ts +27 -0
- package/server/api/federation/hub-post-like.post.ts +115 -0
- package/server/api/federation/hub-post-likes.get.ts +24 -0
- package/server/api/federation/hub-post-reply.post.ts +42 -0
- package/server/api/federation/hub-post.post.ts +33 -0
- package/server/api/federation/like.post.ts +21 -0
- package/server/api/federation/remote-actor.get.ts +22 -0
- package/server/api/federation/reply.post.ts +22 -0
- package/server/api/federation/search.post.ts +17 -0
- package/server/api/federation/timeline.get.ts +22 -0
- package/server/api/federation/unfollow.post.ts +17 -0
- package/server/api/files/[id].delete.ts +35 -0
- package/server/api/files/mine.get.ts +31 -0
- package/server/api/files/upload-from-url.post.ts +68 -0
- package/server/api/files/upload.post.ts +105 -0
- package/server/api/health.get.ts +4 -0
- package/server/api/hubs/[slug]/bans/[userId].delete.ts +13 -0
- package/server/api/hubs/[slug]/bans.get.ts +20 -0
- package/server/api/hubs/[slug]/bans.post.ts +23 -0
- package/server/api/hubs/[slug]/feed.xml.get.ts +60 -0
- package/server/api/hubs/[slug]/gallery.get.ts +21 -0
- package/server/api/hubs/[slug]/index.delete.ts +18 -0
- package/server/api/hubs/[slug]/index.get.ts +14 -0
- package/server/api/hubs/[slug]/index.put.ts +22 -0
- package/server/api/hubs/[slug]/invites.get.ts +20 -0
- package/server/api/hubs/[slug]/invites.post.ts +27 -0
- package/server/api/hubs/[slug]/join.post.ts +17 -0
- package/server/api/hubs/[slug]/leave.post.ts +13 -0
- package/server/api/hubs/[slug]/members/[userId].delete.ts +13 -0
- package/server/api/hubs/[slug]/members/[userId].put.ts +16 -0
- package/server/api/hubs/[slug]/members.get.ts +20 -0
- package/server/api/hubs/[slug]/posts/[postId]/like.post.ts +21 -0
- package/server/api/hubs/[slug]/posts/[postId]/lock.post.ts +17 -0
- package/server/api/hubs/[slug]/posts/[postId]/pin.post.ts +17 -0
- package/server/api/hubs/[slug]/posts/[postId]/replies.get.ts +16 -0
- package/server/api/hubs/[slug]/posts/[postId]/replies.post.ts +21 -0
- package/server/api/hubs/[slug]/posts/[postId].delete.ts +23 -0
- package/server/api/hubs/[slug]/posts/[postId].get.ts +16 -0
- package/server/api/hubs/[slug]/posts/index.get.ts +16 -0
- package/server/api/hubs/[slug]/posts/index.post.ts +36 -0
- package/server/api/hubs/[slug]/products.get.ts +26 -0
- package/server/api/hubs/[slug]/products.post.ts +18 -0
- package/server/api/hubs/[slug]/share.post.ts +39 -0
- package/server/api/hubs/index.get.ts +15 -0
- package/server/api/hubs/index.post.ts +11 -0
- package/server/api/image-proxy.get.ts +91 -0
- package/server/api/learn/[slug]/[lessonSlug]/complete.post.ts +13 -0
- package/server/api/learn/[slug]/[lessonSlug]/index.get.ts +68 -0
- package/server/api/learn/[slug]/enroll.post.ts +12 -0
- package/server/api/learn/[slug]/index.delete.ts +12 -0
- package/server/api/learn/[slug]/index.get.ts +14 -0
- package/server/api/learn/[slug]/index.put.ts +19 -0
- package/server/api/learn/[slug]/lessons/[lessonId].delete.ts +14 -0
- package/server/api/learn/[slug]/lessons/[lessonId].put.ts +24 -0
- package/server/api/learn/[slug]/lessons.post.ts +10 -0
- package/server/api/learn/[slug]/modules/[moduleId].delete.ts +14 -0
- package/server/api/learn/[slug]/modules/[moduleId].put.ts +15 -0
- package/server/api/learn/[slug]/modules.post.ts +14 -0
- package/server/api/learn/[slug]/publish.post.ts +13 -0
- package/server/api/learn/[slug]/unenroll.post.ts +12 -0
- package/server/api/learn/certificates.get.ts +9 -0
- package/server/api/learn/enrollments.get.ts +9 -0
- package/server/api/learn/index.get.ts +17 -0
- package/server/api/learn/index.post.ts +11 -0
- package/server/api/me.get.ts +13 -0
- package/server/api/messages/[conversationId]/info.get.ts +43 -0
- package/server/api/messages/[conversationId]/stream.get.ts +73 -0
- package/server/api/messages/[conversationId].get.ts +13 -0
- package/server/api/messages/[conversationId].post.ts +12 -0
- package/server/api/messages/index.get.ts +39 -0
- package/server/api/messages/index.post.ts +58 -0
- package/server/api/notifications/[id].delete.ts +11 -0
- package/server/api/notifications/count.get.ts +10 -0
- package/server/api/notifications/index.get.ts +24 -0
- package/server/api/notifications/read.post.ts +15 -0
- package/server/api/notifications/stream.get.ts +59 -0
- package/server/api/openapi.get.ts +5 -0
- package/server/api/products/[id].delete.ts +15 -0
- package/server/api/products/[id].put.ts +18 -0
- package/server/api/products/[slug]/content.get.ts +21 -0
- package/server/api/products/[slug].get.ts +16 -0
- package/server/api/products/index.get.ts +28 -0
- package/server/api/profile.get.ts +15 -0
- package/server/api/profile.put.ts +24 -0
- package/server/api/resolve-identity.post.ts +34 -0
- package/server/api/search/index.get.ts +24 -0
- package/server/api/search/trending.get.ts +22 -0
- package/server/api/social/bookmark.get.ts +16 -0
- package/server/api/social/bookmark.post.ts +15 -0
- package/server/api/social/bookmarks.get.ts +16 -0
- package/server/api/social/comments/[id].delete.ts +13 -0
- package/server/api/social/comments.get.ts +18 -0
- package/server/api/social/comments.post.ts +11 -0
- package/server/api/social/like.get.ts +17 -0
- package/server/api/social/like.post.ts +35 -0
- package/server/api/stats.get.ts +8 -0
- package/server/api/user/hubs.get.ts +22 -0
- package/server/api/users/[username]/content.get.ts +21 -0
- package/server/api/users/[username]/feed.xml.get.ts +65 -0
- package/server/api/users/[username]/follow.delete.ts +15 -0
- package/server/api/users/[username]/follow.post.ts +15 -0
- package/server/api/users/[username]/followers.get.ts +22 -0
- package/server/api/users/[username]/following.get.ts +22 -0
- package/server/api/users/[username]/learning.get.ts +18 -0
- package/server/api/users/[username].get.ts +25 -0
- package/server/api/users/index.get.ts +78 -0
- package/server/api/videos/[id].get.ts +18 -0
- package/server/api/videos/categories/[id].delete.ts +15 -0
- package/server/api/videos/categories/[id].put.ts +17 -0
- package/server/api/videos/categories.get.ts +7 -0
- package/server/api/videos/categories.post.ts +11 -0
- package/server/api/videos/index.get.ts +20 -0
- package/server/api/videos/index.post.ts +11 -0
- package/server/middleware/auth.ts +180 -0
- package/server/middleware/features.ts +31 -0
- package/server/middleware/security.ts +54 -0
- package/server/plugins/auto-admin.ts +69 -0
- package/server/plugins/federation-delivery.ts +74 -0
- package/server/routes/.well-known/nodeinfo.ts +15 -0
- package/server/routes/.well-known/webfinger.ts +86 -0
- package/server/routes/actor/followers.ts +34 -0
- package/server/routes/actor/following.ts +34 -0
- package/server/routes/actor/outbox.ts +31 -0
- package/server/routes/actor.ts +30 -0
- package/server/routes/feed.xml.ts +59 -0
- package/server/routes/hubs/[slug]/followers.ts +39 -0
- package/server/routes/hubs/[slug]/inbox.ts +35 -0
- package/server/routes/hubs/[slug]/outbox.ts +43 -0
- package/server/routes/hubs/[slug]/posts/[postId].ts +63 -0
- package/server/routes/hubs/[slug].ts +29 -0
- package/server/routes/inbox.ts +34 -0
- package/server/routes/nodeinfo/2.1.ts +27 -0
- package/server/routes/robots.txt.ts +19 -0
- package/server/routes/sitemap.xml.ts +105 -0
- package/server/routes/users/[username]/followers.ts +33 -0
- package/server/routes/users/[username]/following.ts +33 -0
- package/server/routes/users/[username]/inbox.ts +34 -0
- package/server/routes/users/[username]/outbox.ts +35 -0
- package/server/routes/users/[username].ts +62 -0
- package/server/utils/auth.ts +36 -0
- package/server/utils/db.ts +34 -0
- package/server/utils/errors.ts +24 -0
- package/server/utils/inbox.ts +122 -0
- package/server/utils/validate.ts +82 -0
- package/theme/base.css +283 -0
- package/theme/components.css +322 -0
- package/theme/dark.css +87 -0
- package/theme/editor-panels.css +63 -0
- package/theme/forms.css +216 -0
- package/theme/generics.css +68 -0
- package/theme/layouts.css +415 -0
- package/theme/prose.css +342 -0
- package/types/hub.ts +61 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { createContent } from '@commonpub/server';
|
|
2
|
+
import type { ContentDetail } from '@commonpub/server';
|
|
3
|
+
import { createContentSchema } from '@commonpub/schema';
|
|
4
|
+
|
|
5
|
+
export default defineEventHandler(async (event): Promise<ContentDetail> => {
|
|
6
|
+
const user = requireAuth(event);
|
|
7
|
+
const db = useDB();
|
|
8
|
+
const input = await parseBody(event, createContentSchema);
|
|
9
|
+
|
|
10
|
+
return createContent(db, user.id, input);
|
|
11
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { listContestEntries, getContestBySlug } from '@commonpub/server';
|
|
2
|
+
import type { ContestEntryItem } from '@commonpub/server';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
|
|
5
|
+
const entriesQuerySchema = z.object({
|
|
6
|
+
limit: z.coerce.number().int().min(1).max(100).optional(),
|
|
7
|
+
offset: z.coerce.number().int().min(0).optional(),
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
export default defineEventHandler(async (event): Promise<{ items: ContestEntryItem[]; total: number }> => {
|
|
11
|
+
requireFeature('contests');
|
|
12
|
+
const db = useDB();
|
|
13
|
+
const { slug } = parseParams(event, { slug: 'string' });
|
|
14
|
+
const query = parseQueryParams(event, entriesQuerySchema);
|
|
15
|
+
const contest = await getContestBySlug(db, slug);
|
|
16
|
+
if (!contest) throw createError({ statusCode: 404, statusMessage: 'Contest not found' });
|
|
17
|
+
return listContestEntries(db, contest.id, query);
|
|
18
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { submitContestEntry, getContestBySlug } from '@commonpub/server';
|
|
2
|
+
import type { ContestEntryItem } from '@commonpub/server';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
|
|
5
|
+
const submitEntrySchema = z.object({
|
|
6
|
+
contentId: z.string().uuid(),
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
export default defineEventHandler(async (event): Promise<ContestEntryItem> => {
|
|
10
|
+
requireFeature('contests');
|
|
11
|
+
const user = requireAuth(event);
|
|
12
|
+
const db = useDB();
|
|
13
|
+
const { slug } = parseParams(event, { slug: 'string' });
|
|
14
|
+
const contest = await getContestBySlug(db, slug);
|
|
15
|
+
if (!contest) throw createError({ statusCode: 404, statusMessage: 'Contest not found' });
|
|
16
|
+
const input = await parseBody(event, submitEntrySchema);
|
|
17
|
+
|
|
18
|
+
const entry = await submitContestEntry(db, contest.id, input.contentId, user.id);
|
|
19
|
+
if (!entry) {
|
|
20
|
+
throw createError({ statusCode: 400, statusMessage: 'Cannot submit entry. Contest may not be active, or content is not published or not owned by you.' });
|
|
21
|
+
}
|
|
22
|
+
return entry;
|
|
23
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { getContestBySlug, deleteContest } from '@commonpub/server';
|
|
2
|
+
|
|
3
|
+
export default defineEventHandler(async (event): Promise<{ deleted: boolean }> => {
|
|
4
|
+
requireFeature('contests');
|
|
5
|
+
const db = useDB();
|
|
6
|
+
const user = requireAuth(event);
|
|
7
|
+
const { slug } = parseParams(event, { slug: 'string' });
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
const contest = await getContestBySlug(db, slug);
|
|
11
|
+
if (!contest) {
|
|
12
|
+
throw createError({ statusCode: 404, statusMessage: 'Contest not found' });
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const deleted = await deleteContest(db, contest.id, user.id);
|
|
16
|
+
if (!deleted) {
|
|
17
|
+
throw createError({ statusCode: 403, statusMessage: 'Not authorized to delete this contest' });
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return { deleted: true };
|
|
21
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { getContestBySlug } from '@commonpub/server';
|
|
2
|
+
import type { ContestDetail } from '@commonpub/server';
|
|
3
|
+
|
|
4
|
+
export default defineEventHandler(async (event): Promise<ContestDetail> => {
|
|
5
|
+
requireFeature('contests');
|
|
6
|
+
const db = useDB();
|
|
7
|
+
const { slug } = parseParams(event, { slug: 'string' });
|
|
8
|
+
const contest = await getContestBySlug(db, slug);
|
|
9
|
+
if (!contest) throw createError({ statusCode: 404, statusMessage: 'Contest not found' });
|
|
10
|
+
return contest;
|
|
11
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { updateContest } from '@commonpub/server';
|
|
2
|
+
import type { ContestDetail } from '@commonpub/server';
|
|
3
|
+
import { updateContestSchema } from '@commonpub/schema';
|
|
4
|
+
|
|
5
|
+
export default defineEventHandler(async (event): Promise<ContestDetail> => {
|
|
6
|
+
requireFeature('contests');
|
|
7
|
+
const user = requireAuth(event);
|
|
8
|
+
const db = useDB();
|
|
9
|
+
const { slug } = parseParams(event, { slug: 'string' });
|
|
10
|
+
const input = await parseBody(event, updateContestSchema);
|
|
11
|
+
|
|
12
|
+
const result = await updateContest(db, slug, user.id, input);
|
|
13
|
+
if (!result) throw createError({ statusCode: 403, statusMessage: 'Not authorized or contest not found' });
|
|
14
|
+
return result;
|
|
15
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { judgeContestEntry } from '@commonpub/server';
|
|
2
|
+
import { judgeEntrySchema } from '@commonpub/schema';
|
|
3
|
+
|
|
4
|
+
export default defineEventHandler(async (event): Promise<{ success: boolean }> => {
|
|
5
|
+
requireFeature('contests');
|
|
6
|
+
const user = requireAuth(event);
|
|
7
|
+
const db = useDB();
|
|
8
|
+
const input = await parseBody(event, judgeEntrySchema);
|
|
9
|
+
|
|
10
|
+
await judgeContestEntry(db, input.entryId, input.score, user.id);
|
|
11
|
+
return { success: true };
|
|
12
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { getContestBySlug, transitionContestStatus } from '@commonpub/server';
|
|
2
|
+
import type { ContestStatus } from '@commonpub/server';
|
|
3
|
+
import { contestTransitionSchema } from '@commonpub/schema';
|
|
4
|
+
|
|
5
|
+
export default defineEventHandler(async (event): Promise<{ transitioned: boolean; newStatus: ContestStatus }> => {
|
|
6
|
+
requireFeature('contests');
|
|
7
|
+
const db = useDB();
|
|
8
|
+
const user = requireAuth(event);
|
|
9
|
+
const { slug } = parseParams(event, { slug: 'string' });
|
|
10
|
+
const input = await parseBody(event, contestTransitionSchema);
|
|
11
|
+
|
|
12
|
+
const contest = await getContestBySlug(db, slug);
|
|
13
|
+
if (!contest) {
|
|
14
|
+
throw createError({ statusCode: 404, statusMessage: 'Contest not found' });
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const result = await transitionContestStatus(db, contest.id, user.id, input.status);
|
|
18
|
+
|
|
19
|
+
if (!result.transitioned) {
|
|
20
|
+
throw createError({ statusCode: 400, statusMessage: result.error || 'Transition failed' });
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return { transitioned: true, newStatus: input.status };
|
|
24
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { listContests } from '@commonpub/server';
|
|
2
|
+
import type { PaginatedResponse, ContestListItem } from '@commonpub/server';
|
|
3
|
+
import { contestFiltersSchema } from '@commonpub/schema';
|
|
4
|
+
|
|
5
|
+
export default defineEventHandler(async (event): Promise<PaginatedResponse<ContestListItem>> => {
|
|
6
|
+
requireFeature('contests');
|
|
7
|
+
const db = useDB();
|
|
8
|
+
const filters = parseQueryParams(event, contestFiltersSchema);
|
|
9
|
+
return listContests(db, filters);
|
|
10
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { createContest } from '@commonpub/server';
|
|
2
|
+
import type { ContestDetail, CreateContestInput } from '@commonpub/server';
|
|
3
|
+
import { createContestSchema } from '@commonpub/schema';
|
|
4
|
+
|
|
5
|
+
function slugify(text: string): string {
|
|
6
|
+
return text
|
|
7
|
+
.toLowerCase()
|
|
8
|
+
.replace(/[^\w\s-]/g, '')
|
|
9
|
+
.replace(/\s+/g, '-')
|
|
10
|
+
.replace(/-+/g, '-')
|
|
11
|
+
.replace(/^-|-$/g, '')
|
|
12
|
+
.slice(0, 128);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export default defineEventHandler(async (event): Promise<ContestDetail> => {
|
|
16
|
+
requireFeature('contests');
|
|
17
|
+
const user = requireAuth(event);
|
|
18
|
+
const db = useDB();
|
|
19
|
+
const config = useConfig();
|
|
20
|
+
const input = await parseBody(event, createContestSchema);
|
|
21
|
+
|
|
22
|
+
const slug = slugify(input.title) || `contest-${Date.now()}`;
|
|
23
|
+
|
|
24
|
+
return createContest(db, { ...input, slug, createdBy: user.id } as CreateContestInput, {
|
|
25
|
+
userRole: user.role,
|
|
26
|
+
contestCreationPolicy: config.instance.contestCreation ?? 'staff',
|
|
27
|
+
});
|
|
28
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { getDocsSiteBySlug, deleteDocsSite } from '@commonpub/server';
|
|
2
|
+
|
|
3
|
+
export default defineEventHandler(async (event) => {
|
|
4
|
+
const user = requireAuth(event);
|
|
5
|
+
const db = useDB();
|
|
6
|
+
const { siteSlug } = parseParams(event, { siteSlug: 'string' });
|
|
7
|
+
|
|
8
|
+
const result = await getDocsSiteBySlug(db, siteSlug);
|
|
9
|
+
if (!result) {
|
|
10
|
+
throw createError({ statusCode: 404, statusMessage: 'Docs site not found' });
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return deleteDocsSite(db, result.site.id, user.id);
|
|
14
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { getDocsSiteBySlug } from '@commonpub/server';
|
|
2
|
+
|
|
3
|
+
export default defineEventHandler(async (event) => {
|
|
4
|
+
const db = useDB();
|
|
5
|
+
const { siteSlug } = parseParams(event, { siteSlug: 'string' });
|
|
6
|
+
|
|
7
|
+
const result = await getDocsSiteBySlug(db, siteSlug);
|
|
8
|
+
if (!result) {
|
|
9
|
+
throw createError({ statusCode: 404, statusMessage: 'Docs site not found' });
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Flatten the nested { site, versions } shape so frontend can use site.name directly
|
|
13
|
+
return {
|
|
14
|
+
...result.site,
|
|
15
|
+
versions: result.versions,
|
|
16
|
+
};
|
|
17
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { getDocsSiteBySlug, updateDocsSite } from '@commonpub/server';
|
|
2
|
+
import { updateDocsSiteSchema } from '@commonpub/schema';
|
|
3
|
+
|
|
4
|
+
export default defineEventHandler(async (event) => {
|
|
5
|
+
const user = requireAuth(event);
|
|
6
|
+
const db = useDB();
|
|
7
|
+
const { siteSlug } = parseParams(event, { siteSlug: 'string' });
|
|
8
|
+
const input = await parseBody(event, updateDocsSiteSchema);
|
|
9
|
+
|
|
10
|
+
const result = await getDocsSiteBySlug(db, siteSlug);
|
|
11
|
+
if (!result) {
|
|
12
|
+
throw createError({ statusCode: 404, statusMessage: 'Docs site not found' });
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const updated = await updateDocsSite(db, result.site.id, user.id, input);
|
|
16
|
+
if (!updated) {
|
|
17
|
+
throw createError({ statusCode: 403, statusMessage: 'Not authorized to update this docs site' });
|
|
18
|
+
}
|
|
19
|
+
return updated;
|
|
20
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { getDocsSiteBySlug, listDocsPages } from '@commonpub/server';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
|
|
4
|
+
const navQuerySchema = z.object({
|
|
5
|
+
version: z.string().max(32).optional(),
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
export default defineEventHandler(async (event) => {
|
|
9
|
+
const db = useDB();
|
|
10
|
+
const { siteSlug } = parseParams(event, { siteSlug: 'string' });
|
|
11
|
+
const query = parseQueryParams(event, navQuerySchema);
|
|
12
|
+
|
|
13
|
+
const result = await getDocsSiteBySlug(db, siteSlug);
|
|
14
|
+
if (!result) throw createError({ statusCode: 404, statusMessage: 'Docs site not found' });
|
|
15
|
+
|
|
16
|
+
// Find the requested or default version
|
|
17
|
+
const version = query.version
|
|
18
|
+
? result.versions?.find((v) => v.version === query.version)
|
|
19
|
+
: result.versions?.find((v) => v.isDefault) ?? result.versions?.[0];
|
|
20
|
+
|
|
21
|
+
if (!version) return [];
|
|
22
|
+
|
|
23
|
+
// Return pages directly as nav items
|
|
24
|
+
const pages = await listDocsPages(db, version.id);
|
|
25
|
+
return pages.map(p => ({ id: p.id, title: p.title, slug: p.slug, sortOrder: p.sortOrder, parentId: p.parentId }));
|
|
26
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { deleteDocsPage } from '@commonpub/server';
|
|
2
|
+
|
|
3
|
+
export default defineEventHandler(async (event) => {
|
|
4
|
+
const user = requireAuth(event);
|
|
5
|
+
const db = useDB();
|
|
6
|
+
const { pageId } = parseParams(event, { pageId: 'uuid' });
|
|
7
|
+
|
|
8
|
+
const result = await deleteDocsPage(db, pageId, user.id);
|
|
9
|
+
if (!result) {
|
|
10
|
+
throw createError({ statusCode: 404, statusMessage: 'Page not found or not authorized' });
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return { success: true };
|
|
14
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { getDocsSiteBySlug, listDocsPages } from '@commonpub/server';
|
|
2
|
+
import { renderMarkdown } from '@commonpub/docs';
|
|
3
|
+
|
|
4
|
+
export default defineEventHandler(async (event) => {
|
|
5
|
+
const db = useDB();
|
|
6
|
+
const { siteSlug, pageId: pageSlug } = parseParams(event, { siteSlug: 'string', pageId: 'string' });
|
|
7
|
+
const query = getQuery(event) as { version?: string };
|
|
8
|
+
|
|
9
|
+
const result = await getDocsSiteBySlug(db, siteSlug);
|
|
10
|
+
if (!result) throw createError({ statusCode: 404, statusMessage: 'Docs site not found' });
|
|
11
|
+
|
|
12
|
+
const version = query.version
|
|
13
|
+
? result.versions.find((v) => v.version === query.version)
|
|
14
|
+
: result.versions.find((v) => v.isDefault) ?? result.versions[0];
|
|
15
|
+
|
|
16
|
+
if (!version) throw createError({ statusCode: 404, statusMessage: 'No version found' });
|
|
17
|
+
|
|
18
|
+
const pages = await listDocsPages(db, version.id);
|
|
19
|
+
const page = pages.find((p) => p.slug === pageSlug);
|
|
20
|
+
if (!page) throw createError({ statusCode: 404, statusMessage: 'Page not found' });
|
|
21
|
+
|
|
22
|
+
// Render markdown to HTML with TOC extraction
|
|
23
|
+
const rendered = await renderMarkdown(page.content ?? '');
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
...page,
|
|
27
|
+
html: rendered.html,
|
|
28
|
+
toc: rendered.toc,
|
|
29
|
+
frontmatter: rendered.frontmatter,
|
|
30
|
+
};
|
|
31
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { updateDocsPage } from '@commonpub/server';
|
|
2
|
+
import { updateDocsPageSchema } from '@commonpub/schema';
|
|
3
|
+
|
|
4
|
+
export default defineEventHandler(async (event) => {
|
|
5
|
+
const user = requireAuth(event);
|
|
6
|
+
const db = useDB();
|
|
7
|
+
const { pageId } = parseParams(event, { pageId: 'uuid' });
|
|
8
|
+
const input = await parseBody(event, updateDocsPageSchema);
|
|
9
|
+
|
|
10
|
+
const page = await updateDocsPage(db, pageId, user.id, input);
|
|
11
|
+
if (!page) {
|
|
12
|
+
throw createError({ statusCode: 404, statusMessage: 'Page not found or not owned by you' });
|
|
13
|
+
}
|
|
14
|
+
return page;
|
|
15
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { getDocsSiteBySlug, listDocsPages } from '@commonpub/server';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
|
|
4
|
+
const pagesQuerySchema = z.object({
|
|
5
|
+
version: z.string().max(32).optional(),
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
export default defineEventHandler(async (event) => {
|
|
9
|
+
const db = useDB();
|
|
10
|
+
const { siteSlug } = parseParams(event, { siteSlug: 'string' });
|
|
11
|
+
const query = parseQueryParams(event, pagesQuerySchema);
|
|
12
|
+
|
|
13
|
+
const result = await getDocsSiteBySlug(db, siteSlug);
|
|
14
|
+
if (!result) {
|
|
15
|
+
throw createError({ statusCode: 404, statusMessage: 'Docs site not found' });
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Find the requested version or fall back to the default version
|
|
19
|
+
const requestedVersion = query.version;
|
|
20
|
+
let version = requestedVersion
|
|
21
|
+
? result.versions.find((v) => v.version === requestedVersion)
|
|
22
|
+
: result.versions.find((v) => v.isDefault === true);
|
|
23
|
+
|
|
24
|
+
// Fall back to first version if no default found
|
|
25
|
+
if (!version && result.versions.length > 0) {
|
|
26
|
+
version = result.versions[0];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (!version) {
|
|
30
|
+
throw createError({ statusCode: 404, statusMessage: 'No version found for docs site' });
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return listDocsPages(db, version.id);
|
|
34
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { getDocsSiteBySlug, createDocsPage } from '@commonpub/server';
|
|
2
|
+
import { createDocsPageSchema } from '@commonpub/schema';
|
|
3
|
+
|
|
4
|
+
export default defineEventHandler(async (event) => {
|
|
5
|
+
const user = requireAuth(event);
|
|
6
|
+
const db = useDB();
|
|
7
|
+
const { siteSlug } = parseParams(event, { siteSlug: 'string' });
|
|
8
|
+
const input = await parseBody(event, createDocsPageSchema);
|
|
9
|
+
|
|
10
|
+
const data = { ...input };
|
|
11
|
+
|
|
12
|
+
// If no versionId provided, resolve from the site's default version
|
|
13
|
+
if (!data.versionId) {
|
|
14
|
+
const result = await getDocsSiteBySlug(db, siteSlug);
|
|
15
|
+
if (!result) {
|
|
16
|
+
throw createError({ statusCode: 404, statusMessage: 'Docs site not found' });
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const defaultVersion = result.versions.find((v) => v.isDefault === true) ?? result.versions[0];
|
|
20
|
+
if (!defaultVersion) {
|
|
21
|
+
throw createError({ statusCode: 404, statusMessage: 'No version found for docs site' });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
data.versionId = defaultVersion.id;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return createDocsPage(db, user.id, data as typeof data & { versionId: string });
|
|
28
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { reorderDocsPages, getDocsSiteBySlug } from '@commonpub/server';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
|
|
4
|
+
const reorderSchema = z.object({
|
|
5
|
+
pageIds: z.array(z.string().uuid()),
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
export default defineEventHandler(async (event) => {
|
|
9
|
+
const user = requireAuth(event);
|
|
10
|
+
const db = useDB();
|
|
11
|
+
const { siteSlug } = parseParams(event, { siteSlug: 'string' });
|
|
12
|
+
const body = await parseBody(event, reorderSchema);
|
|
13
|
+
|
|
14
|
+
const site = await getDocsSiteBySlug(db, siteSlug);
|
|
15
|
+
if (!site) throw createError({ statusCode: 404, statusMessage: 'Docs site not found' });
|
|
16
|
+
|
|
17
|
+
const version = site.versions.find((v) => v.isDefault) ?? site.versions[0];
|
|
18
|
+
if (!version) throw createError({ statusCode: 404, statusMessage: 'No version found' });
|
|
19
|
+
|
|
20
|
+
const result = await reorderDocsPages(db, version.id, user.id, body.pageIds);
|
|
21
|
+
if (!result) {
|
|
22
|
+
throw createError({ statusCode: 403, statusMessage: 'Not authorized' });
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return { success: true };
|
|
26
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { searchDocsPages, getDocsSiteBySlug } from '@commonpub/server';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
|
|
4
|
+
const searchQuerySchema = z.object({
|
|
5
|
+
q: z.string().max(200).optional(),
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
export default defineEventHandler(async (event) => {
|
|
9
|
+
const db = useDB();
|
|
10
|
+
const { siteSlug } = parseParams(event, { siteSlug: 'string' });
|
|
11
|
+
const query = parseQueryParams(event, searchQuerySchema);
|
|
12
|
+
|
|
13
|
+
const result = await getDocsSiteBySlug(db, siteSlug);
|
|
14
|
+
if (!result) throw createError({ statusCode: 404, statusMessage: 'Docs site not found' });
|
|
15
|
+
|
|
16
|
+
const version = result.versions?.find((v) => v.isDefault) ?? result.versions?.[0];
|
|
17
|
+
if (!version) return [];
|
|
18
|
+
|
|
19
|
+
return searchDocsPages(db, result.site.id, version.id, query.q ?? '');
|
|
20
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { createDocsVersion } from '@commonpub/server';
|
|
2
|
+
import { createDocsVersionSchema } from '@commonpub/schema';
|
|
3
|
+
|
|
4
|
+
export default defineEventHandler(async (event) => {
|
|
5
|
+
const user = requireAuth(event);
|
|
6
|
+
const db = useDB();
|
|
7
|
+
const { siteSlug } = parseParams(event, { siteSlug: 'string' });
|
|
8
|
+
const input = await parseBody(event, createDocsVersionSchema);
|
|
9
|
+
|
|
10
|
+
return createDocsVersion(db, siteSlug, user.id, input);
|
|
11
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { createDocsSite } from '@commonpub/server';
|
|
2
|
+
import { createDocsSiteSchema } from '@commonpub/schema';
|
|
3
|
+
|
|
4
|
+
export default defineEventHandler(async (event) => {
|
|
5
|
+
const user = requireAuth(event);
|
|
6
|
+
const db = useDB();
|
|
7
|
+
const input = await parseBody(event, createDocsSiteSchema);
|
|
8
|
+
|
|
9
|
+
return createDocsSite(db, user.id, input);
|
|
10
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { getFederatedHubPost } from '@commonpub/server';
|
|
2
|
+
|
|
3
|
+
export default defineEventHandler(async (event) => {
|
|
4
|
+
requireFeature('federation');
|
|
5
|
+
requireFeature('federateHubs');
|
|
6
|
+
|
|
7
|
+
const db = useDB();
|
|
8
|
+
const { postId } = parseParams(event, { postId: 'uuid' });
|
|
9
|
+
|
|
10
|
+
const post = await getFederatedHubPost(db, postId);
|
|
11
|
+
if (!post) {
|
|
12
|
+
throw createError({ statusCode: 404, statusMessage: 'Federated hub post not found' });
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return post;
|
|
16
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { listFederatedHubPosts } from '@commonpub/server';
|
|
2
|
+
|
|
3
|
+
export default defineEventHandler(async (event) => {
|
|
4
|
+
requireFeature('federation');
|
|
5
|
+
requireFeature('federateHubs');
|
|
6
|
+
|
|
7
|
+
const db = useDB();
|
|
8
|
+
const { id } = parseParams(event, { id: 'uuid' });
|
|
9
|
+
const query = getQuery(event);
|
|
10
|
+
|
|
11
|
+
return listFederatedHubPosts(db, id, {
|
|
12
|
+
limit: query.limit ? Number(query.limit) : undefined,
|
|
13
|
+
offset: query.offset ? Number(query.offset) : undefined,
|
|
14
|
+
});
|
|
15
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { getFederatedHub } from '@commonpub/server';
|
|
2
|
+
|
|
3
|
+
export default defineEventHandler(async (event) => {
|
|
4
|
+
requireFeature('federation');
|
|
5
|
+
requireFeature('federateHubs');
|
|
6
|
+
|
|
7
|
+
const db = useDB();
|
|
8
|
+
const { id } = parseParams(event, { id: 'uuid' });
|
|
9
|
+
|
|
10
|
+
const hub = await getFederatedHub(db, id);
|
|
11
|
+
if (!hub) {
|
|
12
|
+
throw createError({ statusCode: 404, statusMessage: 'Federated hub not found' });
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return hub;
|
|
16
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { boostRemoteContent } from '@commonpub/server';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
|
|
4
|
+
const boostSchema = z.object({
|
|
5
|
+
federatedContentId: z.string().uuid(),
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
export default defineEventHandler(async (event): Promise<{ success: boolean }> => {
|
|
9
|
+
requireFeature('federation');
|
|
10
|
+
const user = requireAuth(event);
|
|
11
|
+
const db = useDB();
|
|
12
|
+
const config = useConfig();
|
|
13
|
+
const { federatedContentId } = await parseBody(event, boostSchema);
|
|
14
|
+
|
|
15
|
+
const success = await boostRemoteContent(db, user.id, federatedContentId, config.instance.domain);
|
|
16
|
+
if (!success) {
|
|
17
|
+
throw createError({ statusCode: 404, statusMessage: 'Content not found' });
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return { success };
|
|
21
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { getFederatedContent } from '@commonpub/server';
|
|
2
|
+
import type { FederatedContentItem } from '@commonpub/server';
|
|
3
|
+
|
|
4
|
+
export default defineEventHandler(async (event): Promise<FederatedContentItem> => {
|
|
5
|
+
requireFeature('federation');
|
|
6
|
+
const db = useDB();
|
|
7
|
+
const { id } = parseParams(event, { id: 'uuid' });
|
|
8
|
+
|
|
9
|
+
const content = await getFederatedContent(db, id);
|
|
10
|
+
if (!content) {
|
|
11
|
+
throw createError({ statusCode: 404, statusMessage: 'Federated content not found' });
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return content;
|
|
15
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { sendFollow } from '@commonpub/server';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
|
|
4
|
+
const followSchema = z.object({
|
|
5
|
+
actorUri: z.string().url(),
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
export default defineEventHandler(async (event): Promise<{ id: string }> => {
|
|
9
|
+
requireFeature('federation');
|
|
10
|
+
const user = requireAuth(event);
|
|
11
|
+
const db = useDB();
|
|
12
|
+
const config = useConfig();
|
|
13
|
+
const { actorUri } = await parseBody(event, followSchema);
|
|
14
|
+
|
|
15
|
+
return sendFollow(db, user.id, actorUri, config.instance.domain);
|
|
16
|
+
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { activities, followRelationships, instanceMirrors, federatedContent } from '@commonpub/schema';
|
|
2
|
+
import { sql } from 'drizzle-orm';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* GET /api/federation/health
|
|
6
|
+
* Public health check for federation status. No auth required.
|
|
7
|
+
*/
|
|
8
|
+
export default defineEventHandler(async (event) => {
|
|
9
|
+
const config = useConfig();
|
|
10
|
+
if (!config.features.federation) {
|
|
11
|
+
return { enabled: false };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const db = useDB();
|
|
15
|
+
|
|
16
|
+
const [activityStats] = await db
|
|
17
|
+
.select({
|
|
18
|
+
pending: sql<number>`count(*) filter (where ${activities.direction} = 'outbound' and ${activities.status} = 'pending')`,
|
|
19
|
+
delivered: sql<number>`count(*) filter (where ${activities.direction} = 'outbound' and ${activities.status} = 'delivered')`,
|
|
20
|
+
failed: sql<number>`count(*) filter (where ${activities.direction} = 'outbound' and ${activities.status} = 'failed')`,
|
|
21
|
+
inbound: sql<number>`count(*) filter (where ${activities.direction} = 'inbound')`,
|
|
22
|
+
})
|
|
23
|
+
.from(activities);
|
|
24
|
+
|
|
25
|
+
const [followStats] = await db
|
|
26
|
+
.select({
|
|
27
|
+
followers: sql<number>`count(*) filter (where ${followRelationships.status} = 'accepted')`,
|
|
28
|
+
})
|
|
29
|
+
.from(followRelationships);
|
|
30
|
+
|
|
31
|
+
const [mirrorStats] = await db
|
|
32
|
+
.select({
|
|
33
|
+
active: sql<number>`count(*) filter (where ${instanceMirrors.status} = 'active')`,
|
|
34
|
+
})
|
|
35
|
+
.from(instanceMirrors);
|
|
36
|
+
|
|
37
|
+
const [contentStats] = await db
|
|
38
|
+
.select({
|
|
39
|
+
total: sql<number>`count(*)`,
|
|
40
|
+
})
|
|
41
|
+
.from(federatedContent);
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
enabled: true,
|
|
45
|
+
domain: config.instance.domain,
|
|
46
|
+
activities: {
|
|
47
|
+
pending: Number(activityStats?.pending ?? 0),
|
|
48
|
+
delivered: Number(activityStats?.delivered ?? 0),
|
|
49
|
+
failed: Number(activityStats?.failed ?? 0),
|
|
50
|
+
inbound: Number(activityStats?.inbound ?? 0),
|
|
51
|
+
},
|
|
52
|
+
followers: Number(followStats?.followers ?? 0),
|
|
53
|
+
mirrors: Number(mirrorStats?.active ?? 0),
|
|
54
|
+
federatedContent: Number(contentStats?.total ?? 0),
|
|
55
|
+
};
|
|
56
|
+
});
|