Some checks failed
Veza CI / Notify on failure (push) Blocked by required conditions
Veza CI / Rust (Stream Server) (push) Successful in 5m33s
Security Scan / Secret Scanning (gitleaks) (push) Failing after 1m0s
Veza CI / Backend (Go) (push) Failing after 9m37s
Veza CI / Frontend (Web) (push) Has been cancelled
E2E Playwright / e2e (full) (push) Has been cancelled
End-to-end DMCA workflow. Public submission, admin queue, takedown
flips track to is_public=false + dmca_blocked=true, playback paths
return 451 Unavailable For Legal Reasons.
Backend
- migrations/988_dmca_notices.sql + rollback : table dmca_notices
(id, status, claimant_*, work_description, infringing_track_id FK,
sworn_statement_at, takedown_at, counter_notice_at, restored_at,
audit_log JSONB, created_at, updated_at). Adds tracks.dmca_blocked
BOOLEAN. Partial indexes for the pending queue + per-track lookup.
Status enum constrained via CHECK.
- internal/models/dmca_notice.go + DmcaBlocked field on Track.
- internal/services/dmca_service.go : CreateNotice + ListPending +
Takedown + Dismiss. Takedown is a single transaction that flips the
track's flags AND appends an audit_log entry — partial state can't
happen if the track was deleted between fetch and update.
- internal/handlers/dmca_handler.go : POST /api/v1/dmca/notice (public),
GET /api/v1/admin/dmca/notices (paginated), POST /:id/takedown,
POST /:id/dismiss. sworn_statement=false → 400. Conflict → 409.
Track gone after notice → 410.
- internal/api/routes_legal.go : route registration. Admin chain :
RequireAuth + RequireAdmin + RequireMFA (same as moderation routes).
- internal/core/track/track_hls_handler.go : both StreamTrack +
DownloadTrack now early-return 451 when track.DmcaBlocked. Owner
cannot bypass — only an admin restoring the notice clears the gate.
- internal/services/dmca_service_test.go : audit_log append helpers,
malformed-JSON rejection, ordering preservation.
Frontend
- apps/web/src/features/legal/pages/DmcaNoticePage.tsx : public form
at /legal/dmca/notice. Validates sworn-statement checkbox client-side.
Receipt panel shows the notice ID after submission.
- apps/web/src/services/api/dmca.ts : thin client (POST /dmca/notice).
- routeConfig + lazy registry updated for the new route.
- DmcaPage now links to /legal/dmca/notice instead of saying "form
pending".
E2E
- tests/e2e/29-dmca-notice.spec.ts : 3 tests. (1) anonymous submit
yields 201 + pending receipt. (2) sworn_statement=false rejected
with 400. (3) admin takedown gates playback with 451 — gated behind
E2E_DMCA_ADMIN=1 because admin path requires MFA-bearing seed.
Acceptance (Day 14) : public submission produces a pending notice,
admin takedown blocks playback at 451. Lab-side validation pending
admin MFA seed for the e2e admin pathway.
W3 progress : Redis Sentinel ✓ · MinIO distribué ✓ · CDN ✓ · DMCA ✓ ·
embed ⏳ Day 15.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
371 lines
9.4 KiB
TypeScript
371 lines
9.4 KiB
TypeScript
import { createLazyComponent } from './createLazyComponent';
|
|
|
|
export const LazyDashboard = createLazyComponent(
|
|
() => import('@/features/dashboard/pages/DashboardPage'),
|
|
undefined,
|
|
'Dashboard',
|
|
);
|
|
export const LazyChat = createLazyComponent(
|
|
() =>
|
|
import('@/features/chat/pages/ChatPage').then((m) => ({ default: m.ChatPage })),
|
|
undefined,
|
|
'Chat',
|
|
);
|
|
export const LazyChatJoin = createLazyComponent(
|
|
() =>
|
|
import('@/features/chat/pages/ChatJoinPage').then((m) => ({ default: m.ChatJoinPage })),
|
|
undefined,
|
|
'Chat Join',
|
|
);
|
|
export const LazyLibrary = createLazyComponent(
|
|
() =>
|
|
import('@/features/library/pages/LibraryPage').then((m) => ({
|
|
default: m.LibraryPage,
|
|
})),
|
|
undefined,
|
|
'Library',
|
|
);
|
|
export const LazyProfile = createLazyComponent(
|
|
() =>
|
|
import('@/features/profile/pages/UserProfilePage').then((m) => ({
|
|
default: m.UserProfilePage,
|
|
})),
|
|
undefined,
|
|
'Profile',
|
|
);
|
|
export const LazySettings = createLazyComponent(
|
|
() =>
|
|
import('@/features/settings/pages/SettingsPage').then((m) => ({
|
|
default: m.SettingsPage,
|
|
})),
|
|
undefined,
|
|
'Settings',
|
|
);
|
|
export const LazyLogin = createLazyComponent(
|
|
() => import('@/features/auth/pages/LoginPage'),
|
|
undefined,
|
|
'Login',
|
|
);
|
|
export const LazyRegister = createLazyComponent(
|
|
() => import('@/features/auth/pages/RegisterPage'),
|
|
undefined,
|
|
'Register',
|
|
);
|
|
export const LazyForgotPassword = createLazyComponent(
|
|
() => import('@/features/auth/pages/ForgotPasswordPage'),
|
|
undefined,
|
|
'Forgot Password',
|
|
);
|
|
export const LazyVerifyEmail = createLazyComponent(
|
|
() => import('@/features/auth/pages/VerifyEmailPage'),
|
|
undefined,
|
|
'Verify Email',
|
|
);
|
|
export const LazyResetPassword = createLazyComponent(
|
|
() => import('@/features/auth/pages/ResetPasswordPage'),
|
|
undefined,
|
|
'Reset Password',
|
|
);
|
|
export const LazySessions = createLazyComponent(
|
|
() => import('@/features/auth/pages/SessionsPage'),
|
|
undefined,
|
|
'Sessions',
|
|
);
|
|
export const LazyNotFound = createLazyComponent(
|
|
() => import('@/features/error/pages/NotFoundPage'),
|
|
undefined,
|
|
'Not Found',
|
|
);
|
|
export const LazyServerError = createLazyComponent(
|
|
() => import('@/features/error/pages/ServerErrorPage'),
|
|
undefined,
|
|
'Server Error',
|
|
);
|
|
export const LazyUserProfile = createLazyComponent(
|
|
() =>
|
|
import('@/features/profile/pages/UserProfilePage').then((m) => ({
|
|
default: m.UserProfilePage,
|
|
})),
|
|
undefined,
|
|
'User Profile',
|
|
);
|
|
export const LazyRoles = createLazyComponent(
|
|
() =>
|
|
import('@/features/roles/pages/RolesPage').then((m) => ({
|
|
default: m.RolesPage,
|
|
})),
|
|
undefined,
|
|
'Roles',
|
|
);
|
|
export const LazyTrackDetail = createLazyComponent(
|
|
() =>
|
|
import('@/features/tracks/pages/TrackDetailPage').then((m) => ({
|
|
default: m.TrackDetailPage,
|
|
})),
|
|
undefined,
|
|
'Track Detail',
|
|
);
|
|
export const LazyPlaylistRoutes = createLazyComponent(
|
|
() =>
|
|
import('@/features/playlists/routes').then((m) => ({
|
|
default: m.PlaylistRoutes,
|
|
})),
|
|
undefined,
|
|
'Playlists',
|
|
);
|
|
export const LazySharedPlaylistPage = createLazyComponent(
|
|
() =>
|
|
import('@/features/playlists/pages/SharedPlaylistPage').then((m) => ({
|
|
default: m.SharedPlaylistPage,
|
|
})),
|
|
undefined,
|
|
'Shared Playlist',
|
|
);
|
|
export const LazyAdminDashboard = createLazyComponent(
|
|
() =>
|
|
import('@/features/admin/pages/AdminDashboardPage').then((m) => ({
|
|
default: m.AdminDashboardPage,
|
|
})),
|
|
undefined,
|
|
'Admin Dashboard',
|
|
);
|
|
export const LazyAdminModeration = createLazyComponent(
|
|
() =>
|
|
import('@/features/admin/pages/AdminModerationPage').then((m) => ({
|
|
default: m.AdminModerationPage,
|
|
})),
|
|
undefined,
|
|
'Admin Moderation',
|
|
);
|
|
export const LazyAdminPlatform = createLazyComponent(
|
|
() =>
|
|
import('@/features/admin/pages/AdminPlatformPage').then((m) => ({
|
|
default: m.AdminPlatformPage,
|
|
})),
|
|
undefined,
|
|
'Admin Platform',
|
|
);
|
|
export const LazyAdminTransfers = createLazyComponent(
|
|
() =>
|
|
import('@/features/admin/pages/AdminTransfersPage').then((m) => ({
|
|
default: m.AdminTransfersPage,
|
|
})),
|
|
undefined,
|
|
'Admin Transfers',
|
|
);
|
|
export const LazyAnalytics = createLazyComponent(
|
|
() =>
|
|
import('@/features/analytics/pages/AnalyticsPage').then((m) => ({
|
|
default: m.AnalyticsPage,
|
|
})),
|
|
undefined,
|
|
'Analytics',
|
|
);
|
|
export const LazyWebhooks = createLazyComponent(
|
|
() =>
|
|
import('@/features/developer/pages/WebhooksPage').then((m) => ({
|
|
default: m.WebhooksPage,
|
|
})),
|
|
undefined,
|
|
'Webhooks',
|
|
);
|
|
export const LazyDesignSystemDemo = createLazyComponent(
|
|
() =>
|
|
import('@/components/demo/DesignSystemDemo').then((m) => ({
|
|
default: m.DesignSystemDemo,
|
|
})),
|
|
undefined,
|
|
'Design System Demo',
|
|
);
|
|
export const LazySocial = createLazyComponent(
|
|
() =>
|
|
import('@/features/social/pages/SocialPage').then((m) => ({
|
|
default: m.SocialPage,
|
|
})),
|
|
undefined,
|
|
'Social',
|
|
);
|
|
export const LazyFeed = createLazyComponent(
|
|
() =>
|
|
import('@/features/feed/pages/FeedPage').then((m) => ({
|
|
default: m.FeedPage,
|
|
})),
|
|
undefined,
|
|
'Feed',
|
|
);
|
|
export const LazyDiscover = createLazyComponent(
|
|
() =>
|
|
import('@/features/discover/pages/DiscoverPage').then((m) => ({
|
|
default: m.DiscoverPage,
|
|
})),
|
|
undefined,
|
|
'Discover',
|
|
);
|
|
export const LazyGear = createLazyComponent(
|
|
() =>
|
|
import('@/features/inventory/pages/GearPage').then((m) => ({
|
|
default: m.GearPage,
|
|
})),
|
|
undefined,
|
|
'Gear',
|
|
);
|
|
export const LazyLive = createLazyComponent(
|
|
() =>
|
|
import('@/features/live/pages/LivePage').then((m) => ({
|
|
default: m.LivePage,
|
|
})),
|
|
undefined,
|
|
'Live',
|
|
);
|
|
export const LazyGoLive = createLazyComponent(
|
|
() =>
|
|
import('@/features/live/pages/GoLivePage').then((m) => ({
|
|
default: m.GoLivePage,
|
|
})),
|
|
undefined,
|
|
'Go Live',
|
|
);
|
|
export const LazyListenTogether = createLazyComponent(
|
|
() =>
|
|
import('@/features/player/pages/ListenTogetherPage').then((m) => ({
|
|
default: m.ListenTogetherPage,
|
|
})),
|
|
undefined,
|
|
'Listen Together',
|
|
);
|
|
export const LazyCloud = createLazyComponent(
|
|
() => import('@/features/cloud/pages/CloudPage'),
|
|
undefined,
|
|
'Cloud',
|
|
);
|
|
export const LazyQueue = createLazyComponent(
|
|
() =>
|
|
import('@/features/library/pages/QueuePage').then((m) => ({
|
|
default: m.QueuePage,
|
|
})),
|
|
undefined,
|
|
'Queue',
|
|
);
|
|
export const LazyDeveloper = createLazyComponent(
|
|
() =>
|
|
import('@/features/developer/pages/DeveloperDashboardPage').then((m) => ({
|
|
default: m.DeveloperDashboardPage,
|
|
})),
|
|
undefined,
|
|
'Developer',
|
|
);
|
|
export const LazyNotifications = createLazyComponent(
|
|
() =>
|
|
import('@/features/notifications/pages/NotificationsPage').then((m) => ({
|
|
default: m.NotificationsPage,
|
|
})),
|
|
undefined,
|
|
'Notifications',
|
|
);
|
|
export const LazyMarketplace = createLazyComponent(
|
|
() =>
|
|
import('@/features/marketplace/pages/MarketplacePage').then((m) => ({
|
|
default: m.MarketplacePage,
|
|
})),
|
|
undefined,
|
|
'Marketplace',
|
|
);
|
|
export const LazySearch = createLazyComponent(
|
|
() =>
|
|
import('@/features/search/pages/SearchPage').then((m) => ({
|
|
default: m.SearchPage,
|
|
})),
|
|
undefined,
|
|
'Search',
|
|
);
|
|
export const LazySellerDashboard = createLazyComponent(
|
|
() =>
|
|
import('@/features/seller/pages/SellerDashboardPage').then((m) => ({
|
|
default: m.SellerDashboardPage,
|
|
})),
|
|
undefined,
|
|
'Seller Dashboard',
|
|
);
|
|
export const LazyWishlist = createLazyComponent(
|
|
() =>
|
|
import('@/features/marketplace/pages/WishlistPage').then((m) => ({
|
|
default: m.WishlistPage,
|
|
})),
|
|
undefined,
|
|
'Wishlist',
|
|
);
|
|
export const LazyPurchases = createLazyComponent(
|
|
() =>
|
|
import('@/features/purchases/pages/PurchasesPage').then((m) => ({
|
|
default: m.PurchasesPage,
|
|
})),
|
|
undefined,
|
|
'Purchases',
|
|
);
|
|
export const LazyProductDetail = createLazyComponent(
|
|
() =>
|
|
import('@/features/marketplace/pages/ProductDetailPage').then((m) => ({
|
|
default: m.ProductDetailPage,
|
|
})),
|
|
undefined,
|
|
'Product Detail',
|
|
);
|
|
export const LazyCheckoutComplete = createLazyComponent(
|
|
() =>
|
|
import('@/features/checkout/CheckoutCompletePage').then((m) => ({
|
|
default: m.CheckoutCompletePage,
|
|
})),
|
|
undefined,
|
|
'Checkout Complete',
|
|
);
|
|
export const LazySubscription = createLazyComponent(
|
|
() =>
|
|
import('@/features/subscription/pages/SubscriptionPage').then((m) => ({
|
|
default: m.SubscriptionPage,
|
|
})),
|
|
undefined,
|
|
'Subscription',
|
|
);
|
|
export const LazyDistribution = createLazyComponent(
|
|
() =>
|
|
import('@/features/distribution/pages/DistributionPage').then((m) => ({
|
|
default: m.DistributionPage,
|
|
})),
|
|
undefined,
|
|
'Distribution',
|
|
);
|
|
export const LazyEducation = createLazyComponent(
|
|
() =>
|
|
import('@/features/education/pages/EducationPage').then((m) => ({
|
|
default: m.EducationPage,
|
|
})),
|
|
undefined,
|
|
'Education',
|
|
);
|
|
// v0.13.5 TASK-MKT-004: Support page
|
|
export const LazySupport = createLazyComponent(
|
|
() =>
|
|
import('@/features/support/pages/SupportPage').then((m) => ({
|
|
default: m.SupportPage,
|
|
})),
|
|
undefined,
|
|
'Support',
|
|
);
|
|
// Pre-launch landing page
|
|
export const LazyLanding = createLazyComponent(
|
|
() => import('@/features/landing/pages/LandingPage'),
|
|
undefined,
|
|
'Landing',
|
|
);
|
|
// Legal — DMCA notice & designated agent
|
|
export const LazyDmca = createLazyComponent(
|
|
() => import('@/features/legal/pages/DmcaPage'),
|
|
undefined,
|
|
'Dmca',
|
|
);
|
|
// Legal — DMCA notice submission form (v1.0.9 W3 Day 14)
|
|
export const LazyDmcaNotice = createLazyComponent(
|
|
() => import('@/features/legal/pages/DmcaNoticePage'),
|
|
undefined,
|
|
'DmcaNotice',
|
|
);
|