@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
package/plugins/auth.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Auth plugin — hydrates session state from SSR context.
|
|
2
|
+
// On the server, reads from event.context.auth (enriched by middleware).
|
|
3
|
+
// On the client, trusts the SSR-hydrated useState values — refreshSession()
|
|
4
|
+
// in the layout's onMounted handles lazy revalidation.
|
|
5
|
+
import type { ClientAuthUser, ClientAuthSession } from '../composables/useAuth';
|
|
6
|
+
|
|
7
|
+
export default defineNuxtPlugin(() => {
|
|
8
|
+
const user = useState<ClientAuthUser | null>('auth-user', () => null);
|
|
9
|
+
const session = useState<ClientAuthSession | null>('auth-session', () => null);
|
|
10
|
+
|
|
11
|
+
if (import.meta.server) {
|
|
12
|
+
const event = useRequestEvent();
|
|
13
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
14
|
+
const authCtx = (event?.context as any)?.auth as { user?: ClientAuthUser; session?: ClientAuthSession } | undefined;
|
|
15
|
+
if (authCtx) {
|
|
16
|
+
user.value = (authCtx.user as ClientAuthUser) ?? null;
|
|
17
|
+
session.value = (authCtx.session as ClientAuthSession) ?? null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Client: useState already hydrated from SSR payload — no fetch needed here.
|
|
22
|
+
// refreshSession() in the default layout's onMounted() handles revalidation.
|
|
23
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { listAuditLogs } from '@commonpub/server';
|
|
2
|
+
import type { PaginatedResponse, AuditLogItem } from '@commonpub/server';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
|
|
5
|
+
const auditQuerySchema = z.object({
|
|
6
|
+
limit: z.coerce.number().int().positive().max(100).optional(),
|
|
7
|
+
offset: z.coerce.number().int().min(0).optional(),
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
export default defineEventHandler(async (event): Promise<PaginatedResponse<AuditLogItem>> => {
|
|
11
|
+
requireFeature('admin');
|
|
12
|
+
requireAdmin(event);
|
|
13
|
+
const db = useDB();
|
|
14
|
+
const filters = parseQueryParams(event, auditQuerySchema);
|
|
15
|
+
|
|
16
|
+
return listAuditLogs(db, filters);
|
|
17
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { removeContent, removeFederatedContent } from '@commonpub/server';
|
|
2
|
+
|
|
3
|
+
export default defineEventHandler(async (event): Promise<void> => {
|
|
4
|
+
requireFeature('admin');
|
|
5
|
+
const admin = requireAdmin(event);
|
|
6
|
+
const db = useDB();
|
|
7
|
+
const { id } = parseParams(event, { id: 'uuid' });
|
|
8
|
+
|
|
9
|
+
try {
|
|
10
|
+
return await removeContent(db, id, admin.id);
|
|
11
|
+
} catch {
|
|
12
|
+
// Content not found in local table — try federated content table
|
|
13
|
+
return await removeFederatedContent(db, id, admin.id);
|
|
14
|
+
}
|
|
15
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { contentItems } from '@commonpub/schema';
|
|
2
|
+
import { eq } from 'drizzle-orm';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* PATCH /api/admin/content/[id]
|
|
7
|
+
* Update admin-managed content fields (featured status, visibility).
|
|
8
|
+
*/
|
|
9
|
+
export default defineEventHandler(async (event) => {
|
|
10
|
+
requireAdmin(event);
|
|
11
|
+
|
|
12
|
+
const contentId = getRouterParam(event, 'id')!;
|
|
13
|
+
const body = await parseBody(event, z.object({
|
|
14
|
+
isFeatured: z.boolean().optional(),
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
const db = useDB();
|
|
18
|
+
|
|
19
|
+
const updates: Record<string, unknown> = {};
|
|
20
|
+
if (body.isFeatured !== undefined) updates.isFeatured = body.isFeatured;
|
|
21
|
+
|
|
22
|
+
if (Object.keys(updates).length === 0) {
|
|
23
|
+
throw createError({ statusCode: 400, statusMessage: 'No fields to update' });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const result = await db
|
|
27
|
+
.update(contentItems)
|
|
28
|
+
.set(updates)
|
|
29
|
+
.where(eq(contentItems.id, contentId))
|
|
30
|
+
.returning({ id: contentItems.id, isFeatured: contentItems.isFeatured });
|
|
31
|
+
|
|
32
|
+
if (result.length === 0) {
|
|
33
|
+
throw createError({ statusCode: 404, statusMessage: 'Content not found' });
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return result[0];
|
|
37
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { listFederationActivity } from '@commonpub/server';
|
|
2
|
+
|
|
3
|
+
const VALID_DIRECTIONS = ['inbound', 'outbound'] as const;
|
|
4
|
+
const VALID_STATUSES = ['pending', 'delivered', 'failed', 'processed'] as const;
|
|
5
|
+
|
|
6
|
+
export default defineEventHandler(async (event) => {
|
|
7
|
+
requireFeature('admin');
|
|
8
|
+
requireAdmin(event);
|
|
9
|
+
const db = useDB();
|
|
10
|
+
|
|
11
|
+
const query = getQuery(event);
|
|
12
|
+
|
|
13
|
+
const direction = VALID_DIRECTIONS.includes(query.direction as typeof VALID_DIRECTIONS[number])
|
|
14
|
+
? (query.direction as 'inbound' | 'outbound')
|
|
15
|
+
: undefined;
|
|
16
|
+
|
|
17
|
+
const status = VALID_STATUSES.includes(query.status as typeof VALID_STATUSES[number])
|
|
18
|
+
? (query.status as 'pending' | 'delivered' | 'failed' | 'processed')
|
|
19
|
+
: undefined;
|
|
20
|
+
|
|
21
|
+
const limit = Math.max(1, Math.min(100, parseInt(query.limit as string, 10) || 50));
|
|
22
|
+
const offset = Math.max(0, parseInt(query.offset as string, 10) || 0);
|
|
23
|
+
|
|
24
|
+
return listFederationActivity(db, {
|
|
25
|
+
direction,
|
|
26
|
+
status,
|
|
27
|
+
type: typeof query.type === 'string' ? query.type : undefined,
|
|
28
|
+
limit,
|
|
29
|
+
offset,
|
|
30
|
+
});
|
|
31
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { registerOAuthClient } from '@commonpub/server';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
|
|
4
|
+
const registerSchema = z.object({
|
|
5
|
+
instanceDomain: z.string().min(3).max(255),
|
|
6
|
+
redirectUris: z.array(z.string().url()).min(1),
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
export default defineEventHandler(async (event) => {
|
|
10
|
+
requireFeature('federation');
|
|
11
|
+
requireAdmin(event);
|
|
12
|
+
const db = useDB();
|
|
13
|
+
const { instanceDomain, redirectUris } = await parseBody(event, registerSchema);
|
|
14
|
+
|
|
15
|
+
return registerOAuthClient(db, instanceDomain, redirectUris);
|
|
16
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { listFederatedHubs } from '@commonpub/server';
|
|
2
|
+
|
|
3
|
+
export default defineEventHandler(async (event) => {
|
|
4
|
+
requireFeature('federation');
|
|
5
|
+
requireFeature('federateHubs');
|
|
6
|
+
requireAdmin(event);
|
|
7
|
+
|
|
8
|
+
const db = useDB();
|
|
9
|
+
return listFederatedHubs(db);
|
|
10
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { followRemoteHub, sendHubFollow, resolveRemoteActor } from '@commonpub/server';
|
|
3
|
+
|
|
4
|
+
const bodySchema = z.object({
|
|
5
|
+
actorUri: z.string().url(),
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
export default defineEventHandler(async (event) => {
|
|
9
|
+
requireFeature('federation');
|
|
10
|
+
requireFeature('federateHubs');
|
|
11
|
+
requireAdmin(event);
|
|
12
|
+
|
|
13
|
+
const db = useDB();
|
|
14
|
+
const config = useConfig();
|
|
15
|
+
const body = await parseBody(event, bodySchema);
|
|
16
|
+
|
|
17
|
+
const actor = await resolveRemoteActor(db, body.actorUri);
|
|
18
|
+
if (!actor) {
|
|
19
|
+
throw createError({ statusCode: 404, statusMessage: 'Could not resolve remote hub actor' });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (actor.type !== 'Group') {
|
|
23
|
+
throw createError({ statusCode: 400, statusMessage: 'Actor is not a Group (hub)' });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const domain = new URL(body.actorUri).hostname;
|
|
27
|
+
const slugMatch = body.actorUri.match(/\/hubs\/([^/]+)$/);
|
|
28
|
+
const remoteSlug = slugMatch?.[1] ?? actor.preferredUsername ?? 'unknown';
|
|
29
|
+
|
|
30
|
+
const result = await followRemoteHub(db, body.actorUri, {
|
|
31
|
+
originDomain: domain,
|
|
32
|
+
remoteSlug,
|
|
33
|
+
name: actor.name ?? actor.preferredUsername ?? remoteSlug,
|
|
34
|
+
description: actor.summary ?? undefined,
|
|
35
|
+
iconUrl: actor.icon?.url ?? undefined,
|
|
36
|
+
url: `https://${domain}/hubs/${remoteSlug}`,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
await sendHubFollow(db, body.actorUri, config.instance.domain);
|
|
40
|
+
|
|
41
|
+
return { success: true, hubId: result.id, created: result.created };
|
|
42
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { getMirror, backfillFromOutbox } from '@commonpub/server';
|
|
2
|
+
/** Extract clean domain from URL */
|
|
3
|
+
function extractDomain(url: string): string {
|
|
4
|
+
try { return new URL(url).hostname; }
|
|
5
|
+
catch { return url.replace(/^https?:\/\//, '').replace(/[:/].*$/, ''); }
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* POST /api/admin/federation/mirrors/[id]/backfill
|
|
10
|
+
* Crawl the remote instance's outbox to import historical content.
|
|
11
|
+
* Admin only.
|
|
12
|
+
*/
|
|
13
|
+
export default defineEventHandler(async (event) => {
|
|
14
|
+
requireAdmin(event);
|
|
15
|
+
|
|
16
|
+
const config = useConfig();
|
|
17
|
+
if (!config.features.federation) {
|
|
18
|
+
throw createError({ statusCode: 404, statusMessage: 'Not Found' });
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const mirrorId = getRouterParam(event, 'id')!;
|
|
22
|
+
const db = useDB();
|
|
23
|
+
|
|
24
|
+
const mirror = await getMirror(db, mirrorId);
|
|
25
|
+
if (!mirror) {
|
|
26
|
+
throw createError({ statusCode: 404, statusMessage: 'Mirror not found' });
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const runtimeConfig = useRuntimeConfig();
|
|
30
|
+
const domain = extractDomain((runtimeConfig.public?.siteUrl as string) || `https://${config.instance.domain}`);
|
|
31
|
+
|
|
32
|
+
const result = await backfillFromOutbox(db, mirror.remoteActorUri, domain);
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
mirrorId: mirror.id,
|
|
36
|
+
remoteDomain: mirror.remoteDomain,
|
|
37
|
+
...result,
|
|
38
|
+
};
|
|
39
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { cancelMirror } from '@commonpub/server';
|
|
2
|
+
|
|
3
|
+
export default defineEventHandler(async (event) => {
|
|
4
|
+
requireFeature('federation');
|
|
5
|
+
requireAdmin(event);
|
|
6
|
+
const db = useDB();
|
|
7
|
+
const { id } = parseParams(event, { id: 'uuid' });
|
|
8
|
+
|
|
9
|
+
await cancelMirror(db, id);
|
|
10
|
+
return { success: true };
|
|
11
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { getMirror } from '@commonpub/server';
|
|
2
|
+
|
|
3
|
+
export default defineEventHandler(async (event) => {
|
|
4
|
+
requireFeature('federation');
|
|
5
|
+
requireAdmin(event);
|
|
6
|
+
const db = useDB();
|
|
7
|
+
const { id } = parseParams(event, { id: 'uuid' });
|
|
8
|
+
|
|
9
|
+
const mirror = await getMirror(db, id);
|
|
10
|
+
if (!mirror) {
|
|
11
|
+
throw createError({ statusCode: 404, statusMessage: 'Mirror not found' });
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return mirror;
|
|
15
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { pauseMirror, resumeMirror } from '@commonpub/server';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
|
|
4
|
+
const updateMirrorSchema = z.object({
|
|
5
|
+
action: z.enum(['pause', 'resume']),
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
export default defineEventHandler(async (event) => {
|
|
9
|
+
requireFeature('federation');
|
|
10
|
+
requireAdmin(event);
|
|
11
|
+
const db = useDB();
|
|
12
|
+
const { id } = parseParams(event, { id: 'uuid' });
|
|
13
|
+
const { action } = await parseBody(event, updateMirrorSchema);
|
|
14
|
+
|
|
15
|
+
if (action === 'pause') {
|
|
16
|
+
await pauseMirror(db, id);
|
|
17
|
+
} else {
|
|
18
|
+
await resumeMirror(db, id);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return { success: true };
|
|
22
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { createMirror } from '@commonpub/server';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
|
|
4
|
+
const createMirrorSchema = z.object({
|
|
5
|
+
remoteDomain: z.string().min(3).max(255),
|
|
6
|
+
remoteActorUri: z.string().url(),
|
|
7
|
+
direction: z.enum(['pull', 'push']),
|
|
8
|
+
filterContentTypes: z.array(z.string()).nullable().optional(),
|
|
9
|
+
filterTags: z.array(z.string()).nullable().optional(),
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
export default defineEventHandler(async (event) => {
|
|
13
|
+
requireFeature('federation');
|
|
14
|
+
requireAdmin(event);
|
|
15
|
+
const db = useDB();
|
|
16
|
+
const input = await parseBody(event, createMirrorSchema);
|
|
17
|
+
|
|
18
|
+
const config = useConfig();
|
|
19
|
+
return createMirror(
|
|
20
|
+
db,
|
|
21
|
+
input.remoteDomain,
|
|
22
|
+
input.remoteActorUri,
|
|
23
|
+
input.direction,
|
|
24
|
+
config.instance.domain,
|
|
25
|
+
{
|
|
26
|
+
contentTypes: input.filterContentTypes ?? undefined,
|
|
27
|
+
tags: input.filterTags ?? undefined,
|
|
28
|
+
},
|
|
29
|
+
);
|
|
30
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { eq, and, desc } from 'drizzle-orm';
|
|
2
|
+
import { activities } from '@commonpub/schema';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* GET /api/admin/federation/pending
|
|
6
|
+
* Lists pending outbound activities for diagnostic purposes.
|
|
7
|
+
* Helps debug delivery issues (stuck Delete activities, etc.)
|
|
8
|
+
*/
|
|
9
|
+
export default defineEventHandler(async (event) => {
|
|
10
|
+
requireFeature('admin');
|
|
11
|
+
requireAdmin(event);
|
|
12
|
+
const db = useDB();
|
|
13
|
+
|
|
14
|
+
const pending = await db
|
|
15
|
+
.select()
|
|
16
|
+
.from(activities)
|
|
17
|
+
.where(and(eq(activities.direction, 'outbound'), eq(activities.status, 'pending')))
|
|
18
|
+
.orderBy(desc(activities.createdAt))
|
|
19
|
+
.limit(50);
|
|
20
|
+
|
|
21
|
+
return { count: pending.length, activities: pending };
|
|
22
|
+
});
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { contentItems, hubs, hubPosts } from '@commonpub/schema';
|
|
2
|
+
import { federateContent, federateHubPost, federateHubActor } from '@commonpub/server';
|
|
3
|
+
import { eq, isNull } from 'drizzle-orm';
|
|
4
|
+
import { extractDomain } from '../../../utils/inbox';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* POST /api/admin/federation/refederate
|
|
8
|
+
* Queue all published content AND hub posts for federation delivery.
|
|
9
|
+
* Useful after establishing new mirrors or enabling federation.
|
|
10
|
+
*
|
|
11
|
+
* Body: { contentId?: string, hubsOnly?: boolean } — if omitted, re-federates ALL
|
|
12
|
+
*/
|
|
13
|
+
export default defineEventHandler(async (event) => {
|
|
14
|
+
requireAdmin(event);
|
|
15
|
+
|
|
16
|
+
const config = useConfig();
|
|
17
|
+
if (!config.features.federation) {
|
|
18
|
+
throw createError({ statusCode: 404, statusMessage: 'Not Found' });
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const body = await readBody(event);
|
|
22
|
+
const contentId = body?.contentId as string | undefined;
|
|
23
|
+
const hubsOnly = body?.hubsOnly === true;
|
|
24
|
+
|
|
25
|
+
const db = useDB();
|
|
26
|
+
const runtimeConfig = useRuntimeConfig();
|
|
27
|
+
const domain = extractDomain((runtimeConfig.public?.siteUrl as string) || `https://${config.instance.domain}`);
|
|
28
|
+
|
|
29
|
+
if (contentId) {
|
|
30
|
+
await federateContent(db, contentId, domain);
|
|
31
|
+
return { queued: 1 };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
let contentQueued = 0;
|
|
35
|
+
let hubsQueued = 0;
|
|
36
|
+
let hubPostsQueued = 0;
|
|
37
|
+
|
|
38
|
+
// Re-federate published content (unless hubsOnly)
|
|
39
|
+
if (!hubsOnly) {
|
|
40
|
+
const published = await db
|
|
41
|
+
.select({ id: contentItems.id })
|
|
42
|
+
.from(contentItems)
|
|
43
|
+
.where(eq(contentItems.status, 'published'));
|
|
44
|
+
|
|
45
|
+
for (const item of published) {
|
|
46
|
+
try {
|
|
47
|
+
await federateContent(db, item.id, domain);
|
|
48
|
+
contentQueued++;
|
|
49
|
+
} catch {
|
|
50
|
+
// Skip items that fail
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Re-federate hubs: announce each hub's Group actor + all hub posts
|
|
56
|
+
if (config.features.federateHubs) {
|
|
57
|
+
const allHubs = await db
|
|
58
|
+
.select({ id: hubs.id })
|
|
59
|
+
.from(hubs)
|
|
60
|
+
.where(isNull(hubs.deletedAt));
|
|
61
|
+
|
|
62
|
+
for (const hub of allHubs) {
|
|
63
|
+
try {
|
|
64
|
+
await federateHubActor(db, hub.id, domain);
|
|
65
|
+
hubsQueued++;
|
|
66
|
+
} catch {
|
|
67
|
+
// Skip hubs that fail
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const posts = await db
|
|
71
|
+
.select({ id: hubPosts.id })
|
|
72
|
+
.from(hubPosts)
|
|
73
|
+
.where(eq(hubPosts.hubId, hub.id));
|
|
74
|
+
|
|
75
|
+
for (const post of posts) {
|
|
76
|
+
try {
|
|
77
|
+
await federateHubPost(db, post.id, hub.id, domain);
|
|
78
|
+
hubPostsQueued++;
|
|
79
|
+
} catch {
|
|
80
|
+
// Skip posts that fail
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
queued: contentQueued + hubsQueued + hubPostsQueued,
|
|
88
|
+
content: contentQueued,
|
|
89
|
+
hubs: hubsQueued,
|
|
90
|
+
hubPosts: hubPostsQueued,
|
|
91
|
+
};
|
|
92
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { eq, and, isNull } from 'drizzle-orm';
|
|
2
|
+
import { federatedContent } from '@commonpub/schema';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* POST /api/admin/federation/repair-types
|
|
6
|
+
* Re-fetches source objects for federated content with missing cpubType
|
|
7
|
+
* and updates them based on the cpub:type extension field.
|
|
8
|
+
*/
|
|
9
|
+
export default defineEventHandler(async (event) => {
|
|
10
|
+
requireFeature('admin');
|
|
11
|
+
requireAdmin(event);
|
|
12
|
+
const db = useDB();
|
|
13
|
+
|
|
14
|
+
const rows = await db
|
|
15
|
+
.select({
|
|
16
|
+
id: federatedContent.id,
|
|
17
|
+
objectUri: federatedContent.objectUri,
|
|
18
|
+
})
|
|
19
|
+
.from(federatedContent)
|
|
20
|
+
.where(and(
|
|
21
|
+
isNull(federatedContent.cpubType),
|
|
22
|
+
isNull(federatedContent.deletedAt),
|
|
23
|
+
))
|
|
24
|
+
.limit(500);
|
|
25
|
+
|
|
26
|
+
let updated = 0;
|
|
27
|
+
let errors = 0;
|
|
28
|
+
|
|
29
|
+
for (const row of rows) {
|
|
30
|
+
try {
|
|
31
|
+
const controller = new AbortController();
|
|
32
|
+
const timeout = setTimeout(() => controller.abort(), 15_000);
|
|
33
|
+
const response = await fetch(row.objectUri, {
|
|
34
|
+
headers: { Accept: 'application/activity+json, application/ld+json' },
|
|
35
|
+
signal: controller.signal,
|
|
36
|
+
});
|
|
37
|
+
clearTimeout(timeout);
|
|
38
|
+
|
|
39
|
+
if (!response.ok) { errors++; continue; }
|
|
40
|
+
|
|
41
|
+
const object = await response.json() as Record<string, unknown>;
|
|
42
|
+
const cpubType = typeof object['cpub:type'] === 'string' ? object['cpub:type'] : null;
|
|
43
|
+
|
|
44
|
+
if (cpubType) {
|
|
45
|
+
await db
|
|
46
|
+
.update(federatedContent)
|
|
47
|
+
.set({ cpubType, updatedAt: new Date() })
|
|
48
|
+
.where(eq(federatedContent.id, row.id));
|
|
49
|
+
updated++;
|
|
50
|
+
}
|
|
51
|
+
} catch {
|
|
52
|
+
errors++;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return { total: rows.length, updated, errors };
|
|
57
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { activities } from '@commonpub/schema';
|
|
2
|
+
import { eq, and } from 'drizzle-orm';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* POST /api/admin/federation/retry
|
|
6
|
+
* Reset failed activities to pending so the delivery worker retries them.
|
|
7
|
+
* Optionally filter by activity ID.
|
|
8
|
+
*
|
|
9
|
+
* Body: { activityId?: string } — if omitted, retries ALL failed activities
|
|
10
|
+
*/
|
|
11
|
+
export default defineEventHandler(async (event) => {
|
|
12
|
+
requireAdmin(event);
|
|
13
|
+
|
|
14
|
+
const config = useConfig();
|
|
15
|
+
if (!config.features.federation) {
|
|
16
|
+
throw createError({ statusCode: 404, statusMessage: 'Not Found' });
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const body = await readBody(event);
|
|
20
|
+
const activityId = body?.activityId as string | undefined;
|
|
21
|
+
|
|
22
|
+
const db = useDB();
|
|
23
|
+
|
|
24
|
+
if (activityId) {
|
|
25
|
+
const result = await db
|
|
26
|
+
.update(activities)
|
|
27
|
+
.set({ status: 'pending', attempts: 0, error: null, updatedAt: new Date() })
|
|
28
|
+
.where(and(eq(activities.id, activityId), eq(activities.status, 'failed')))
|
|
29
|
+
.returning({ id: activities.id });
|
|
30
|
+
|
|
31
|
+
return { retried: result.length };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const result = await db
|
|
35
|
+
.update(activities)
|
|
36
|
+
.set({ status: 'pending', attempts: 0, error: null, updatedAt: new Date() })
|
|
37
|
+
.where(eq(activities.status, 'failed'))
|
|
38
|
+
.returning({ id: activities.id });
|
|
39
|
+
|
|
40
|
+
return { retried: result.length };
|
|
41
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { sql, eq } from 'drizzle-orm';
|
|
2
|
+
import { activities, followRelationships } from '@commonpub/schema';
|
|
3
|
+
|
|
4
|
+
export default defineEventHandler(async (event) => {
|
|
5
|
+
requireFeature('admin');
|
|
6
|
+
requireAdmin(event);
|
|
7
|
+
const db = useDB();
|
|
8
|
+
|
|
9
|
+
const [inbound, outbound, pending, failed, followers, following] = await Promise.all([
|
|
10
|
+
db.select({ count: sql<number>`count(*)::int` }).from(activities).where(eq(activities.direction, 'inbound')),
|
|
11
|
+
db.select({ count: sql<number>`count(*)::int` }).from(activities).where(eq(activities.direction, 'outbound')),
|
|
12
|
+
db.select({ count: sql<number>`count(*)::int` }).from(activities).where(eq(activities.status, 'pending')),
|
|
13
|
+
db.select({ count: sql<number>`count(*)::int` }).from(activities).where(eq(activities.status, 'failed')),
|
|
14
|
+
db.select({ count: sql<number>`count(*)::int` }).from(followRelationships).where(eq(followRelationships.status, 'accepted')),
|
|
15
|
+
db.select({ count: sql<number>`count(*)::int` }).from(followRelationships),
|
|
16
|
+
]);
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
inbound: inbound[0]?.count ?? 0,
|
|
20
|
+
outbound: outbound[0]?.count ?? 0,
|
|
21
|
+
pending: pending[0]?.count ?? 0,
|
|
22
|
+
failed: failed[0]?.count ?? 0,
|
|
23
|
+
followers: followers[0]?.count ?? 0,
|
|
24
|
+
following: following[0]?.count ?? 0,
|
|
25
|
+
};
|
|
26
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { resolveReport } from '@commonpub/server';
|
|
2
|
+
import { resolveReportSchema } from '@commonpub/schema';
|
|
3
|
+
|
|
4
|
+
export default defineEventHandler(async (event): Promise<void> => {
|
|
5
|
+
requireFeature('admin');
|
|
6
|
+
const admin = requireAdmin(event);
|
|
7
|
+
const db = useDB();
|
|
8
|
+
const { id } = parseParams(event, { id: 'uuid' });
|
|
9
|
+
const input = await parseBody(event, resolveReportSchema);
|
|
10
|
+
|
|
11
|
+
return resolveReport(db, id, input.resolution, input.status ?? 'resolved', admin.id);
|
|
12
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { listReports } from '@commonpub/server';
|
|
2
|
+
import type { PaginatedResponse, ReportListItem } from '@commonpub/server';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
|
|
5
|
+
const reportsQuerySchema = z.object({
|
|
6
|
+
limit: z.coerce.number().int().positive().max(100).optional(),
|
|
7
|
+
offset: z.coerce.number().int().min(0).optional(),
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
export default defineEventHandler(async (event): Promise<PaginatedResponse<ReportListItem>> => {
|
|
11
|
+
requireFeature('admin');
|
|
12
|
+
requireAdmin(event);
|
|
13
|
+
const db = useDB();
|
|
14
|
+
const filters = parseQueryParams(event, reportsQuerySchema);
|
|
15
|
+
|
|
16
|
+
return listReports(db, filters);
|
|
17
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { getInstanceSettings } from '@commonpub/server';
|
|
2
|
+
|
|
3
|
+
export default defineEventHandler(async (event) => {
|
|
4
|
+
requireFeature('admin');
|
|
5
|
+
requireAdmin(event);
|
|
6
|
+
const db = useDB();
|
|
7
|
+
const config = useConfig();
|
|
8
|
+
|
|
9
|
+
// Get DB-stored settings
|
|
10
|
+
const dbSettings = await getInstanceSettings(db);
|
|
11
|
+
|
|
12
|
+
// Merge with running config defaults so the UI shows actual values
|
|
13
|
+
const defaults: Record<string, string> = {
|
|
14
|
+
'instance.name': config.instance.name,
|
|
15
|
+
'instance.description': config.instance.description,
|
|
16
|
+
'instance.registrationOpen': 'true',
|
|
17
|
+
'instance.maxUploadSize': String(config.instance.maxUploadSize ?? 10485760),
|
|
18
|
+
'instance.contactEmail': config.instance.contactEmail ?? '',
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
return { ...defaults, ...dbSettings };
|
|
22
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { setInstanceSetting } from '@commonpub/server';
|
|
2
|
+
import { adminSettingSchema } from '@commonpub/schema';
|
|
3
|
+
|
|
4
|
+
export default defineEventHandler(async (event): Promise<void> => {
|
|
5
|
+
requireFeature('admin');
|
|
6
|
+
const admin = requireAdmin(event);
|
|
7
|
+
const db = useDB();
|
|
8
|
+
const input = await parseBody(event, adminSettingSchema);
|
|
9
|
+
|
|
10
|
+
return setInstanceSetting(db, input.key, input.value, admin.id);
|
|
11
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { getPlatformStats } from '@commonpub/server';
|
|
2
|
+
import type { PlatformStats } from '@commonpub/server';
|
|
3
|
+
|
|
4
|
+
export default defineEventHandler(async (event): Promise<PlatformStats> => {
|
|
5
|
+
requireFeature('admin');
|
|
6
|
+
requireAdmin(event);
|
|
7
|
+
const db = useDB();
|
|
8
|
+
return getPlatformStats(db);
|
|
9
|
+
});
|