├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ ├── feature_request.md
│ ├── task.md
│ ├── usabilty_report.md
│ └── user_story.md
├── PULL_REQUEST_TEMPLATE.md
├── PULL_REQUEST_TEMPLATE
│ ├── generic-pr.md
│ └── post_mortem.md
└── workflows
│ ├── build_server.yml
│ ├── clean_caches.yml
│ ├── generate.yml
│ ├── playwright.yml
│ └── prettier.yml
├── .gitignore
├── .husky
└── pre-commit
├── .lintstagedrc.js
├── .vercelignore
├── .yarnrc.yml
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── docker-compose-dev.yml
├── docker-compose.prod.yml
├── docker-compose.router.yml
├── docker-compose.staging.yml
├── docker-compose
├── build-local.sh
├── create-secrets.sh
└── readme.md
├── funding.json
├── mongo-init
└── init.js
├── package.json
├── packages
├── app
│ ├── .eslintrc.json
│ ├── .gitignore
│ ├── .prettierrc.json
│ ├── Dockerfile.dev
│ ├── app
│ │ ├── (legal)
│ │ │ ├── data-request
│ │ │ │ ├── components
│ │ │ │ │ └── createRequest.tsx
│ │ │ │ └── page.tsx
│ │ │ ├── privacy
│ │ │ │ └── page.tsx
│ │ │ └── terms
│ │ │ │ └── page.tsx
│ │ ├── (vod)
│ │ │ ├── archive
│ │ │ │ └── page.tsx
│ │ │ └── watch
│ │ │ │ └── page.tsx
│ │ ├── [organization]
│ │ │ ├── [event]
│ │ │ │ ├── components
│ │ │ │ │ └── EventHomeComponent.tsx
│ │ │ │ ├── layout.tsx
│ │ │ │ ├── page.tsx
│ │ │ │ ├── schedule
│ │ │ │ │ ├── components
│ │ │ │ │ │ ├── DateSelect.tsx
│ │ │ │ │ │ ├── ScheduleCard.tsx
│ │ │ │ │ │ ├── ScheduleCardModal.tsx
│ │ │ │ │ │ ├── ScheduleComponent.tsx
│ │ │ │ │ │ └── StageSelect.tsx
│ │ │ │ │ └── page.tsx
│ │ │ │ ├── speakers
│ │ │ │ │ ├── components
│ │ │ │ │ │ ├── SpeakerCard.tsx
│ │ │ │ │ │ ├── SpeakerComponent.tsx
│ │ │ │ │ │ ├── SpeakerModal.tsx
│ │ │ │ │ │ └── SpeakerPhoto.tsx
│ │ │ │ │ └── page.tsx
│ │ │ │ └── stage
│ │ │ │ │ └── components
│ │ │ │ │ ├── StageComponent.tsx
│ │ │ │ │ ├── StagePreview.tsx
│ │ │ │ │ └── UpcomingSession.tsx
│ │ │ ├── components
│ │ │ │ ├── ChannelBanner.tsx
│ │ │ │ ├── ChannelDescription.tsx
│ │ │ │ ├── ChannelPlayer.tsx
│ │ │ │ ├── ChannelShareIcons.tsx
│ │ │ │ ├── ShareVideoMenuItem.tsx
│ │ │ │ ├── UpcomingStreams.tsx
│ │ │ │ ├── VideoDownload.tsx
│ │ │ │ ├── ViewCounts.tsx
│ │ │ │ └── WatchGrid.tsx
│ │ │ ├── layout.tsx
│ │ │ ├── livestream
│ │ │ │ ├── components
│ │ │ │ │ ├── ArchiveVideosSkeleton.tsx
│ │ │ │ │ ├── CalendarReminder.tsx
│ │ │ │ │ ├── Counter.tsx
│ │ │ │ │ ├── PaginationSkeleteon.tsx
│ │ │ │ │ └── Player.tsx
│ │ │ │ └── page.tsx
│ │ │ ├── loading.tsx
│ │ │ ├── page.tsx
│ │ │ ├── playlists
│ │ │ │ ├── [playlistId]
│ │ │ │ │ └── page.tsx
│ │ │ │ └── components
│ │ │ │ │ ├── ArchivePlaylists.tsx
│ │ │ │ │ ├── OrganizationVideos.tsx
│ │ │ │ │ ├── PlaylistCard.tsx
│ │ │ │ │ ├── PlaylistShareDialog.tsx
│ │ │ │ │ ├── PlaylistVideo.tsx
│ │ │ │ │ ├── PlaylistVideoPlayer.tsx
│ │ │ │ │ ├── PlaylistVideoPlayerSkeleton.tsx
│ │ │ │ │ └── PlaylistVideos.tsx
│ │ │ ├── videos
│ │ │ │ ├── components
│ │ │ │ │ ├── ArchiveVideos.tsx
│ │ │ │ │ └── eventSelect.tsx
│ │ │ │ └── page.tsx
│ │ │ └── watch
│ │ │ │ └── page.tsx
│ │ ├── api
│ │ │ ├── auth
│ │ │ │ └── [...nextauth]
│ │ │ │ │ └── route.ts
│ │ │ ├── google
│ │ │ │ ├── callback
│ │ │ │ │ └── route.ts
│ │ │ │ └── request
│ │ │ │ │ └── route.ts
│ │ │ ├── twitter
│ │ │ │ ├── callback
│ │ │ │ │ └── route.ts
│ │ │ │ └── request
│ │ │ │ │ └── route.ts
│ │ │ └── video-download
│ │ │ │ └── route.ts
│ │ ├── auth
│ │ │ ├── auth-error
│ │ │ │ └── page.tsx
│ │ │ ├── auth-success
│ │ │ │ └── page.tsx
│ │ │ ├── login
│ │ │ │ ├── components
│ │ │ │ │ └── SignInWithSocials.tsx
│ │ │ │ └── page.tsx
│ │ │ ├── logout
│ │ │ │ └── page.tsx
│ │ │ └── magic-link
│ │ │ │ └── page.tsx
│ │ ├── embed
│ │ │ └── page.tsx
│ │ ├── error.tsx
│ │ ├── explore
│ │ │ ├── components
│ │ │ │ ├── ExploreTabs.tsx
│ │ │ │ └── FeaturedEvents.tsx
│ │ │ └── page.tsx
│ │ ├── globals.css
│ │ ├── layout.tsx
│ │ ├── not-found.tsx
│ │ ├── redirect
│ │ │ └── google
│ │ │ │ └── page.tsx
│ │ ├── speaker
│ │ │ └── [session]
│ │ │ │ ├── components
│ │ │ │ ├── ClientSidePlayer.tsx
│ │ │ │ ├── SpeakerYoutubePublishButton.tsx
│ │ │ │ └── ZoraUploadButton.tsx
│ │ │ │ ├── layout.tsx
│ │ │ │ └── page.tsx
│ │ └── studio
│ │ │ ├── (home)
│ │ │ ├── components
│ │ │ │ ├── CreateOrganizationForm.tsx
│ │ │ │ └── JoinOrganizationForm.tsx
│ │ │ ├── create
│ │ │ │ └── page.tsx
│ │ │ ├── layout.tsx
│ │ │ └── page.tsx
│ │ │ ├── [organization]
│ │ │ ├── (no-side-bar)
│ │ │ │ ├── clips
│ │ │ │ │ ├── [stageId]
│ │ │ │ │ │ ├── ClipPageContext.tsx
│ │ │ │ │ │ ├── Controls
│ │ │ │ │ │ │ ├── KeyboardShortcuts.tsx
│ │ │ │ │ │ │ ├── ZoomControls.tsx
│ │ │ │ │ │ │ └── index.tsx
│ │ │ │ │ │ ├── Player.tsx
│ │ │ │ │ │ ├── Timeline
│ │ │ │ │ │ │ ├── PlayHead.tsx
│ │ │ │ │ │ │ ├── TimelineContext.tsx
│ │ │ │ │ │ │ ├── TrimmControls.tsx
│ │ │ │ │ │ │ ├── TrimmControlsContext.tsx
│ │ │ │ │ │ │ ├── index.tsx
│ │ │ │ │ │ │ └── useTimeline.ts
│ │ │ │ │ │ ├── page.tsx
│ │ │ │ │ │ ├── sidebar
│ │ │ │ │ │ │ ├── Transcipts
│ │ │ │ │ │ │ │ ├── TranscriptText.tsx
│ │ │ │ │ │ │ │ └── index.tsx
│ │ │ │ │ │ │ ├── clips
│ │ │ │ │ │ │ │ ├── Clip.tsx
│ │ │ │ │ │ │ │ ├── ClipsContext.tsx
│ │ │ │ │ │ │ │ ├── CreateClipForm.tsx
│ │ │ │ │ │ │ │ ├── Preview.tsx
│ │ │ │ │ │ │ │ ├── TimeSetter.tsx
│ │ │ │ │ │ │ │ └── index.tsx
│ │ │ │ │ │ │ ├── index.tsx
│ │ │ │ │ │ │ └── markers
│ │ │ │ │ │ │ │ ├── AddOrEditMarkerForm.tsx
│ │ │ │ │ │ │ │ ├── DeleteMarkerButton.tsx
│ │ │ │ │ │ │ │ ├── ExtractHighlightsForm.tsx
│ │ │ │ │ │ │ │ ├── ImportMarkersForm.tsx
│ │ │ │ │ │ │ │ ├── Marker.tsx
│ │ │ │ │ │ │ │ ├── TimelineMarker.tsx
│ │ │ │ │ │ │ │ ├── index.tsx
│ │ │ │ │ │ │ │ └── markersContext.tsx
│ │ │ │ │ │ └── topBar
│ │ │ │ │ │ │ ├── SelectAnimation.tsx
│ │ │ │ │ │ │ ├── SessionSelector.tsx
│ │ │ │ │ │ │ └── index.tsx
│ │ │ │ │ └── page.tsx
│ │ │ │ └── layout.tsx
│ │ │ ├── (root)
│ │ │ │ ├── destinations
│ │ │ │ │ ├── components
│ │ │ │ │ │ ├── AddDestination.tsx
│ │ │ │ │ │ ├── DeleteDestination.tsx
│ │ │ │ │ │ ├── TwitterConnectButton.tsx
│ │ │ │ │ │ └── YoutubeConnectButton.tsx
│ │ │ │ │ └── page.tsx
│ │ │ │ ├── layout.tsx
│ │ │ │ ├── library
│ │ │ │ │ ├── [session]
│ │ │ │ │ │ ├── components
│ │ │ │ │ │ │ ├── AddSpeakersInput.tsx
│ │ │ │ │ │ │ ├── EditSessionForm.tsx
│ │ │ │ │ │ │ ├── ImageDropzone.tsx
│ │ │ │ │ │ │ ├── SessionAccordion.tsx
│ │ │ │ │ │ │ ├── SessionOptions.tsx
│ │ │ │ │ │ │ ├── SessionTranscriptions.tsx
│ │ │ │ │ │ │ ├── UploadToYoutubeButton.tsx
│ │ │ │ │ │ │ ├── UploadTwitterButton.tsx
│ │ │ │ │ │ │ └── form
│ │ │ │ │ │ │ │ ├── BasicFormFields.tsx
│ │ │ │ │ │ │ │ ├── FormActions.tsx
│ │ │ │ │ │ │ │ ├── ThumbnailSection.tsx
│ │ │ │ │ │ │ │ ├── VisibilitySelector.tsx
│ │ │ │ │ │ │ │ └── utils.ts
│ │ │ │ │ │ └── page.tsx
│ │ │ │ │ ├── components
│ │ │ │ │ │ ├── AddToPlaylistMenu.tsx
│ │ │ │ │ │ ├── ClipContentButton.tsx
│ │ │ │ │ │ ├── CreatePlaylistDialog.tsx
│ │ │ │ │ │ ├── DeleteAsset.tsx
│ │ │ │ │ │ ├── DropdownActions.tsx
│ │ │ │ │ │ ├── EmptyLibrary.tsx
│ │ │ │ │ │ ├── GetHashButton.tsx
│ │ │ │ │ │ ├── InjectUrlInput.tsx
│ │ │ │ │ │ ├── LayoutSelection.tsx
│ │ │ │ │ │ ├── Library.tsx
│ │ │ │ │ │ ├── LibraryFilter.tsx
│ │ │ │ │ │ ├── Pagination.tsx
│ │ │ │ │ │ ├── PlaylistTable.tsx
│ │ │ │ │ │ ├── TableCells.tsx
│ │ │ │ │ │ ├── UploadVideoDialog.tsx
│ │ │ │ │ │ ├── VisibilityButton.tsx
│ │ │ │ │ │ ├── misc
│ │ │ │ │ │ │ ├── PopoverActions.tsx
│ │ │ │ │ │ │ └── VideoCardWithMenu.tsx
│ │ │ │ │ │ ├── tabs
│ │ │ │ │ │ │ ├── PlaylistsTab.tsx
│ │ │ │ │ │ │ └── VideosTab.tsx
│ │ │ │ │ │ └── upload
│ │ │ │ │ │ │ ├── Dropzone.tsx
│ │ │ │ │ │ │ ├── UploadProgress.tsx
│ │ │ │ │ │ │ └── UploadVideoForm.tsx
│ │ │ │ │ ├── page.tsx
│ │ │ │ │ └── playlists
│ │ │ │ │ │ └── [playlistId]
│ │ │ │ │ │ ├── components
│ │ │ │ │ │ ├── PlaylistDetail.tsx
│ │ │ │ │ │ ├── PlaylistDropdownActions.tsx
│ │ │ │ │ │ └── PlaylistTableCells.tsx
│ │ │ │ │ │ └── page.tsx
│ │ │ │ ├── livestreams
│ │ │ │ │ ├── [streamId]
│ │ │ │ │ │ ├── components
│ │ │ │ │ │ │ ├── ClientSidePlayer.tsx
│ │ │ │ │ │ │ ├── ClipsList.tsx
│ │ │ │ │ │ │ ├── DeleteMultistream.tsx
│ │ │ │ │ │ │ ├── Destinations.tsx
│ │ │ │ │ │ │ ├── LivestreamEmbedCode.tsx
│ │ │ │ │ │ │ ├── Multistream.tsx
│ │ │ │ │ │ │ ├── PublishLivestream.tsx
│ │ │ │ │ │ │ ├── ShareAndEmbed.tsx
│ │ │ │ │ │ │ ├── Sidebar.tsx
│ │ │ │ │ │ │ ├── StageControls.tsx
│ │ │ │ │ │ │ ├── StageDataImport
│ │ │ │ │ │ │ │ ├── ImportDataButton.tsx
│ │ │ │ │ │ │ │ ├── ImportPreviewDialog.tsx
│ │ │ │ │ │ │ │ └── ViewMarkersDialog.tsx
│ │ │ │ │ │ │ ├── StreamConfigWithPlayer.tsx
│ │ │ │ │ │ │ ├── StreamHeader.tsx
│ │ │ │ │ │ │ ├── StreamHealth.tsx
│ │ │ │ │ │ │ └── StreamPlatforms
│ │ │ │ │ │ │ │ ├── CreateMultistreamTarget.tsx
│ │ │ │ │ │ │ │ ├── CreateYoutubeStream.tsx
│ │ │ │ │ │ │ │ ├── StreamPlatforms.tsx
│ │ │ │ │ │ │ │ └── forms
│ │ │ │ │ │ │ │ └── CustomRtmpForm.tsx
│ │ │ │ │ │ └── page.tsx
│ │ │ │ │ ├── components
│ │ │ │ │ │ ├── CreateLivestreamModal.tsx
│ │ │ │ │ │ ├── CreateLivestreamOptions.tsx
│ │ │ │ │ │ ├── DeleteLivestream.tsx
│ │ │ │ │ │ ├── EditLivestream.tsx
│ │ │ │ │ │ ├── ImportSchedule.tsx
│ │ │ │ │ │ ├── LivestreamActions.tsx
│ │ │ │ │ │ ├── LivestreamTable.tsx
│ │ │ │ │ │ ├── LivestreamTableCard.tsx
│ │ │ │ │ │ ├── ShareLivestream.tsx
│ │ │ │ │ │ ├── SubscriptionAlert.tsx
│ │ │ │ │ │ └── ToggleLivestreamVisibility.tsx
│ │ │ │ │ └── page.tsx
│ │ │ │ ├── page.tsx
│ │ │ │ ├── payments
│ │ │ │ │ ├── components
│ │ │ │ │ │ ├── ActiveSubscriptionView.tsx
│ │ │ │ │ │ ├── ExpiredSubscriptionCard.tsx
│ │ │ │ │ │ ├── ExpiredSubscriptionView.tsx
│ │ │ │ │ │ ├── LoadingState.tsx
│ │ │ │ │ │ ├── MonthlySubscriptionTiers.tsx
│ │ │ │ │ │ ├── NewSubscriptionView.tsx
│ │ │ │ │ │ ├── PricingTiers.tsx
│ │ │ │ │ │ └── ResourceCounter.tsx
│ │ │ │ │ └── page.tsx
│ │ │ │ ├── settings
│ │ │ │ │ ├── components
│ │ │ │ │ │ └── NavigationItem.tsx
│ │ │ │ │ └── page.tsx
│ │ │ │ └── team
│ │ │ │ │ ├── components
│ │ │ │ │ ├── DeleteTeamMember.tsx
│ │ │ │ │ └── InviteCode.tsx
│ │ │ │ │ └── page.tsx
│ │ │ ├── layout.tsx
│ │ │ └── nfts
│ │ │ │ ├── [nftId]
│ │ │ │ ├── components
│ │ │ │ │ └── ContractDetails.tsx
│ │ │ │ └── page.tsx
│ │ │ │ ├── components
│ │ │ │ ├── CreateNFTCollectionModal.tsx
│ │ │ │ ├── MintNftSort.tsx
│ │ │ │ └── NFTCollectionCard.tsx
│ │ │ │ ├── create
│ │ │ │ ├── components
│ │ │ │ │ ├── AddMedia.tsx
│ │ │ │ │ ├── AddMediaTabItem.tsx
│ │ │ │ │ ├── CollectionDetails.tsx
│ │ │ │ │ ├── CreateNFTForm.tsx
│ │ │ │ │ ├── CreateNFTModal.tsx
│ │ │ │ │ ├── PublishingNFTModal.tsx
│ │ │ │ │ ├── SelectedMediaItem.tsx
│ │ │ │ │ ├── ShareCollection.tsx
│ │ │ │ │ ├── ShareVideoNFT.tsx
│ │ │ │ │ └── TransactionHash.tsx
│ │ │ │ └── page.tsx
│ │ │ │ └── page.tsx
│ │ │ └── layout.tsx
│ ├── auth.ts
│ ├── components.json
│ ├── components
│ │ ├── Layout
│ │ │ ├── EmbedLayout.tsx
│ │ │ ├── Footer.tsx
│ │ │ ├── HomePageNavbar.tsx
│ │ │ ├── Navbar.tsx
│ │ │ ├── NavbarStudio.tsx
│ │ │ ├── NavbarTop.tsx
│ │ │ ├── Navigation.tsx
│ │ │ ├── NavigationItem.tsx
│ │ │ └── SwitchOrganization.tsx
│ │ ├── Sidebar
│ │ │ ├── Sidebar.tsx
│ │ │ └── SidebarMenu.tsx
│ │ ├── misc
│ │ │ ├── ConnectWalletButton.tsx
│ │ │ ├── CopyString.tsx
│ │ │ ├── CopyText.tsx
│ │ │ ├── MarkdownDisplay.tsx
│ │ │ ├── SearchBar
│ │ │ │ ├── SearchResults.tsx
│ │ │ │ ├── index.tsx
│ │ │ │ └── useSearch.ts
│ │ │ ├── Support
│ │ │ │ ├── SupportForm.tsx
│ │ │ │ └── index.tsx
│ │ │ ├── Table
│ │ │ │ └── TableSkeleton.tsx
│ │ │ ├── TableSort.tsx
│ │ │ ├── UserDropdown.tsx
│ │ │ ├── UserProfile.tsx
│ │ │ ├── VideoCard
│ │ │ │ ├── LivestreamCard.tsx
│ │ │ │ ├── VideoCardProcessing.tsx
│ │ │ │ ├── VideoCardSkeleton.tsx
│ │ │ │ ├── VideoCardWithMenu.tsx
│ │ │ │ ├── index.tsx
│ │ │ │ └── thumbnail.tsx
│ │ │ ├── VideoDownloadClient.tsx
│ │ │ ├── Videos.tsx
│ │ │ ├── form
│ │ │ │ ├── colorPicker.tsx
│ │ │ │ ├── dataConfigElement.tsx
│ │ │ │ ├── datePicker.tsx
│ │ │ │ ├── imageUpload.tsx
│ │ │ │ ├── timePicker.tsx
│ │ │ │ └── videoUpload.tsx
│ │ │ └── interact
│ │ │ │ ├── Alert.tsx
│ │ │ │ ├── EmbedButton.tsx
│ │ │ │ ├── InfoHoverCard.tsx
│ │ │ │ └── ShareButton.tsx
│ │ ├── plugins
│ │ │ ├── NFT
│ │ │ │ └── index.tsx
│ │ │ ├── NFTMintComponent.tsx
│ │ │ └── SignUp
│ │ │ │ └── index.tsx
│ │ ├── sessions
│ │ │ ├── CollectVideButton.tsx
│ │ │ ├── DropdownMenuWithActionButtons.tsx
│ │ │ ├── InfoBoxDescription.tsx
│ │ │ ├── LiveIndicator.tsx
│ │ │ ├── SessionInfoBox.tsx
│ │ │ ├── SessionList.tsx
│ │ │ └── TranscriptionModal.tsx
│ │ ├── speakers
│ │ │ └── speakerIcon.tsx
│ │ └── ui
│ │ │ ├── Player.tsx
│ │ │ ├── TitleTextItem.tsx
│ │ │ ├── accordion.tsx
│ │ │ ├── alert-dialog.tsx
│ │ │ ├── alert.tsx
│ │ │ ├── aspect-ratio.tsx
│ │ │ ├── avatar.tsx
│ │ │ ├── badge.tsx
│ │ │ ├── button.tsx
│ │ │ ├── calendar.tsx
│ │ │ ├── card.tsx
│ │ │ ├── checkbox.tsx
│ │ │ ├── combo-box.tsx
│ │ │ ├── command.tsx
│ │ │ ├── context-menu.tsx
│ │ │ ├── crezenda.tsx
│ │ │ ├── dialog.tsx
│ │ │ ├── drawer.tsx
│ │ │ ├── dropdown-menu.tsx
│ │ │ ├── feature-button.tsx
│ │ │ ├── form.tsx
│ │ │ ├── hover-card.tsx
│ │ │ ├── input.tsx
│ │ │ ├── label.tsx
│ │ │ ├── navigation-menu.tsx
│ │ │ ├── popover.tsx
│ │ │ ├── progress.tsx
│ │ │ ├── resizable.tsx
│ │ │ ├── select.tsx
│ │ │ ├── separator.tsx
│ │ │ ├── skeleton.tsx
│ │ │ ├── sonner.tsx
│ │ │ ├── switch.tsx
│ │ │ ├── table.tsx
│ │ │ ├── tabs.tsx
│ │ │ ├── text-placeholder.tsx
│ │ │ ├── textarea.tsx
│ │ │ └── tooltip.tsx
│ ├── lib
│ │ ├── actions
│ │ │ ├── auth.ts
│ │ │ ├── chat.ts
│ │ │ ├── events.ts
│ │ │ ├── imageUpload.ts
│ │ │ ├── livepeer.ts
│ │ │ ├── marker.ts
│ │ │ ├── nftCollection.ts
│ │ │ ├── organizations.ts
│ │ │ ├── playlists.ts
│ │ │ ├── sessions.ts
│ │ │ ├── stages.ts
│ │ │ ├── state.ts
│ │ │ ├── support.ts
│ │ │ └── users.ts
│ │ ├── context
│ │ │ ├── GeneralContext.tsx
│ │ │ ├── LoadingContext.tsx
│ │ │ ├── MobileContext.tsx
│ │ │ ├── OrganizationContext.tsx
│ │ │ ├── SiweContext.tsx
│ │ │ ├── TopNavbarContext.tsx
│ │ │ └── UserContext.tsx
│ │ ├── contract
│ │ │ └── index.ts
│ │ ├── data.ts
│ │ ├── hooks
│ │ │ ├── useClickOutside.ts
│ │ │ ├── useCreateClip.ts
│ │ │ ├── useDebounce.ts
│ │ │ ├── useGenerateThumbnail.ts
│ │ │ ├── useLocalStorage.tsx
│ │ │ ├── useMediaQuery.ts
│ │ │ ├── useOrganization.ts
│ │ │ ├── usePayment.ts
│ │ │ ├── usePlayer.ts
│ │ │ └── useSearchParams.ts
│ │ ├── schema.ts
│ │ ├── services
│ │ │ ├── authService.ts
│ │ │ ├── chatService.ts
│ │ │ ├── eventService.tsx
│ │ │ ├── fetch-client.ts
│ │ │ ├── googleDriveService.ts
│ │ │ ├── googleSheetService.ts
│ │ │ ├── imageUploadService.ts
│ │ │ ├── markerSevice.ts
│ │ │ ├── nftCollectionService.ts
│ │ │ ├── organizationService.tsx
│ │ │ ├── playlistService.ts
│ │ │ ├── sessionService.ts
│ │ │ ├── stageService.tsx
│ │ │ ├── stateService.ts
│ │ │ ├── stripeService.ts
│ │ │ ├── supportService.tsx
│ │ │ ├── userService.ts
│ │ │ └── videoUploadService.ts
│ │ ├── svg
│ │ │ ├── DefaultThumbnail.tsx
│ │ │ ├── EmptyFolder.tsx
│ │ │ ├── StreamethLogo.tsx
│ │ │ ├── StreamethLogoGray.tsx
│ │ │ ├── StreamethLogoWhite.tsx
│ │ │ ├── StreamethStudio.tsx
│ │ │ ├── UploadComplete.tsx
│ │ │ └── YoutubeIcon.tsx
│ │ ├── types.ts
│ │ ├── uploadVideo.ts
│ │ └── utils
│ │ │ ├── generateGoogleCalendar.ts
│ │ │ ├── googleAuth.ts
│ │ │ ├── metadata.ts
│ │ │ ├── resizeImage.ts
│ │ │ ├── subscription.ts
│ │ │ ├── time.tsx
│ │ │ ├── timezone.tsx
│ │ │ ├── twitterAuth.ts
│ │ │ └── utils.ts
│ ├── middleware.ts
│ ├── next.config.js
│ ├── not-found.tsx
│ ├── package.json
│ ├── postcss.config.js
│ ├── public
│ │ ├── Base-house.jpg
│ │ ├── Folder.png
│ │ ├── UserEmptyIcon.png
│ │ ├── backgrounds
│ │ │ ├── livestreamBg.png
│ │ │ ├── nftBg.jpg
│ │ │ └── nftBg.svg
│ │ ├── cover.png
│ │ ├── farcaster-transparent-white.png
│ │ ├── favicon.ico
│ │ ├── images
│ │ │ ├── NFTSuccess.png
│ │ │ ├── empty-box.png
│ │ │ ├── multipleNFT.png
│ │ │ ├── singleNFT.png
│ │ │ ├── videoPlaceholder.png
│ │ │ ├── youtube-tutorial.png
│ │ │ └── youtube_social_icon_red.png
│ │ ├── legal
│ │ │ ├── privacy.md
│ │ │ └── terms.md
│ │ ├── lens-green.png
│ │ ├── lib_wsURRdlMczHRgWBe
│ │ │ ├── 002clfqcrzy725fn.png
│ │ │ ├── 0tcvaer9b0dwp6gg.png
│ │ │ ├── 0ud783pd9etcohgd.png
│ │ │ ├── 26sfvru7y3df4lpu.png
│ │ │ ├── 2hesu5v0s2y9a7h5.png
│ │ │ ├── 329bez6yoeajgob0.png
│ │ │ ├── 4179bdhw7ckua4hb.png
│ │ │ ├── 46j3ppjs675nuhka.png
│ │ │ ├── 4k1ngp6h5n5l1u5l.jpg
│ │ │ ├── 4xmsxtzufoulc5af.png
│ │ │ ├── 550nopa7lubbte09.jpg
│ │ │ ├── 7hf72jf59l4e0mh9.png
│ │ │ ├── 7uegl4bozj0pbp5x.png
│ │ │ ├── 83fie8t0fjm8is86.png
│ │ │ ├── 90fnji3oxuxld4l5.png
│ │ │ ├── 9upukn8zuxn9rewm.png
│ │ │ ├── a17fxhoo1yzgkowd.jpg
│ │ │ ├── a69ad1z1iuc16c6j.png
│ │ │ ├── a9c854il4fopdsp0.png
│ │ │ ├── byv0vxn9ncxss1tj.png
│ │ │ ├── ccz1ztuk5ny0qloe.png
│ │ │ ├── cebcodsnth35kyyg.png
│ │ │ ├── d8emwluemsveqjxh.png
│ │ │ ├── defjaol1kkd8qw0p.png
│ │ │ ├── ee54i24u1no8ee51.png
│ │ │ ├── fxwq49em5sfw8gbu.png
│ │ │ ├── gv2jrclmcqnmpc3y.png
│ │ │ ├── hf1359h01lj90puc.jpg
│ │ │ ├── hnw3w49fub3pfo8b.png
│ │ │ ├── igfide8bqmwmyoha.png
│ │ │ ├── kfql38xo5bq7u5f3.png
│ │ │ ├── ki5ehzpo5d6umljx.png
│ │ │ ├── lepklk4j8usqgbnu.png
│ │ │ ├── lm5yh6zo4xfktsd4.png
│ │ │ ├── n7lvh3tpq34a2b6c.png
│ │ │ ├── oda6gbylddkx0sx4.jpg
│ │ │ ├── pgm18l3l2za5a1nl.jpg
│ │ │ ├── ppz6updxo1mb5m1s.png
│ │ │ ├── r1t65rm26qvise2c.png
│ │ │ ├── r2kkux1myqgch11c.jpg
│ │ │ ├── rtmq0dmhuzf7k5fn.png
│ │ │ ├── sa6qfea8vo2smjyp.png
│ │ │ ├── sm5tz6r6vwjsy3fn.png
│ │ │ ├── sstwil0urefp64f5.png
│ │ │ ├── t8254hkl9zfudy6e.png
│ │ │ ├── u4bku1qbk3x61o2z.png
│ │ │ ├── wrgo0xbslsvxx7ho.jpg
│ │ │ ├── xhjg9gn5lpi674e6.png
│ │ │ └── zon4mxn9vxoqd8r2.jpg
│ │ ├── livepeer-logo.png
│ │ ├── livepeer.svg
│ │ ├── livepeer_black.png
│ │ ├── login-background.png
│ │ ├── logo.png
│ │ ├── logo_dark.png
│ │ ├── streameth_banner.png
│ │ ├── streameth_twitter_banner.jpeg
│ │ ├── studio_logo.png
│ │ ├── success.png
│ │ └── twitter-white.png
│ ├── tailwind.config.js
│ └── tsconfig.json
├── contracts
│ ├── .gitignore
│ ├── README.md
│ ├── contracts
│ │ ├── VIdeoNFTFactory.sol
│ │ └── VideoNFT.sol
│ ├── hardhat.config.ts
│ ├── package.json
│ ├── scripts
│ │ └── deploy.ts
│ └── tsconfig.json
├── reel-creator
│ ├── .dockerignore
│ ├── .eslintrc.json
│ ├── .gitignore
│ ├── .npmrc
│ ├── Dockerfile
│ ├── Dockerfile.dev
│ ├── README.md
│ ├── app
│ │ ├── api
│ │ │ └── lambda
│ │ │ │ ├── progress
│ │ │ │ └── route.ts
│ │ │ │ └── render
│ │ │ │ └── route.ts
│ │ ├── data
│ │ │ └── transcription.js
│ │ ├── favicon.ico
│ │ ├── hooks
│ │ │ └── useCurrentPlayerFrame.tsx
│ │ ├── layout.tsx
│ │ └── page.tsx
│ ├── components.json
│ ├── components
│ │ ├── AlignEnd.tsx
│ │ ├── Button.tsx
│ │ ├── Container.tsx
│ │ ├── DownloadButton.tsx
│ │ ├── EditorSidebar.tsx
│ │ ├── Error.tsx
│ │ ├── Input.tsx
│ │ ├── Labels.tsx
│ │ ├── Modal.tsx
│ │ ├── Player.tsx
│ │ ├── PlayerControl.tsx
│ │ ├── ProgressBar.tsx
│ │ ├── RenderControls.tsx
│ │ ├── Spacing.tsx
│ │ ├── Spinner.tsx
│ │ ├── Tips.tsx
│ │ ├── TranscriptSelector.tsx
│ │ ├── VideoResizer.tsx
│ │ ├── sidebar-components
│ │ │ ├── AnimationSettings.tsx
│ │ │ ├── BrandingSettings.tsx
│ │ │ ├── CaptionSettings.tsx
│ │ │ └── LayoutSettings.tsx
│ │ ├── timeline
│ │ │ ├── TimelineEvents.tsx
│ │ │ ├── TimelineMarkers.tsx
│ │ │ ├── TimelinePlayhead.tsx
│ │ │ ├── TimelineToolbar.tsx
│ │ │ └── index.tsx
│ │ └── ui
│ │ │ ├── accordion.tsx
│ │ │ ├── button.tsx
│ │ │ ├── collapsible.tsx
│ │ │ ├── dialog.tsx
│ │ │ ├── input.tsx
│ │ │ ├── label.tsx
│ │ │ ├── radio-group.tsx
│ │ │ ├── select.tsx
│ │ │ ├── slider.tsx
│ │ │ └── switch.tsx
│ ├── config.mjs
│ ├── context
│ │ ├── EditorContext.tsx
│ │ └── TimelineContext.tsx
│ ├── deploy.mjs
│ ├── helpers
│ │ ├── api-response.ts
│ │ └── use-rendering.ts
│ ├── lambda
│ │ └── api.ts
│ ├── lib
│ │ └── utils.ts
│ ├── next.config.js
│ ├── package-lock.json
│ ├── package.json
│ ├── postcss.config.js
│ ├── remotion.config.ts
│ ├── remotion
│ │ ├── Editor
│ │ │ ├── Captions.tsx
│ │ │ ├── Editor.tsx
│ │ │ ├── constants.tsx
│ │ │ └── index.tsx
│ │ ├── Root.tsx
│ │ ├── index.ts
│ │ └── webpack-override.mjs
│ ├── styles
│ │ └── global.css
│ ├── tailwind.config.ts
│ ├── tsconfig.json
│ ├── types
│ │ ├── constants.ts
│ │ ├── fluent-ffmpeg.d.ts
│ │ └── schema.ts
│ ├── vercel.json
│ └── yarn.lock
├── server
│ ├── .gitignore
│ ├── .mocharc.json
│ ├── .npmrc
│ ├── .prettierignore
│ ├── .prettierrc.json
│ ├── README.md
│ ├── package.json
│ ├── src
│ │ ├── Dockerfile
│ │ ├── Dockerfile.dev
│ │ ├── app.ts
│ │ ├── config
│ │ │ └── index.ts
│ │ ├── controllers
│ │ │ ├── auth.controller.ts
│ │ │ ├── chat.controller.ts
│ │ │ ├── event.controller.ts
│ │ │ ├── index.controller.ts
│ │ │ ├── marker.controller.ts
│ │ │ ├── nft.controller.ts
│ │ │ ├── organization.controller.ts
│ │ │ ├── playlist.controller.ts
│ │ │ ├── schedule-import.controller.ts
│ │ │ ├── session.controller.ts
│ │ │ ├── speaker.controller.ts
│ │ │ ├── stage.controller.ts
│ │ │ ├── state.controller.ts
│ │ │ ├── stream.controller.ts
│ │ │ ├── stripe.controller.ts
│ │ │ ├── support.controller.ts
│ │ │ └── user.controller.ts
│ │ ├── databases
│ │ │ ├── index.ts
│ │ │ └── storage
│ │ │ │ ├── db.ts
│ │ │ │ ├── fs.ts
│ │ │ │ └── index.ts
│ │ ├── dtos
│ │ │ ├── auth
│ │ │ │ └── auth.dto.ts
│ │ │ ├── chat
│ │ │ │ └── create-chat.dto.ts
│ │ │ ├── event
│ │ │ │ ├── create-event.dto.ts
│ │ │ │ └── update-event.dto.ts
│ │ │ ├── marker
│ │ │ │ ├── create-marker.dto.ts
│ │ │ │ └── update-marker.dto.ts
│ │ │ ├── nft
│ │ │ │ ├── create-colletion.dto.ts
│ │ │ │ └── update-collection.dto.ts
│ │ │ ├── organization
│ │ │ │ ├── create-organization.dto.ts
│ │ │ │ ├── orgid.dto.ts
│ │ │ │ └── update-organization.dto.ts
│ │ │ ├── playlist
│ │ │ │ ├── create-playlist.dto.ts
│ │ │ │ └── update-playlist.dto.ts
│ │ │ ├── schedule-importer
│ │ │ │ └── create-import.dto.ts
│ │ │ ├── session
│ │ │ │ ├── create-session.dto.ts
│ │ │ │ ├── update-session.dto.ts
│ │ │ │ └── upload-session.dto.ts
│ │ │ ├── speaker
│ │ │ │ └── create-speaker.dto.ts
│ │ │ ├── stage
│ │ │ │ ├── create-hls.dto.ts
│ │ │ │ ├── create-stage.dto.ts
│ │ │ │ ├── livestream.dto.ts
│ │ │ │ └── update-stage.dto.ts
│ │ │ ├── state
│ │ │ │ ├── create-state.dto.ts
│ │ │ │ └── update-state.dto.ts
│ │ │ ├── stream
│ │ │ │ ├── create-clip.dto.ts
│ │ │ │ ├── create-multistream.dto.ts
│ │ │ │ └── delete-multistream.dto.ts
│ │ │ ├── stripe
│ │ │ │ ├── create-checkout-session.dto.ts
│ │ │ │ └── create-portal-session.dto.ts
│ │ │ ├── support
│ │ │ │ └── create-ticket.dto.ts
│ │ │ └── user
│ │ │ │ └── user.dto.ts
│ │ ├── exceptions
│ │ │ └── HttpException.ts
│ │ ├── http
│ │ │ ├── auth.http
│ │ │ └── users.http
│ │ ├── interfaces
│ │ │ ├── auth.interface.ts
│ │ │ ├── chat.interface.ts
│ │ │ ├── clip.editor.interface.ts
│ │ │ ├── clip.interface.ts
│ │ │ ├── event.interface.ts
│ │ │ ├── livepeer.interface.ts
│ │ │ ├── livepeer.webhook.interface.ts
│ │ │ ├── marker.interface.ts
│ │ │ ├── nft.collection.interface.ts
│ │ │ ├── organization.interface.ts
│ │ │ ├── playlist.interface.ts
│ │ │ ├── remotion.webhook.interface.ts
│ │ │ ├── schedule-importer.interface.ts
│ │ │ ├── session.interface.ts
│ │ │ ├── speaker.interface.ts
│ │ │ ├── stage.interface.ts
│ │ │ ├── state.interface.ts
│ │ │ ├── storage.interface.ts
│ │ │ ├── stream.interface.ts
│ │ │ ├── support.interface.ts
│ │ │ ├── upload.session.interface.ts
│ │ │ └── user.interface.ts
│ │ ├── middlewares
│ │ │ ├── auth.middleware.ts
│ │ │ ├── error.middleware.ts
│ │ │ └── multer.middleware.ts
│ │ ├── models
│ │ │ ├── chat.model.ts
│ │ │ ├── clip.editor.model.ts
│ │ │ ├── event.model.ts
│ │ │ ├── markers.model.ts
│ │ │ ├── nft.collection.model.ts
│ │ │ ├── organization.model.ts
│ │ │ ├── playlist.model.ts
│ │ │ ├── schedule.model.ts
│ │ │ ├── session.model.ts
│ │ │ ├── speaker.model.ts
│ │ │ ├── stage.model.ts
│ │ │ ├── state.model.ts
│ │ │ ├── support.model.ts
│ │ │ └── user.model.ts
│ │ ├── nodemon.json
│ │ ├── routes
│ │ │ └── routes.ts
│ │ ├── server.ts
│ │ ├── services
│ │ │ ├── auth.service.ts
│ │ │ ├── chat.service.ts
│ │ │ ├── clipEditor.service.ts
│ │ │ ├── event.service.ts
│ │ │ ├── marker.service.ts
│ │ │ ├── nft.service.ts
│ │ │ ├── organization.service.ts
│ │ │ ├── playlist.service.ts
│ │ │ ├── schedule-import.service.ts
│ │ │ ├── session.service.ts
│ │ │ ├── speaker.service.ts
│ │ │ ├── stage.service.ts
│ │ │ ├── state.service.ts
│ │ │ ├── stripe.service.ts
│ │ │ ├── support.service.ts
│ │ │ └── user.service.ts
│ │ ├── swagger
│ │ │ └── swagger.json
│ │ └── utils
│ │ │ ├── ai.chat.ts
│ │ │ ├── ai.transcribes.ts
│ │ │ ├── api.response.ts
│ │ │ ├── google-sheet.ts
│ │ │ ├── livepeer.ts
│ │ │ ├── logger.ts
│ │ │ ├── mail.service.ts
│ │ │ ├── nft.storage.ts
│ │ │ ├── oauth.ts
│ │ │ ├── pinecone.ts
│ │ │ ├── pulse.cron.ts
│ │ │ ├── redis.ts
│ │ │ ├── s3.ts
│ │ │ ├── util.ts
│ │ │ ├── validateEnv.ts
│ │ │ ├── validateWebhook.ts
│ │ │ └── youtube.ts
│ ├── templates
│ │ ├── invite.html
│ │ └── login.html
│ ├── test
│ │ ├── auth.ts
│ │ ├── event.spec.ts
│ │ ├── global.ts
│ │ ├── organization.spec.ts
│ │ ├── session.spec.ts
│ │ ├── speaker.spec.ts
│ │ ├── stage.spec.ts
│ │ └── utils
│ │ │ └── generateRandomString.ts
│ ├── tsconfig.json
│ ├── tsconfig.prod.json
│ ├── tsoa.json
│ └── workers
│ │ ├── clips
│ │ ├── Dockerfile
│ │ ├── Dockerfile.dev
│ │ ├── index.ts
│ │ └── nodemon.json
│ │ ├── session-transcriptions
│ │ ├── Dockerfile
│ │ ├── Dockerfile.dev
│ │ ├── index.ts
│ │ └── nodemon.json
│ │ ├── session-translations
│ │ ├── Dockerfile
│ │ ├── Dockerfile.dev
│ │ ├── index.ts
│ │ └── nodemon.json
│ │ ├── stage-transcriptions
│ │ ├── Dockerfile
│ │ ├── Dockerfile.dev
│ │ ├── index.ts
│ │ └── nodemon.json
│ │ └── video-importer
│ │ ├── Dockerfile
│ │ ├── Dockerfile.dev
│ │ ├── index.ts
│ │ └── nodemon.json
└── video-uploader
│ ├── .gitignore
│ ├── Dockerfile.dev
│ ├── docker-entrypoint.sh
│ ├── package.json
│ ├── src
│ ├── config
│ │ └── index.ts
│ ├── stage-transcriptions-consumer.ts
│ ├── utils
│ │ ├── errors.ts
│ │ ├── ffmpeg.ts
│ │ ├── helper.ts
│ │ ├── logger.ts
│ │ ├── mq.ts
│ │ ├── redis.ts
│ │ ├── twitter.ts
│ │ └── youtube.ts
│ └── video-consumer.ts
│ ├── tsconfig.json
│ └── tsconfig.prod.json
└── yarn.lock
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: "[BUG]"
5 | labels: bug
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Desktop (please complete the following information):**
27 | - Browser [e.g. chrome, safari] *Required*
28 | - OS: [e.g. iOS] *Optional*
29 | - Version [e.g. 22] *Optional*
30 |
31 | **Smartphone (please complete the following information):**
32 | - Browser [e.g. stock browser, safari] *Required*
33 | - Device: [e.g. iPhone6] *Optional*
34 | - OS: [e.g. iOS8.1] *Optional*
35 | - Version [e.g. 22] *Optional*
36 |
37 | **Additional context**
38 | Add any other context about the problem here.
39 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: As a [User, Event, Developer, etc.],
5 | labels: enhancement
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Additional context**
17 | Add any other context or screenshots about the feature request here.
18 |
19 | **Screenshots**
20 | Do you have an example from another platform, or prototype you made by yourself?
21 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/task.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Task
3 | about: Define a new task related to a User Story
4 | title: [TASK]
5 | labels: task
6 | assignees: ''
7 |
8 | ---
9 |
10 | ## Description
11 | [Provide a clear and concise description of the task]
12 |
13 | ## Related User Story
14 | Include the related User Story #< User Story >
15 |
16 | ## Objectives
17 | - [ ] [Objective 1]
18 | - [ ] [Objective 2]
19 | - [ ] [Objective 3]
20 |
21 | ## Acceptance Criteria
22 | - [ ] [Criterion 1]
23 | - [ ] [Criterion 2]
24 | - [ ] [Criterion 3]
25 |
26 | ## Additional Information
27 | [Any extra details, context, or resources that might be helpful]
28 |
29 |
30 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/usabilty_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Usability issue
3 | about: Report a usability problem to help us improve the user experience
4 | title: "[USABILITY]"
5 | labels: usability
6 | assignees: ''
7 | ---
8 |
9 | **Describe the usability issue**
10 | A clear and concise description of what the usability problem is.
11 |
12 | **To Reproduce**
13 | Steps to encounter the usability issue:
14 | 1. Go to '...'
15 | 2. Click on '....'
16 | 3. Scroll down to '....'
17 | 4. Observe difficulty in '....'
18 |
19 | **Expected user experience**
20 | A clear and concise description of what you expected the user experience to be.
21 |
22 | **Actual user experience**
23 | A clear and concise description of the actual, problematic user experience.
24 |
25 | **Screenshots or screen recordings**
26 | If applicable, add screenshots or screen recordings to help explain the usability issue.
27 |
28 | **Accessibility considerations**
29 | Does this usability issue impact accessibility? If so, please provide details.
30 |
31 | **Additional context**
32 | Add any other context about the usability issue here.
33 |
--------------------------------------------------------------------------------
/.github/workflows/generate.yml:
--------------------------------------------------------------------------------
1 | name: Generate assets
2 |
3 | on:
4 | workflow_dispatch:
5 |
6 | concurrency:
7 | group: ${{ github.workflow }}-${{ github.ref }}
8 | cancel-in-progress: true
9 |
10 | jobs:
11 | build:
12 | runs-on: ubuntu-latest
13 | timeout-minutes: 15
14 | steps:
15 | - uses: actions/checkout@v4
16 | - uses: actions/setup-node@v4
17 | with:
18 | node-version: '20'
19 | cache: 'yarn'
20 | - run: yarn install --frozen-lockfile
21 | - run: yarn run:generate
22 |
--------------------------------------------------------------------------------
/.github/workflows/prettier.yml:
--------------------------------------------------------------------------------
1 | name: Prettier
2 |
3 | # on:
4 | # pull_request:
5 | # branches: [main, develop]
6 | # push:
7 | # branches: [main, develop]
8 |
9 | # Prevent multiple runs of the same workflow on the same ref
10 | concurrency:
11 | group: ${{ github.workflow }}-${{ github.ref }}
12 | cancel-in-progress: true
13 |
14 | jobs:
15 | format:
16 | runs-on: ubuntu-latest
17 | timeout-minutes: 10
18 | steps:
19 | - name: Check out Git repository
20 | uses: actions/checkout@v4
21 | - name: Set up Node.js
22 | uses: actions/setup-node@v3
23 | with:
24 | node-version: '20'
25 | cache: 'yarn'
26 | - name: Install dependencies
27 | run: yarn install --frozen-lockfile
28 | - name: Format code with Prettier
29 | run: yarn prettier:fix
30 | - name: Commit changes
31 | uses: stefanzweifel/git-auto-commit-action@v5
32 | with:
33 | commit_message: "style: format code with prettier"
34 | branch: ${{ github.head_ref }}
35 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | undefined/*
4 | undefined
5 | # dependencies
6 | node_modules
7 | /.pnp
8 | .pnp.js
9 |
10 | .yarn/*
11 | .yarn
12 |
13 | # testing
14 | /coverage
15 | /playwright
16 |
17 | # next.js
18 | /.next/
19 | /out/
20 |
21 | # production
22 | /build
23 |
24 | # misc
25 | .DS_Store
26 | *.pem
27 |
28 | # debug
29 | npm-debug.log*
30 | yarn-debug.log*
31 | yarn-error.log*
32 |
33 | # local env files
34 | .env
35 | .env*.local
36 | test.env
37 |
38 | # vercel
39 | .vercel
40 |
41 | # typescript
42 | *.tsbuildinfo
43 | next-env.d.ts
44 | error.log
45 | test.ts
46 |
47 | # Google Cloud secrets
48 | google_client_secret.json
49 | google_sa_secret.json
50 |
51 | # Generated assets
52 | /assets
53 |
54 | # Sentry Config File
55 | .sentryclirc
56 |
57 | .yarn/*
58 | .yarn
59 | .yarn/
60 |
61 |
62 | *.log
63 | *.mp4
64 |
65 | # To avoid pushing the local db connection string
66 | packages/server/src/databases/index.ts
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 |
--------------------------------------------------------------------------------
/.lintstagedrc.js:
--------------------------------------------------------------------------------
1 | const path = require("path");
2 |
3 | const buildNextEslintCommand = (filenames) =>
4 | `yarn next:lint --fix --file ${filenames
5 | .map((f) => path.relative(path.join("packages", "app"), f))
6 | .join(" --file ")}`;
7 |
8 | // const checkTypesNextCommand = () => "yarn next:check-types";
9 | const checkPrettierCommand = () => "yarn prettier:check";
10 |
11 | module.exports = {
12 | "packages/app/**/*.{ts,tsx}": [
13 | buildNextEslintCommand,
14 | checkPrettierCommand,
15 | ],
16 | };
17 |
--------------------------------------------------------------------------------
/.vercelignore:
--------------------------------------------------------------------------------
1 | /packages/tools/av-tools
--------------------------------------------------------------------------------
/.yarnrc.yml:
--------------------------------------------------------------------------------
1 | nodeLinker: node-modules
2 |
--------------------------------------------------------------------------------
/docker-compose/build-local.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | docker build -f packages/server/workers/session-transcriptions/Dockerfile . -t session-transcriptions:1
4 | docker build -f packages/server/workers/stage-transcriptions/Dockerfile . -t stage-transcriptions:1
5 | docker build -f packages/server/src/Dockerfile . -t server:1
--------------------------------------------------------------------------------
/docker-compose/create-secrets.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Function to extract value from _FILE suffix variables
4 | extract_secret_value() {
5 | local var_name=$1
6 | local var_value=$2
7 |
8 | # Remove _FILE suffix for secret name
9 | secret_name=$(echo "$var_name" | sed 's/_FILE$//')
10 | secret_name=$(echo "$secret_name" | tr '[:upper:]' '[:lower:]')
11 |
12 | # Create Docker secret
13 | echo "Creating secret: $secret_name"
14 | echo "$var_value" | docker secret create "$secret_name" -
15 | }
16 |
17 | # Read .env file line by line
18 | while IFS='=' read -r key value; do
19 | # Skip empty lines and comments
20 | if [[ -z "$key" ]] || [[ "$key" =~ ^# ]]; then
21 | continue
22 | fi
23 |
24 | # Clean up key and value
25 | key=$(echo "$key" | xargs)
26 | value=$(echo "$value" | xargs)
27 |
28 | # Process only *_FILE variables that have values
29 | if [[ "$key" == *"_FILE"* ]] && [[ ! -z "$value" ]]; then
30 | # Remove any surrounding quotes from value
31 | value=$(echo "$value" | sed -e 's/^"//' -e 's/"$//' -e "s/^'//" -e "s/'$//")
32 | extract_secret_value "$key" "$value"
33 | fi
34 | done < .env
35 |
36 | echo "Docker secrets creation completed!"
--------------------------------------------------------------------------------
/docker-compose/readme.md:
--------------------------------------------------------------------------------
1 | ## To run dev
2 | ```
3 | docker compose --env-file .env -f docker-compose-dev.yml up --build
4 | ```
5 |
6 | ## Docker stack deploy
7 | docker stack deploy -c docker-compose/docker-compose.yml streameth-build --detach=false --build
--------------------------------------------------------------------------------
/funding.json:
--------------------------------------------------------------------------------
1 | {
2 | "opRetro": {
3 | "projectId": "0x848488156d9bfc5356583da57253193a258acebcdbc3ba2a10dc8ef81bd815fe"
4 | }
5 | }
--------------------------------------------------------------------------------
/packages/app/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals",
3 | "rules": {
4 | "react-hooks/exhaustive-deps": "off"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/packages/app/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 | /playwright
11 |
12 | # next.js
13 | /.next/
14 | /out/
15 |
16 | # production
17 | /build
18 |
19 | # misc
20 | .DS_Store
21 | *.pem
22 |
23 | # debug
24 | npm-debug.log*
25 | yarn-debug.log*
26 | yarn-error.log*
27 |
28 | # local env files
29 | .env*.local
30 | *.env
31 | .env.*
32 |
33 | # vercel
34 | .vercel
35 |
36 | # typescript
37 | *.tsbuildinfo
38 | next-env.d.ts
39 | error.log
40 | test.ts
41 |
42 | # Google Cloud secrets
43 | google_client_secret.json
44 | google_sa_secret.json
45 |
46 | # Generated assets
47 | /assets
48 |
49 | # Sentry Config File
50 | .sentryclirc
51 | /test-results/
52 | /playwright-report/
53 | /blob-report/
54 | /playwright/.cache/
55 |
--------------------------------------------------------------------------------
/packages/app/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma": "es5",
3 | "semi": true,
4 | "singleQuote": true,
5 | "printWidth": 80,
6 | "tabWidth": 2,
7 | "useTabs": false,
8 | "bracketSpacing": true,
9 | "bracketSameLine": false,
10 | "arrowParens": "always",
11 | "endOfLine": "lf",
12 | "quoteProps": "as-needed",
13 | "jsxSingleQuote": false,
14 | "proseWrap": "preserve",
15 | "htmlWhitespaceSensitivity": "css"
16 | }
17 |
--------------------------------------------------------------------------------
/packages/app/Dockerfile.dev:
--------------------------------------------------------------------------------
1 | # Use Node.js LTS (Long Term Support) as base image
2 | FROM node:18-alpine
3 |
4 | # Add Python and build dependencies
5 | RUN apk add --no-cache python3 python3-dev py3-pip make g++ gcc
6 |
7 | # Set working directory
8 | WORKDIR /app
9 |
10 | # Copy package files first
11 | COPY package.json yarn.lock ./
12 | COPY packages/*/package.json ./packages/*/
13 |
14 | # Install dependencies
15 | RUN yarn install --frozen-lockfile
16 |
17 | # Copy the rest of the source code
18 | COPY . .
19 |
20 | # Expose the default Next.js port
21 | EXPOSE 3000
22 |
23 | # Set environment variables
24 | ENV NODE_ENV=development
25 | ENV NEXT_TELEMETRY_DISABLED=1
26 |
27 | # Start the development server
28 | CMD ["yarn", "dev:app"]
29 |
--------------------------------------------------------------------------------
/packages/app/app/(legal)/data-request/page.tsx:
--------------------------------------------------------------------------------
1 | 'use server';
2 |
3 | import { Card, CardTitle, CardFooter } from '@/components/ui/card';
4 | import Image from 'next/image';
5 | import Footer from '@/components/Layout/Footer';
6 | import CreateRequest from './components/createRequest';
7 |
8 | const DataRequest = async () => {
9 |
10 | return (
11 |
12 |
13 |
14 |
15 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | );
28 | };
29 |
30 | export default DataRequest;
31 |
--------------------------------------------------------------------------------
/packages/app/app/[organization]/[event]/layout.tsx:
--------------------------------------------------------------------------------
1 | import { notFound } from "next/navigation";
2 | import { fetchEvent } from "@/lib/services/eventService";
3 |
4 | const Layout = async ({
5 | children,
6 | params: paramsPromise,
7 | }: {
8 | children: React.ReactNode;
9 | params: Promise<{
10 | organization: string;
11 | event: string;
12 | }>;
13 | }) => {
14 | const params = await paramsPromise;
15 | const event = await fetchEvent({
16 | eventSlug: params.event,
17 | });
18 |
19 | if (!event) {
20 | return notFound();
21 | }
22 | const style = {
23 | "--colors-accent": event.accentColor,
24 | backgroundColor: event.accentColor,
25 | } as React.CSSProperties;
26 |
27 | return (
28 |
29 |
30 | {children}
31 |
32 |
33 | );
34 | };
35 |
36 | export default Layout;
37 |
--------------------------------------------------------------------------------
/packages/app/app/[organization]/[event]/speakers/components/SpeakerModal.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | CredenzaContent,
3 | CredenzaDescription,
4 | CredenzaTitle,
5 | CredenzaHeader,
6 | CredenzaBody,
7 | } from '@/components/ui/crezenda';
8 |
9 | import Link from 'next/link';
10 | import { IExtendedSpeaker, IExtendedSession } from '@/lib/types';
11 |
12 | interface Params {
13 | speaker: IExtendedSpeaker;
14 | sessions?: IExtendedSession[];
15 | }
16 |
17 | const SpeakerModal = ({ speaker }: Params) => {
18 | return (
19 |
20 |
21 | {speaker?.name}
22 |
23 | {speaker?.twitter && (
24 |
25 | Follow on X
26 |
27 | )}
28 |
29 |
30 | {speaker?.bio}
31 |
32 | );
33 | };
34 |
35 | export default SpeakerModal;
36 |
--------------------------------------------------------------------------------
/packages/app/app/[organization]/[event]/speakers/page.tsx:
--------------------------------------------------------------------------------
1 | import SpeakerPageComponent from "./components/SpeakerComponent";
2 | import EmbedLayout from "@/components/Layout/EmbedLayout";
3 | import { fetchEvent } from "@/lib/services/eventService";
4 | import { notFound } from "next/navigation";
5 | import { EventPageProps } from "@/lib/types";
6 |
7 | const SpeakerPage = async ({ params: paramsPromise }: EventPageProps) => {
8 | const params = await paramsPromise;
9 |
10 | const event = await fetchEvent({
11 | eventId: params.event,
12 | });
13 |
14 | if (!event) return notFound();
15 |
16 | return (
17 |
18 |
19 |
20 | );
21 | };
22 |
23 | export default SpeakerPage;
24 |
--------------------------------------------------------------------------------
/packages/app/app/[organization]/[event]/stage/components/StagePreview.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 | import { Card, CardHeader } from '@/components/ui/card';
3 | import Thumbnail from '@/components/misc/VideoCard/thumbnail';
4 | import useSearchParams from '@/lib/hooks/useSearchParams';
5 | import { IExtendedStage } from '@/lib/types';
6 |
7 | export default function StagePreview({
8 | stage,
9 | eventCover,
10 | }: {
11 | stage: IExtendedStage;
12 | organization: string;
13 | event: string;
14 | eventCover: string | undefined;
15 | }) {
16 | const { handleTermChange } = useSearchParams();
17 |
18 | if (!stage?.streamSettings?.streamId) return null;
19 |
20 | return (
21 |
24 | handleTermChange([
25 | {
26 | key: 'livestream',
27 | value: stage._id as string,
28 | },
29 | ])
30 | }
31 | >
32 |
33 |
34 | {stage.name}
35 |
36 |
37 | );
38 | }
39 |
--------------------------------------------------------------------------------
/packages/app/app/[organization]/components/ShareVideoMenuItem.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 | import ShareButton from '@/components/misc/interact/ShareButton';
3 | import { buttonVariants } from '@/components/ui/button';
4 | import React, { useEffect, useState } from 'react';
5 |
6 | const ShareVideoMenuItem = ({ url }: { url: string }) => {
7 | const [currentUrl, setCurrentUrl] = useState('');
8 | useEffect(() => {
9 | // This code will only run on the client side
10 | if (typeof window === 'undefined') return;
11 | setCurrentUrl(window.location.origin);
12 | }, []);
13 | return (
14 | e.stopPropagation()}>
15 |
16 |
17 | );
18 | };
19 |
20 | export default ShareVideoMenuItem;
21 |
--------------------------------------------------------------------------------
/packages/app/app/[organization]/components/VideoDownload.tsx:
--------------------------------------------------------------------------------
1 | import { Livepeer } from 'livepeer';
2 | import { ArrowDownToLine, Download } from 'lucide-react';
3 | import { Button } from '@/components/ui/button';
4 |
5 | const VideoDownload = async ({ assetId }: { assetId: string }) => {
6 | const livepeer = new Livepeer({
7 | apiKey: process.env.LIVEPEER_API_KEY,
8 | });
9 |
10 | if (!assetId) return null;
11 |
12 | const asset = (await livepeer.asset.get(assetId)).asset;
13 |
14 | if (!asset) return null;
15 |
16 | return (
17 |
23 |
27 |
28 | );
29 | };
30 |
31 | export default VideoDownload;
32 |
--------------------------------------------------------------------------------
/packages/app/app/[organization]/components/ViewCounts.tsx:
--------------------------------------------------------------------------------
1 | import { fetchSessionMetrics } from '@/lib/services/sessionService';
2 |
3 | const ViewCounts = async ({
4 | playbackId,
5 | className,
6 | }: {
7 | playbackId: string;
8 | className?: string;
9 | }) => {
10 | const data = await fetchSessionMetrics({ playbackId });
11 |
12 | return (
13 |
14 | {data.viewCount} views
15 |
16 | );
17 | };
18 |
19 | export default ViewCounts;
20 |
--------------------------------------------------------------------------------
/packages/app/app/[organization]/livestream/components/ArchiveVideosSkeleton.tsx:
--------------------------------------------------------------------------------
1 | import { Card, CardDescription, CardHeader } from '@/components/ui/card';
2 |
3 | const ArchiveVideoSkeleton = () => {
4 | return (
5 |
6 | {[...Array(3)].map((_, index) => (
7 |
8 |
17 |
18 | ))}
19 |
20 | );
21 | };
22 |
23 | export default ArchiveVideoSkeleton;
24 |
--------------------------------------------------------------------------------
/packages/app/app/[organization]/livestream/components/PaginationSkeleteon.tsx:
--------------------------------------------------------------------------------
1 | const PaginationSkeleton = () => {
2 | return (
3 |
4 |
5 | {/* Left Arrow Skeleton */}
6 |
9 |
10 | {/* Page Number Skeleton */}
11 |
14 |
15 | {/* Right Arrow Skeleton */}
16 |
19 |
20 |
21 | );
22 | };
23 |
24 | export default PaginationSkeleton;
25 |
--------------------------------------------------------------------------------
/packages/app/app/[organization]/loading.tsx:
--------------------------------------------------------------------------------
1 |
2 | const Loading = () => {
3 | return Loading...
;
4 | };
5 |
6 | export default Loading;
7 |
--------------------------------------------------------------------------------
/packages/app/app/[organization]/videos/components/eventSelect.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 | import useSearchParams from '@/lib/hooks/useSearchParams';
3 | import Combobox from '@/components/ui/combo-box';
4 | import { IExtendedEvent } from '@/lib/types';
5 |
6 | const EventSelect = ({ events }: { events: IExtendedEvent[] }) => {
7 | const { handleTermChange, searchParams } = useSearchParams();
8 | const event = searchParams.get('event') || '';
9 |
10 | if (events.length === 0) {
11 | return null;
12 | }
13 |
14 | return (
15 | {
19 | handleTermChange([
20 | {
21 | key: 'event',
22 | value: value,
23 | },
24 | ]);
25 | }}
26 | items={[
27 | { label: 'None', value: '' },
28 | ...events.map((event) => ({
29 | label: event.name,
30 | value: event._id,
31 | })),
32 | ]}
33 | />
34 | );
35 | };
36 |
37 | export default EventSelect;
38 |
--------------------------------------------------------------------------------
/packages/app/app/api/auth/[...nextauth]/route.ts:
--------------------------------------------------------------------------------
1 | import { handlers } from '@/auth';
2 |
3 | export const { GET, POST } = handlers;
4 |
--------------------------------------------------------------------------------
/packages/app/app/api/google/request/route.ts:
--------------------------------------------------------------------------------
1 | import { NextRequest, NextResponse } from 'next/server';
2 |
3 | export async function GET(request: NextRequest) {
4 | const searchParams = request.nextUrl.searchParams;
5 | const state = searchParams.get('state');
6 | // const GOOGLE_OAUTH_SECRET = process.env.GOOGLE_OAUTH_SECRET || '';
7 | // const oAuthSecret: any = JSON.parse(GOOGLE_OAUTH_SECRET);
8 | // const clientId = oAuthSecret.web.client_id;
9 | // const redirectUri = oAuthSecret.web.redirect_uri;
10 |
11 | const googleAuthUrl = process.env.GOOGLE_OAUTH_URL;
12 |
13 | const authUrl = `${googleAuthUrl}&state=${state}`;
14 |
15 | try {
16 | return NextResponse.redirect(authUrl);
17 | } catch (err) {
18 | return NextResponse.json(
19 | { error: err },
20 | {
21 | status: 500,
22 | }
23 | );
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/packages/app/app/api/video-download/route.ts:
--------------------------------------------------------------------------------
1 | import { NextRequest, NextResponse } from 'next/server';
2 |
3 | export const GET = async (req: NextRequest): Promise => {
4 | try {
5 | const url = new URL(req.url);
6 |
7 | const playbackId = url.searchParams.get('playbackId');
8 | const Authorization = process.env.LIVEPEER_API_KEY;
9 | if (!playbackId) {
10 | return NextResponse.json('Missing playbackId', { status: 401 });
11 | }
12 | if (!Authorization) {
13 | return NextResponse.json('Missing Authorization Token', {
14 | status: 401,
15 | });
16 | }
17 |
18 | const response = await fetch(
19 | `https://livepeer.studio/api/playback/${playbackId}`,
20 | {
21 | method: 'GET',
22 | headers: {
23 | Authorization: Authorization,
24 | },
25 | }
26 | );
27 |
28 | const data = await response.json();
29 |
30 | return NextResponse.json(data);
31 | } catch (error) {
32 | return NextResponse.json('Internal Server Error', { status: 500 });
33 | }
34 | };
35 |
--------------------------------------------------------------------------------
/packages/app/app/auth/logout/page.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { useEffect } from 'react';
4 | import { signOut } from 'next-auth/react';
5 | import { useRouter } from 'next/navigation';
6 |
7 | const LogoutPage = () => {
8 | const router = useRouter();
9 |
10 | useEffect(() => {
11 | const performLogout = async () => {
12 | await signOut({ redirect: false });
13 | router.push('/auth/login');
14 | };
15 |
16 | performLogout();
17 | }, [router]);
18 |
19 | return (
20 |
21 |
Logging out...
22 |
23 | Please wait while we securely log you out.
24 |
25 |
26 | );
27 | };
28 |
29 | export default LogoutPage;
30 |
--------------------------------------------------------------------------------
/packages/app/app/not-found.tsx:
--------------------------------------------------------------------------------
1 | import Link from 'next/link';
2 |
3 | export default function Component() {
4 | return (
5 |
6 |
7 |
8 | 404
9 |
10 |
11 | Oops, the page you are looking for could not be found.
12 |
13 |
14 |
19 | Go Back
20 |
21 |
22 |
23 |
24 | );
25 | }
26 |
--------------------------------------------------------------------------------
/packages/app/app/speaker/[session]/components/ClientSidePlayer.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { PlayerWithControls } from '@/components/ui/Player';
4 | import { Src } from '@livepeer/react';
5 |
6 | export default function ClientSidePlayer({
7 | name,
8 | thumbnail,
9 | src,
10 | caption,
11 | }: {
12 | name: string;
13 | thumbnail: string;
14 | src: Src[];
15 | caption?: string;
16 | }) {
17 | return (
18 |
24 | );
25 | }
26 |
--------------------------------------------------------------------------------
/packages/app/app/speaker/[session]/layout.tsx:
--------------------------------------------------------------------------------
1 | 'use server';
2 |
3 | import HomePageNavbar from '@/components/Layout/HomePageNavbar';
4 | import Footer from '@/components/Layout/Footer';
5 |
6 | const Layout = async ({ children }: { children: React.ReactNode }) => {
7 | return (
8 |
9 |
14 |
{children}
15 |
16 |
17 |
18 |
19 | );
20 | };
21 |
22 | export default Layout;
23 |
--------------------------------------------------------------------------------
/packages/app/app/studio/(home)/layout.tsx:
--------------------------------------------------------------------------------
1 | import Support from '@/components/misc/Support';
2 |
3 | const StudioLayout = async (props: { children: React.ReactNode }) => {
4 | return (
5 |
6 |
7 | {props.children}
8 |
9 |
10 |
11 | );
12 | };
13 |
14 | export default StudioLayout;
15 |
--------------------------------------------------------------------------------
/packages/app/app/studio/[organization]/(no-side-bar)/clips/page.tsx:
--------------------------------------------------------------------------------
1 | const ClipsPage = () => {
2 | return ClipsPage
;
3 | };
4 |
5 | export default ClipsPage;
6 |
--------------------------------------------------------------------------------
/packages/app/app/studio/[organization]/(no-side-bar)/layout.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import NavbarStudio from '@/components/Layout/NavbarStudio';
3 |
4 | const Layout = async ({
5 | children,
6 | }: {
7 | children: React.ReactNode;
8 | }) => {
9 | return (
10 |
18 | );
19 | };
20 |
21 | export default Layout;
22 |
--------------------------------------------------------------------------------
/packages/app/app/studio/[organization]/(root)/destinations/components/TwitterConnectButton.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { Button } from '@/components/ui/button';
4 | import Link from 'next/link';
5 | import React from 'react';
6 | import { SiX } from 'react-icons/si';
7 |
8 | const TwitterConnectButton = ({ state }: { state?: string }) => {
9 | return (
10 |
11 |
14 |
15 | );
16 | };
17 |
18 | export default TwitterConnectButton;
19 |
--------------------------------------------------------------------------------
/packages/app/app/studio/[organization]/(root)/destinations/components/YoutubeConnectButton.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 | import { Button } from '@/components/ui/button';
3 | import Link from 'next/link';
4 | import React from 'react';
5 | import Image from 'next/image';
6 |
7 | const YoutubeConnectButton = ({ state }: { state?: string }) => {
8 | return (
9 |
10 |
20 |
21 | );
22 | };
23 |
24 | export default YoutubeConnectButton;
25 |
--------------------------------------------------------------------------------
/packages/app/app/studio/[organization]/(root)/layout.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import NavbarStudio from "@/components/Layout/NavbarStudio";
3 | import SidebarMenu from "@/components/Sidebar/SidebarMenu";
4 |
5 | const Layout = async ({
6 | children,
7 | }: {
8 | children: React.ReactNode;
9 | }) => {
10 | return (
11 |
20 | );
21 | };
22 |
23 | export default Layout;
24 |
--------------------------------------------------------------------------------
/packages/app/app/studio/[organization]/(root)/library/[session]/components/form/utils.ts:
--------------------------------------------------------------------------------
1 | import { IExtendedSession } from '@/lib/types';
2 | import * as z from 'zod';
3 | import { sessionSchema } from '@/lib/schema';
4 |
5 | export const createSessionUpdatePayload = (
6 | values: z.infer,
7 | session: IExtendedSession
8 | ) => ({
9 | session: {
10 | ...values,
11 | _id: session._id,
12 | organizationId: session.organizationId,
13 | eventId: session.eventId,
14 | stageId: session.stageId,
15 | start: session.start ?? Number(new Date()),
16 | end: session.end ?? Number(new Date()),
17 | speakers: session.speakers ?? [],
18 | type: session.type,
19 | },
20 | });
21 |
--------------------------------------------------------------------------------
/packages/app/app/studio/[organization]/(root)/library/components/EmptyLibrary.tsx:
--------------------------------------------------------------------------------
1 | 'use server';
2 |
3 | import EmptyFolder from '@/lib/svg/EmptyFolder';
4 | import UploadVideoDialog from './UploadVideoDialog';
5 |
6 | const EmptyLibrary = () => {
7 | return (
8 |
9 |
10 |
11 |
The library is empty
12 |
13 | Upload your first video to get started!
14 |
15 |
16 |
17 |
18 | );
19 | };
20 |
21 | export default EmptyLibrary;
22 |
--------------------------------------------------------------------------------
/packages/app/app/studio/[organization]/(root)/library/components/tabs/PlaylistsTab.tsx:
--------------------------------------------------------------------------------
1 | 'use server';
2 |
3 | import { CreatePlaylistDialog } from '../CreatePlaylistDialog';
4 | import PlaylistTable, { PlaylistTableSkeleton } from '../PlaylistTable';
5 | import { fetchOrganizationPlaylists } from '@/lib/services/playlistService';
6 |
7 | interface PlaylistsTabProps {
8 | organizationId: string;
9 | }
10 |
11 | const PlaylistsTab = async ({ organizationId }: PlaylistsTabProps) => {
12 | const playlists = await fetchOrganizationPlaylists({ organizationId });
13 |
14 | return (
15 | <>
16 |
17 |
18 |
19 |
20 | >
21 | );
22 | };
23 |
24 | export default PlaylistsTab;
--------------------------------------------------------------------------------
/packages/app/app/studio/[organization]/(root)/library/playlists/[playlistId]/page.tsx:
--------------------------------------------------------------------------------
1 | "use server";
2 |
3 | import { fetchPlaylist } from "@/lib/services/playlistService";
4 | import { fetchSession } from "@/lib/services/sessionService";
5 | import { notFound } from "next/navigation";
6 | import PlaylistDetail from "./components/PlaylistDetail";
7 |
8 | const PlaylistDetailPage = async ({
9 | params: paramsPromise,
10 | }: {
11 | params: Promise<{ organization: string; playlistId: string }>;
12 | }) => {
13 | const params = await paramsPromise;
14 |
15 | const playlist = await fetchPlaylist({
16 | organizationId: params.organization,
17 | playlistId: params.playlistId,
18 | }).catch(() => null);
19 |
20 | if (!playlist) {
21 | notFound();
22 | }
23 |
24 | const sessionsPromises = playlist.sessions.map((id) =>
25 | fetchSession({ session: id.toString() }),
26 | );
27 | const sessions = (await Promise.all(sessionsPromises)).filter(
28 | (session): session is NonNullable => session !== null,
29 | );
30 |
31 | return (
32 |
35 | );
36 | };
37 |
38 | export default PlaylistDetailPage;
39 |
40 |
--------------------------------------------------------------------------------
/packages/app/app/studio/[organization]/(root)/livestreams/[streamId]/components/ClientSidePlayer.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { PlayerWithControls } from '@/components/ui/Player';
4 | import { Src } from '@livepeer/react';
5 |
6 | export default function ClientSidePlayer({
7 | name,
8 | thumbnail,
9 | src,
10 | caption,
11 | }: {
12 | name: string;
13 | thumbnail: string;
14 | src: Src[];
15 | caption?: string;
16 | }) {
17 | return (
18 |
24 | );
25 | }
26 |
--------------------------------------------------------------------------------
/packages/app/app/studio/[organization]/(root)/livestreams/[streamId]/components/StreamHeader.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { IExtendedStage } from '@/lib/types';
4 | import React from 'react';
5 | import Link from 'next/link';
6 | import { ArrowLeft } from 'lucide-react';
7 | import { Button } from '@/components/ui/button';
8 | import { useOrganizationContext } from '@/lib/context/OrganizationContext';
9 |
10 | const StreamHeader = ({
11 | stream,
12 | isLiveStreamPage,
13 | }: {
14 | stream: IExtendedStage;
15 | isLiveStreamPage?: boolean;
16 | }) => {
17 | const { organizationId } = useOrganizationContext();
18 | return (
19 |
20 |
{stream.name}
21 |
22 | {isLiveStreamPage && (
23 |
24 |
28 |
29 | )}
30 |
31 | );
32 | };
33 |
34 | export default StreamHeader;
35 |
--------------------------------------------------------------------------------
/packages/app/app/studio/[organization]/(root)/livestreams/components/ShareLivestream.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 | import ShareButton from '@/components/misc/interact/ShareButton';
3 | import { useOrganizationContext } from '@/lib/context/OrganizationContext';
4 | import React, { useEffect, useState } from 'react';
5 |
6 | const ShareLivestream = ({
7 | streamId,
8 | variant = 'ghost',
9 | }: {
10 | variant?: 'outline' | 'ghost' | 'primary' | 'default';
11 | streamId: string;
12 | }) => {
13 | const { organizationId } = useOrganizationContext();
14 | const [url, setUrl] = useState('');
15 | useEffect(() => {
16 | // This code will only run on the client side
17 | if (typeof window === 'undefined') return;
18 |
19 | setUrl(window.location.origin);
20 | }, []);
21 | return (
22 | e.stopPropagation()}>
23 |
29 |
30 | );
31 | };
32 |
33 | export default ShareLivestream;
34 |
--------------------------------------------------------------------------------
/packages/app/app/studio/[organization]/(root)/livestreams/page.tsx:
--------------------------------------------------------------------------------
1 | const LivestreamsPage = async () => {
2 | return 'livestream';
3 | };
4 |
5 | export default LivestreamsPage;
6 |
--------------------------------------------------------------------------------
/packages/app/app/studio/[organization]/(root)/payments/components/ExpiredSubscriptionCard.tsx:
--------------------------------------------------------------------------------
1 | import { AlertCircle } from 'lucide-react';
2 | import { Card } from '@/components/ui/card';
3 |
4 | export function ExpiredSubscriptionCard() {
5 | return (
6 |
7 |
8 |
9 |
10 |
Your subscription has expired
11 |
12 | Purchase a new subscription to continue streaming.
13 |
14 |
15 |
16 |
17 | );
18 | }
--------------------------------------------------------------------------------
/packages/app/app/studio/[organization]/(root)/payments/components/LoadingState.tsx:
--------------------------------------------------------------------------------
1 | import { Loader2 } from 'lucide-react';
2 |
3 | export function LoadingState() {
4 | return (
5 |
6 |
7 |
8 | Loading subscription details...
9 |
10 |
11 | );
12 | }
--------------------------------------------------------------------------------
/packages/app/app/studio/[organization]/(root)/settings/components/NavigationItem.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 | import useSearchParams from '@/lib/hooks/useSearchParams';
3 |
4 | const NavigationItem = ({ lable, path }: { lable: string; path: string }) => {
5 | const { searchParams, handleTermChange } = useSearchParams();
6 |
7 | const active =
8 | searchParams.get('settingsActiveTab') === path ||
9 | (!searchParams.get('settingsActiveTab') && path === 'basicInfo');
10 | return (
11 |
16 | handleTermChange([
17 | {
18 | key: 'settingsActiveTab',
19 | value: path,
20 | },
21 | ])
22 | }
23 | >
24 |
27 |
28 | );
29 | };
30 |
31 | export default NavigationItem;
32 |
--------------------------------------------------------------------------------
/packages/app/app/studio/[organization]/(root)/settings/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import React from "react";
4 | import CreateOrganizationForm from "@/app/studio/(home)/components/CreateOrganizationForm";
5 | import {
6 | Card,
7 | CardContent,
8 | CardDescription,
9 | CardHeader,
10 | CardTitle,
11 | } from "@/components/ui/card";
12 | import { useOrganizationContext } from "@/lib/context/OrganizationContext";
13 |
14 | const Settings = () => {
15 | const { organization } = useOrganizationContext();
16 | return (
17 |
18 |
19 |
20 | Edit yout channel
21 |
22 | Header logo and description will appear on your channel page
23 |
24 |
25 |
26 |
30 |
31 |
32 |
33 | );
34 | };
35 |
36 | export default Settings;
37 |
--------------------------------------------------------------------------------
/packages/app/app/studio/[organization]/nfts/components/MintNftSort.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import Combobox from '@/components/ui/combo-box';
4 | import useSearchParams from '@/lib/hooks/useSearchParams';
5 | import { eSort } from '@/lib/types';
6 |
7 | import React from 'react';
8 |
9 | const sortOptions = [
10 | { value: 'desc_alpha', label: 'name' },
11 | { value: 'desc_date', label: 'recent' },
12 | ];
13 |
14 | const MintNftSort = () => {
15 | const { searchParams, handleTermChange } = useSearchParams();
16 | const currentSort = searchParams.get('sort') as eSort;
17 |
18 | return (
19 |
20 |
Sort By
21 |
22 |
{
27 | handleTermChange([{ key: 'sort', value: value }]);
28 | }}
29 | />
30 |
31 | );
32 | };
33 |
34 | export default MintNftSort;
35 |
--------------------------------------------------------------------------------
/packages/app/app/studio/[organization]/nfts/create/components/ShareCollection.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 | import ShareButton from '@/components/misc/interact/ShareButton';
3 | import React, { useEffect, useState } from 'react';
4 |
5 | const ShareCollection = ({ collectionId }: { collectionId?: string }) => {
6 | const [url, setUrl] = useState('');
7 | useEffect(() => {
8 | // This code will only run on the client side
9 | if (typeof window === 'undefined') return;
10 |
11 | setUrl(window.location.origin);
12 | }, []);
13 | return (
14 | e.stopPropagation()}>
15 |
19 |
20 | );
21 | };
22 |
23 | export default ShareCollection;
24 |
--------------------------------------------------------------------------------
/packages/app/app/studio/[organization]/nfts/create/components/ShareVideoNFT.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 | import ShareButton from '@/components/misc/interact/ShareButton';
3 | import React, { useEffect, useState } from 'react';
4 |
5 | const ShareVideoNFT = ({
6 | collectionId,
7 | organization,
8 | }: {
9 | organization: string;
10 | collectionId: string;
11 | }) => {
12 | const [url, setUrl] = useState('');
13 | useEffect(() => {
14 | // This code will only run on the client side
15 | if (typeof window === 'undefined') return;
16 |
17 | setUrl(window.location.origin);
18 | }, []);
19 | return (
20 | e.stopPropagation()}>
21 |
25 |
26 | );
27 | };
28 |
29 | export default ShareVideoNFT;
30 |
--------------------------------------------------------------------------------
/packages/app/app/studio/[organization]/nfts/create/components/TransactionHash.tsx:
--------------------------------------------------------------------------------
1 | import { ExternalLinkIcon } from 'lucide-react';
2 | import Link from 'next/link';
3 | import React from 'react';
4 |
5 | const TransactionHash = ({ hash }: { hash: string }) => {
6 | return (
7 |
13 | View on Base Scan
14 |
15 | );
16 | };
17 |
18 | export default TransactionHash;
19 |
--------------------------------------------------------------------------------
/packages/app/app/studio/layout.tsx:
--------------------------------------------------------------------------------
1 | import Support from '@/components/misc/Support';
2 | import { fetchUserAction } from '@/lib/actions/users';
3 | import { redirect } from 'next/navigation';
4 |
5 | const StudioLayout = async (props: { children: React.ReactNode }) => {
6 | console.log('🚀 [StudioLayout] Fetching user data...');
7 | const startTime = Date.now();
8 |
9 | const user = await fetchUserAction();
10 |
11 | console.log(`⏱️ [StudioLayout] User data fetch complete in ${Date.now() - startTime}ms:`, user ? '✅ User found' : '❌ User not found');
12 |
13 | if (!user) {
14 | console.log('🔄 [StudioLayout] Redirecting to login page');
15 | return redirect('/auth/login');
16 | }
17 |
18 | return (
19 |
20 |
21 | {props.children}
22 |
23 |
24 |
25 | );
26 | };
27 |
28 | export default StudioLayout;
29 |
--------------------------------------------------------------------------------
/packages/app/components.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema.json",
3 | "style": "default",
4 | "rsc": true,
5 | "tsx": true,
6 | "tailwind": {
7 | "config": "tailwind.config.js",
8 | "css": "app/globals.css",
9 | "baseColor": "neutral",
10 | "cssVariables": true,
11 | "prefix": ""
12 | },
13 | "aliases": {
14 | "components": "@/components",
15 | "utils": "@/lib/utils"
16 | }
17 | }
--------------------------------------------------------------------------------
/packages/app/components/Layout/EmbedLayout.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 | import { useContext, useEffect } from 'react';
3 | import { TopNavbarContext } from '../../lib/context/TopNavbarContext';
4 |
5 | const EmbedLayout = ({ children }: { children: React.ReactNode }) => {
6 | const { setShowNav } = useContext(TopNavbarContext);
7 |
8 | useEffect(() => {
9 | setShowNav(false);
10 | }, [setShowNav]);
11 |
12 | return children;
13 | };
14 |
15 | export default EmbedLayout;
16 |
--------------------------------------------------------------------------------
/packages/app/components/Layout/SwitchOrganization.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 | import Combobox from '@/components/ui/combo-box';
3 | import React from 'react';
4 | import { useRouter } from 'next/navigation';
5 | import { IExtendedOrganization } from '@/lib/types';
6 | const SwitchOrganization = ({
7 | organization,
8 | organizations = [],
9 | }: {
10 | organization?: string;
11 | organizations?: IExtendedOrganization[];
12 | }) => {
13 | const router = useRouter();
14 |
15 | return (
16 |
17 | ({
20 | label: org.name,
21 | value: org.slug!,
22 | logo: org.logo,
23 | })),
24 | ]}
25 | logo
26 | variant="ghost"
27 | value={organization || ''}
28 | setValue={(org) => {
29 | router.push(`/studio/${org}`);
30 | }}
31 | />
32 |
33 | );
34 | };
35 |
36 | export default SwitchOrganization;
37 |
--------------------------------------------------------------------------------
/packages/app/components/misc/ConnectWalletButton.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 | import { ConnectKitButton } from 'connectkit';
3 | import { Button } from '@/components/ui/button';
4 | interface ConnectWalletButtonProps {
5 | className?: string;
6 | btnText?: string;
7 | }
8 |
9 | export const ConnectWalletButton = ({
10 | btnText = 'Connect Wallet',
11 | className,
12 | }: ConnectWalletButtonProps) => {
13 | return (
14 |
15 | {({ isConnected, show, truncatedAddress, ensName }) => {
16 | return (
17 |
22 | );
23 | }}
24 |
25 | );
26 | };
27 |
--------------------------------------------------------------------------------
/packages/app/components/misc/CopyString.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { toast } from 'sonner';
4 | import { Copy } from 'lucide-react';
5 |
6 | const CopyItem = ({ item, itemName }: { item: string; itemName: string }) => {
7 | const handleCopy = () => {
8 | navigator.clipboard.writeText(item);
9 | toast.success(`Copied ${itemName} to your clipboard`);
10 | };
11 |
12 | return (
13 |
17 | {item}
18 |
19 |
20 | );
21 | };
22 |
23 | export default CopyItem;
24 |
--------------------------------------------------------------------------------
/packages/app/components/misc/CopyText.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 | import { copyToClipboard } from '@/lib/utils/utils';
3 | import { Copy } from 'lucide-react';
4 |
5 | const CopyText = ({
6 | label,
7 | text = '',
8 | width = '450px',
9 | classNames = '',
10 | }: {
11 | label: string;
12 | text?: string;
13 | width?: string;
14 | classNames?: string;
15 | }) => (
16 |
20 |
26 |
27 | copyToClipboard(text)}
29 | className="h-4 w-4 cursor-pointer text-primary"
30 | />
31 |
32 |
33 | );
34 | export default CopyText;
35 |
--------------------------------------------------------------------------------
/packages/app/components/misc/MarkdownDisplay.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 | import MDEditor from '@uiw/react-md-editor';
3 |
4 | const MarkdownDisplay = ({ content }: { content: string }) => {
5 | return (
6 |
14 | );
15 | };
16 |
17 | export default MarkdownDisplay;
18 |
--------------------------------------------------------------------------------
/packages/app/components/misc/SearchBar/SearchResults.tsx:
--------------------------------------------------------------------------------
1 | import { IExtendedSession } from '@/lib/types';
2 |
3 | interface SearchResultsProps {
4 | isLoading: boolean;
5 | searchResults: IExtendedSession[];
6 | onSelect: (result: IExtendedSession) => void;
7 | }
8 |
9 | export function SearchResults({
10 | isLoading,
11 | searchResults,
12 | onSelect,
13 | }: SearchResultsProps) {
14 | if (isLoading) {
15 | return Loading...;
16 | }
17 |
18 | return (
19 |
20 | {searchResults.length > 0 && (
21 |
22 |
Videos
23 | {searchResults.map((result) => (
24 |
onSelect(result)}
26 | className="cursor-pointer p-1 hover:bg-gray-100"
27 | key={result._id.toString()}
28 | >
29 | {result.name}
30 |
31 | ))}
32 |
33 | )}
34 |
35 | );
36 | }
37 |
--------------------------------------------------------------------------------
/packages/app/components/misc/TableSort.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 | import useSearchParams from '@/lib/hooks/useSearchParams';
3 | import { eSort } from '@/lib/types';
4 | import { ChevronsUpDown } from 'lucide-react';
5 |
6 | import React from 'react';
7 |
8 | const TableSort = ({ title, sortBy }: { title: string; sortBy: string }) => {
9 | const { searchParams, handleTermChange } = useSearchParams();
10 | const currentSort = searchParams.get('sort') as eSort;
11 |
12 | const handleSortClick = () => {
13 | let newSort;
14 | if (sortBy === 'date') {
15 | newSort =
16 | currentSort === eSort.asc_date ? eSort.desc_date : eSort.asc_date;
17 | } else if (sortBy === 'name') {
18 | newSort =
19 | currentSort === eSort.asc_alpha ? eSort.desc_alpha : eSort.asc_alpha;
20 | }
21 |
22 | handleTermChange([{ key: 'sort', value: newSort as string }]);
23 | };
24 | return (
25 |
32 | );
33 | };
34 |
35 | export default TableSort;
36 |
--------------------------------------------------------------------------------
/packages/app/components/misc/VideoCard/VideoCardSkeleton.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { EllipsisVertical } from 'lucide-react';
4 |
5 | const VideoCardSkeleton = () => {
6 | return (
7 |
18 | );
19 | };
20 |
21 | export const VideoCardSkeletonMobile = () => {
22 | return (
23 |
30 | );
31 | };
32 |
33 | export default VideoCardSkeleton;
34 |
--------------------------------------------------------------------------------
/packages/app/components/misc/VideoCard/thumbnail.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import Image from "next/image";
4 | import DefaultThumbnail from "@/lib/svg/DefaultThumbnail";
5 | import { AspectRatio } from "@/components/ui/aspect-ratio";
6 |
7 | type ThumbnailProps = {
8 | imageUrl?: string;
9 | fallBack?: string;
10 | };
11 |
12 | export default function Thumbnail({ imageUrl, fallBack }: ThumbnailProps) {
13 | const srcUrl = imageUrl || fallBack;
14 |
15 | if (!srcUrl) {
16 | return (
17 |
21 |
22 |
23 | );
24 | }
25 |
26 | return (
27 |
28 |
45 |
46 | );
47 | }
48 |
--------------------------------------------------------------------------------
/packages/app/components/misc/form/colorPicker.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 | import { SketchPicker } from 'react-color';
3 | import { Input } from '@/components/ui/input';
4 | import { useState } from 'react';
5 | import {
6 | Popover,
7 | PopoverContent,
8 | PopoverTrigger,
9 | } from '@/components/ui/popover';
10 |
11 | const ColorPicker = ({
12 | color,
13 | onChange,
14 | }: {
15 | color: string | undefined;
16 | onChange: (color: string) => void;
17 | }) => {
18 | const [isOpen, setIsOpen] = useState(true);
19 | return (
20 |
21 |
22 | setIsOpen(true)}
30 | />
31 |
32 | {isOpen && (
33 |
34 | {
37 | onChange(color.hex);
38 | }}
39 | />
40 |
41 | )}
42 |
43 | );
44 | };
45 |
46 | export default ColorPicker;
47 |
--------------------------------------------------------------------------------
/packages/app/components/sessions/LiveIndicator.tsx:
--------------------------------------------------------------------------------
1 | export default function LiveIndicator() {
2 | return Live
;
3 | }
4 |
--------------------------------------------------------------------------------
/packages/app/components/sessions/TranscriptionModal.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import React from 'react';
4 | import {
5 | Dialog,
6 | DialogContent,
7 | DialogTitle,
8 | DialogTrigger,
9 | } from '../ui/dialog';
10 | import { Button } from '../ui/button';
11 | import { IExtendedSession } from '@/lib/types';
12 | import { formatTimestamp } from '@/lib/utils/utils';
13 |
14 | const TranscriptionModal = ({ video }: { video: IExtendedSession }) => {
15 | if (!video?.transcripts?.text) return null;
16 | return (
17 |
26 | );
27 | };
28 |
29 | export default TranscriptionModal;
30 |
--------------------------------------------------------------------------------
/packages/app/components/ui/TitleTextItem.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const TitleTextItem = ({
4 | title,
5 | text,
6 | }: {
7 | title: string;
8 | text?: string | number;
9 | }) => {
10 | return (
11 |
12 |
{title}
13 |
{text}
14 |
15 | );
16 | };
17 |
18 | export default TitleTextItem;
19 |
--------------------------------------------------------------------------------
/packages/app/components/ui/aspect-ratio.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import * as AspectRatioPrimitive from '@radix-ui/react-aspect-ratio';
4 |
5 | const AspectRatio = AspectRatioPrimitive.Root;
6 |
7 | export { AspectRatio };
8 |
--------------------------------------------------------------------------------
/packages/app/components/ui/badge.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { cva, type VariantProps } from 'class-variance-authority';
3 |
4 | import { cn } from '@/lib/utils/utils';
5 |
6 | const badgeVariants = cva(
7 | 'inline-flex items-center rounded-xl px-3 py-1.5 transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',
8 | {
9 | variants: {
10 | variant: {
11 | default: ' bg-white border hover:bg-primary/80',
12 | secondary:
13 | 'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80',
14 | destructive:
15 | 'border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80',
16 | outline: 'text-foreground',
17 | },
18 | },
19 | defaultVariants: {
20 | variant: 'default',
21 | },
22 | }
23 | );
24 |
25 | export interface BadgeProps
26 | extends React.HTMLAttributes,
27 | VariantProps {}
28 |
29 | function Badge({ className, variant, ...props }: BadgeProps) {
30 | return (
31 |
32 | );
33 | }
34 |
35 | export { Badge, badgeVariants };
36 |
--------------------------------------------------------------------------------
/packages/app/components/ui/checkbox.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import * as React from 'react';
4 | import * as CheckboxPrimitive from '@radix-ui/react-checkbox';
5 | import { Check } from 'lucide-react';
6 |
7 | import { cn } from '@/lib/utils/utils';
8 |
9 | const Checkbox = React.forwardRef<
10 | React.ElementRef,
11 | React.ComponentPropsWithoutRef
12 | >(({ className, ...props }, ref) => (
13 |
21 |
24 |
25 |
26 |
27 | ));
28 | Checkbox.displayName = CheckboxPrimitive.Root.displayName;
29 |
30 | export { Checkbox };
31 |
--------------------------------------------------------------------------------
/packages/app/components/ui/input.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | import { cn } from '@/lib/utils/utils';
4 |
5 | export interface InputProps
6 | extends React.InputHTMLAttributes {}
7 |
8 | const Input = React.forwardRef(
9 | ({ className, type, ...props }, ref) => {
10 | return (
11 |
20 | );
21 | }
22 | );
23 | Input.displayName = 'Input';
24 |
25 | export { Input };
26 |
--------------------------------------------------------------------------------
/packages/app/components/ui/label.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import * as React from 'react';
4 | import * as LabelPrimitive from '@radix-ui/react-label';
5 | import { cva, type VariantProps } from 'class-variance-authority';
6 |
7 | import { cn } from '@/lib/utils/utils';
8 |
9 | type LabelProps = {
10 | required?: boolean;
11 | };
12 |
13 | const labelVariants = cva(
14 | 'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70'
15 | );
16 |
17 | const Label = React.forwardRef<
18 | React.ElementRef,
19 | React.ComponentPropsWithoutRef &
20 | LabelProps &
21 | VariantProps
22 | >(({ className, required, ...props }, ref) => (
23 |
24 |
29 | {required && *}
30 |
31 | ));
32 | Label.displayName = LabelPrimitive.Root.displayName;
33 |
34 | export { Label };
35 |
--------------------------------------------------------------------------------
/packages/app/components/ui/progress.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import * as React from 'react';
4 | import * as ProgressPrimitive from '@radix-ui/react-progress';
5 |
6 | import { cn } from '@/lib/utils/utils';
7 |
8 | const Progress = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, value, ...props }, ref) => (
12 |
20 |
24 |
25 | ));
26 | Progress.displayName = ProgressPrimitive.Root.displayName;
27 |
28 | export { Progress };
29 |
--------------------------------------------------------------------------------
/packages/app/components/ui/separator.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import * as React from 'react';
4 | import * as SeparatorPrimitive from '@radix-ui/react-separator';
5 |
6 | import { cn } from '@/lib/utils/utils';
7 |
8 | const Separator = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(
12 | (
13 | { className, orientation = 'horizontal', decorative = true, ...props },
14 | ref
15 | ) => (
16 |
27 | )
28 | );
29 | Separator.displayName = SeparatorPrimitive.Root.displayName;
30 |
31 | export { Separator };
32 |
--------------------------------------------------------------------------------
/packages/app/components/ui/skeleton.tsx:
--------------------------------------------------------------------------------
1 | import { cn } from '@/lib/utils/utils';
2 |
3 | function Skeleton({
4 | className,
5 | ...props
6 | }: React.HTMLAttributes) {
7 | return (
8 |
12 | );
13 | }
14 |
15 | export { Skeleton };
16 |
--------------------------------------------------------------------------------
/packages/app/components/ui/sonner.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { useTheme } from 'next-themes';
4 | import { Toaster as Sonner } from 'sonner';
5 |
6 | type ToasterProps = React.ComponentProps;
7 |
8 | const Toaster = ({ ...props }: ToasterProps) => {
9 | const { theme = 'system' } = useTheme();
10 |
11 | return (
12 |
28 | );
29 | };
30 |
31 | export { Toaster };
32 |
--------------------------------------------------------------------------------
/packages/app/components/ui/text-placeholder.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 | import { toast } from 'sonner';
3 | import { Copy } from 'lucide-react';
4 |
5 | export default function TextPlaceholder({ text }: { text: string }) {
6 | const handleCopy = (item: string) => {
7 | navigator.clipboard.writeText(item);
8 | toast.success('Copied to your clipboard');
9 | };
10 |
11 | return (
12 | handleCopy(text)}
15 | >
16 | {text}
17 |
18 | Copy to clipboard
19 |
20 |
21 | );
22 | }
23 |
--------------------------------------------------------------------------------
/packages/app/components/ui/textarea.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | import { cn } from '@/lib/utils/utils';
4 |
5 | export interface TextareaProps
6 | extends React.TextareaHTMLAttributes {}
7 |
8 | const Textarea = React.forwardRef(
9 | ({ className, ...props }, ref) => {
10 | return (
11 |
19 | );
20 | }
21 | );
22 | Textarea.displayName = 'Textarea';
23 |
24 | export { Textarea };
25 |
--------------------------------------------------------------------------------
/packages/app/lib/actions/auth.ts:
--------------------------------------------------------------------------------
1 | 'use server';
2 |
3 | import { magicLinkSignIn } from '../services/authService';
4 |
5 | export const magicLinkSignInAction = async ({ email }: { email: string }) => {
6 | const response = await magicLinkSignIn({
7 | email,
8 | });
9 |
10 | if (!response) {
11 | throw new Error('Error sending magic link');
12 | }
13 |
14 | return response;
15 | };
16 |
--------------------------------------------------------------------------------
/packages/app/lib/actions/chat.ts:
--------------------------------------------------------------------------------
1 | 'use server';
2 | import { cookies } from 'next/headers';
3 | import { revalidatePath } from 'next/cache';
4 | import { IChat } from 'streameth-new-server/src/interfaces/chat.interface';
5 | import { createChat } from '../services/chatService';
6 |
7 | export const createChatAction = async ({ chat }: { chat: IChat }) => {
8 | const response = await createChat({
9 | chat,
10 | });
11 |
12 | if (!response) {
13 | throw new Error('Error creating stage');
14 | }
15 | // revalidatePath('/')
16 | return response;
17 | };
18 |
--------------------------------------------------------------------------------
/packages/app/lib/actions/imageUpload.ts:
--------------------------------------------------------------------------------
1 | 'use server';
2 | import { revalidatePath } from 'next/cache';
3 | import { imageUpload } from '../services/imageUploadService';
4 |
5 | export const imageUploadAction = async ({ data }: { data: FormData }) => {
6 | try {
7 | const res = await imageUpload({
8 | data,
9 | });
10 | revalidatePath('/studio');
11 |
12 | return res;
13 | } catch (e) {
14 | console.error('Error uploading image action');
15 | return '';
16 | }
17 | };
18 |
--------------------------------------------------------------------------------
/packages/app/lib/actions/state.ts:
--------------------------------------------------------------------------------
1 | 'use server';
2 |
3 | import { IState } from 'streameth-new-server/src/interfaces/state.interface';
4 | import { createState } from '../services/stateService';
5 |
6 | export const createStateAction = async ({ state }: { state: IState }) => {
7 | const response = await createState({
8 | state,
9 | });
10 | if (!response) {
11 | throw new Error('Error creating state');
12 | }
13 | return response;
14 | };
15 |
--------------------------------------------------------------------------------
/packages/app/lib/actions/support.ts:
--------------------------------------------------------------------------------
1 | 'use server';
2 |
3 | import { createSupportTicket } from '../services/supportService';
4 |
5 | export const createSupportTicketAction = async ({
6 | message,
7 | telegram,
8 | email,
9 | image,
10 | }: {
11 | message: string;
12 | telegram?: string;
13 | email?: string;
14 | image?: string;
15 | }) => {
16 | const response = await createSupportTicket({
17 | message,
18 | telegram,
19 | email,
20 | image,
21 | });
22 |
23 | if (!response) {
24 | throw new Error('Error creating support ticket');
25 | }
26 |
27 | return response;
28 | };
29 |
--------------------------------------------------------------------------------
/packages/app/lib/actions/users.ts:
--------------------------------------------------------------------------------
1 | 'use server';
2 |
3 | import { fetchUserData } from '../services/userService';
4 |
5 | export const fetchUserAction = async () => {
6 | return await fetchUserData();
7 | };
8 |
--------------------------------------------------------------------------------
/packages/app/lib/context/GeneralContext.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { SpeedInsights } from '@vercel/speed-insights/next';
4 | import { Analytics } from '@vercel/analytics/react';
5 | import SiweContext from './SiweContext';
6 |
7 | const GeneralContext = ({ children }: { children: any }) => {
8 | return (
9 |
10 |
11 |
12 |
13 | {children}
14 |
15 | );
16 | };
17 |
18 | export default GeneralContext;
19 |
--------------------------------------------------------------------------------
/packages/app/lib/context/MobileContext.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 | import { useState, useLayoutEffect, useContext, createContext } from 'react';
3 | import { useMediaQuery } from '@/lib/hooks/useMediaQuery';
4 | import { LoadingContext } from './LoadingContext';
5 | const MobileContext = createContext<{
6 | isMobile: boolean;
7 | }>({
8 | isMobile: true,
9 | });
10 |
11 | const MobileContextProvider = ({ children }: { children: React.ReactNode }) => {
12 | const desktop = '(min-width: 768px)';
13 | const [isMobile, setIsMobile] = useState(!useMediaQuery(desktop));
14 |
15 | return (
16 |
17 | {children}
18 |
19 | );
20 | };
21 |
22 | export { MobileContext, MobileContextProvider };
23 |
--------------------------------------------------------------------------------
/packages/app/lib/hooks/useClickOutside.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, RefObject } from "react";
2 |
3 | type EventCallback = () => void;
4 |
5 | export default function useClickOutside(
6 | ref: RefObject,
7 | callBack: EventCallback,
8 | ) {
9 | useEffect(() => {
10 | function handleClickOutside(event: MouseEvent) {
11 | if (
12 | ref.current &&
13 | !ref.current.contains(event.target as Node) &&
14 | callBack
15 | ) {
16 | callBack();
17 | }
18 | }
19 | document.addEventListener("mousedown", handleClickOutside);
20 | return () => {
21 | document.removeEventListener("mousedown", handleClickOutside);
22 | };
23 | }, [ref, callBack]);
24 | }
25 |
--------------------------------------------------------------------------------
/packages/app/lib/hooks/useDebounce.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 |
3 | export default function useDebounce(value: string, delay: number) {
4 | const [debouncedValue, setDebouncedValue] = useState(value);
5 |
6 | useEffect(() => {
7 | const handler = setTimeout(() => {
8 | setDebouncedValue(value);
9 | }, delay);
10 |
11 | return () => {
12 | clearTimeout(handler);
13 | };
14 | }, [value, delay]);
15 |
16 | return debouncedValue;
17 | }
18 |
--------------------------------------------------------------------------------
/packages/app/lib/hooks/useGenerateThumbnail.ts:
--------------------------------------------------------------------------------
1 | 'use client';
2 | import { useEffect, useState } from 'react';
3 | import { IExtendedSession } from '../types';
4 | import { generateThumbnailAction } from '../actions/sessions';
5 |
6 | const useGenerateThumbnail = ({ session }: { session: IExtendedSession }) => {
7 | const [thumbnail, setThumbnail] = useState(undefined);
8 |
9 | useEffect(() => {
10 | const getThumbnail = async (session: IExtendedSession) => {
11 | try {
12 | const generatedThumbnail = await generateThumbnailAction(session);
13 | setThumbnail(generatedThumbnail);
14 | } catch (error) {
15 | console.error('Failed to generate thumbnail:', error);
16 | }
17 | };
18 |
19 | if (session && !session.coverImage) {
20 | getThumbnail(session);
21 | }
22 | }, []);
23 |
24 | return thumbnail;
25 | };
26 |
27 | export default useGenerateThumbnail;
28 |
--------------------------------------------------------------------------------
/packages/app/lib/hooks/useMediaQuery.ts:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | export function useMediaQuery(query: string) {
4 | const [value, setValue] = React.useState(false);
5 |
6 | React.useEffect(() => {
7 | function onChange(event: MediaQueryListEvent) {
8 | setValue(event.matches);
9 | }
10 |
11 | const result = matchMedia(query);
12 | result.addEventListener('change', onChange);
13 | setValue(result.matches);
14 |
15 | return () => result.removeEventListener('change', onChange);
16 | }, [query]);
17 |
18 | return value;
19 | }
20 |
--------------------------------------------------------------------------------
/packages/app/lib/hooks/usePlayer.ts:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from "react";
2 |
3 | const usePlayer = (videoRef: React.RefObject) => {
4 | const [currentTime, setCurrentTime] = useState(0);
5 |
6 | const handleSetCurrentTime = (time: number) => {
7 | if (videoRef.current) {
8 | videoRef.current.currentTime = time;
9 | }
10 | setCurrentTime(time);
11 | };
12 |
13 | const videoDuration = videoRef.current?.duration ?? 0;
14 |
15 | useEffect(() => {
16 | const video = videoRef.current;
17 | if (video) {
18 | const handleTimeUpdate = () => {
19 | setCurrentTime(video.currentTime);
20 | };
21 | video.addEventListener("timeupdate", handleTimeUpdate);
22 | return () => video.removeEventListener("timeupdate", handleTimeUpdate);
23 | }
24 | }, [videoRef]);
25 |
26 | return { currentTime, handleSetCurrentTime, videoDuration };
27 | };
28 |
29 | export default usePlayer;
30 |
--------------------------------------------------------------------------------
/packages/app/lib/hooks/useSearchParams.ts:
--------------------------------------------------------------------------------
1 | import {
2 | useSearchParams as useNextSearchParams,
3 | usePathname,
4 | useRouter,
5 | } from 'next/navigation';
6 |
7 | interface ITerm {
8 | key: string;
9 | value: string | undefined;
10 | }
11 | const useSearchParams = () => {
12 | const pathname = usePathname();
13 | const { push } = useRouter();
14 | const searchParams = useNextSearchParams();
15 |
16 | function handleTermChange(terms: ITerm[]) {
17 | const params = new URLSearchParams(searchParams);
18 | for (const term of terms) {
19 | if (term.value) {
20 | params.set(term.key, term.value);
21 | } else {
22 | params.delete(term.key);
23 | }
24 | push(`${pathname}?${params.toString()}`, {
25 | scroll: false,
26 | });
27 | }
28 | }
29 |
30 | return {
31 | searchParams,
32 | handleTermChange,
33 | };
34 | };
35 |
36 | export default useSearchParams;
37 |
--------------------------------------------------------------------------------
/packages/app/lib/services/authService.ts:
--------------------------------------------------------------------------------
1 | import { apiUrl } from '../utils/utils';
2 | import { fetchClient } from './fetch-client';
3 |
4 | export async function magicLinkSignIn({
5 | email,
6 | }: {
7 | email: string;
8 | }): Promise {
9 | const response = await fetchClient(`${apiUrl()}/auth/magic-link`, {
10 | method: 'POST',
11 | headers: {
12 | 'Content-Type': 'application/json',
13 | },
14 | body: JSON.stringify({ email }),
15 | });
16 |
17 | if (!response.ok) {
18 | throw 'Error sending magic link';
19 | }
20 | return await response.json();
21 | }
22 |
--------------------------------------------------------------------------------
/packages/app/lib/services/imageUploadService.ts:
--------------------------------------------------------------------------------
1 | import { apiUrl } from '../utils/utils';
2 | import { fetchClient } from './fetch-client';
3 |
4 | export const imageUpload = async ({
5 | data,
6 | }: {
7 | data: FormData;
8 | }): Promise => {
9 | try {
10 | const response = await fetchClient(`${apiUrl()}/upload`, {
11 | method: 'POST',
12 | cache: 'no-cache',
13 | headers: {},
14 | body: data,
15 | });
16 |
17 | if (!response.ok) {
18 | throw 'Error uploading image';
19 | }
20 |
21 | return (await response.json()).data;
22 | } catch (e) {
23 | console.log('error in upload image', e);
24 | throw e;
25 | }
26 | };
27 |
--------------------------------------------------------------------------------
/packages/app/lib/services/supportService.tsx:
--------------------------------------------------------------------------------
1 | import { apiUrl } from '../utils/utils';
2 | import { ISupport } from 'streameth-new-server/src/interfaces/support.interface';
3 | import { fetchClient } from './fetch-client';
4 |
5 | export async function createSupportTicket({
6 | message,
7 | telegram,
8 | email,
9 | image,
10 | }: {
11 | message: string;
12 | telegram?: string;
13 | email?: string;
14 | image?: string;
15 | }): Promise {
16 | const response = await fetchClient(`${apiUrl()}/tickets`, {
17 | method: 'POST',
18 | headers: {
19 | 'Content-Type': 'application/json',
20 | },
21 | body: JSON.stringify({ message, telegram, email, image }),
22 | });
23 | if (!response.ok) {
24 | throw 'Error creating ticket';
25 | }
26 | return (await response.json()).data;
27 | }
28 |
--------------------------------------------------------------------------------
/packages/app/lib/services/videoUploadService.ts:
--------------------------------------------------------------------------------
1 | import { apiUrl } from '../utils/utils';
2 |
3 | export const videoUpload = async ({
4 | data,
5 | headers = {},
6 | }: {
7 | data: FormData;
8 | headers?: Record;
9 | }): Promise => {
10 | const uploadUrl = `${apiUrl()}/upload`;
11 |
12 | try {
13 | const response = await fetch(uploadUrl, {
14 | method: 'POST',
15 | cache: 'no-cache',
16 | headers: {
17 | ...headers,
18 | },
19 | body: data,
20 | });
21 |
22 | if (!response.ok) {
23 | const errorText = await response.text();
24 | console.error('❌ Upload failed:', {
25 | status: response.status,
26 | error: errorText,
27 | });
28 | throw new Error(`Error uploading video: ${errorText}`);
29 | }
30 |
31 | const result = await response.json();
32 | return result.data;
33 | } catch (e) {
34 | console.error(
35 | '❌ Upload error:',
36 | e instanceof Error ? e.message : 'Unknown error'
37 | );
38 | throw e;
39 | }
40 | };
41 |
--------------------------------------------------------------------------------
/packages/app/lib/svg/YoutubeIcon.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const YoutubeIcon = ({ className = '' }) => {
4 | return (
5 |
14 | );
15 | };
16 |
17 | export default YoutubeIcon;
18 |
--------------------------------------------------------------------------------
/packages/app/lib/uploadVideo.ts:
--------------------------------------------------------------------------------
1 | import { type MutableRefObject } from 'react';
2 | import { Upload } from 'tus-js-client';
3 |
4 | const uploadVideo = (
5 | video: File,
6 | url: string,
7 | abortControllerRef: AbortController,
8 | onProgress: (percentage: number) => void,
9 | onSuccess: () => void
10 | ) => {
11 | const upload = new Upload(video, {
12 | endpoint: url,
13 | uploadSize: video.size,
14 | metadata: {
15 | filename: video.name,
16 | filetype: video.type,
17 | },
18 | onError: (error) => {
19 | console.log('An error occurred:', error);
20 | },
21 | onProgress: (bytesUploaded, bytesTotal) => {
22 | const percentage = Math.round((bytesUploaded / bytesTotal) * 100);
23 | onProgress(percentage);
24 | },
25 | onSuccess: () => {
26 | console.log('Upload successful!');
27 | onSuccess();
28 | },
29 | });
30 |
31 | //abortControllerRef.current = new AbortController();
32 | abortControllerRef.signal.addEventListener('abort', () => {
33 | console.log('Aborting upload');
34 | upload.abort();
35 | });
36 |
37 | upload.start();
38 | };
39 |
40 | export default uploadVideo;
41 |
--------------------------------------------------------------------------------
/packages/app/lib/utils/generateGoogleCalendar.ts:
--------------------------------------------------------------------------------
1 | import { formatDate } from './time';
2 |
3 | const BASE_URL = 'https://calendar.google.com/calendar/render';
4 |
5 | const generateGoogleCalendar = ({
6 | eventName,
7 | description,
8 | start,
9 | end,
10 | }: {
11 | eventName: string;
12 | description: string;
13 | start: Date;
14 | end: Date;
15 | }) => {
16 | const userTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
17 | const startDate = formatDate(start, 'YYYYMMDDTHHmmss', userTimezone);
18 | const endDate = formatDate(end, 'YYYYMMDDTHHmmss', userTimezone);
19 |
20 | return `${BASE_URL}?action=TEMPLATE&text=${encodeURIComponent(
21 | eventName
22 | )}&dates=${startDate}/${endDate}&details=${encodeURIComponent(description)}`;
23 | };
24 |
25 | export default generateGoogleCalendar;
26 |
--------------------------------------------------------------------------------
/packages/app/not-found.tsx:
--------------------------------------------------------------------------------
1 | import Link from 'next/link'
2 |
3 | export default function Component() {
4 | return (
5 |
6 |
7 |
8 | 404
9 |
10 |
11 | Oops, the page you are looking for could not be found.
12 |
13 |
14 |
18 | Go Back
19 |
20 |
21 |
22 |
23 | )
24 | }
25 |
--------------------------------------------------------------------------------
/packages/app/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/packages/app/public/Base-house.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/app/public/Base-house.jpg
--------------------------------------------------------------------------------
/packages/app/public/Folder.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/app/public/Folder.png
--------------------------------------------------------------------------------
/packages/app/public/UserEmptyIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/app/public/UserEmptyIcon.png
--------------------------------------------------------------------------------
/packages/app/public/backgrounds/livestreamBg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/app/public/backgrounds/livestreamBg.png
--------------------------------------------------------------------------------
/packages/app/public/backgrounds/nftBg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/app/public/backgrounds/nftBg.jpg
--------------------------------------------------------------------------------
/packages/app/public/cover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/app/public/cover.png
--------------------------------------------------------------------------------
/packages/app/public/farcaster-transparent-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/app/public/farcaster-transparent-white.png
--------------------------------------------------------------------------------
/packages/app/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/app/public/favicon.ico
--------------------------------------------------------------------------------
/packages/app/public/images/NFTSuccess.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/app/public/images/NFTSuccess.png
--------------------------------------------------------------------------------
/packages/app/public/images/empty-box.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/app/public/images/empty-box.png
--------------------------------------------------------------------------------
/packages/app/public/images/multipleNFT.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/app/public/images/multipleNFT.png
--------------------------------------------------------------------------------
/packages/app/public/images/singleNFT.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/app/public/images/singleNFT.png
--------------------------------------------------------------------------------
/packages/app/public/images/videoPlaceholder.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/app/public/images/videoPlaceholder.png
--------------------------------------------------------------------------------
/packages/app/public/images/youtube-tutorial.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/app/public/images/youtube-tutorial.png
--------------------------------------------------------------------------------
/packages/app/public/images/youtube_social_icon_red.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/app/public/images/youtube_social_icon_red.png
--------------------------------------------------------------------------------
/packages/app/public/lens-green.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/app/public/lens-green.png
--------------------------------------------------------------------------------
/packages/app/public/lib_wsURRdlMczHRgWBe/002clfqcrzy725fn.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/app/public/lib_wsURRdlMczHRgWBe/002clfqcrzy725fn.png
--------------------------------------------------------------------------------
/packages/app/public/lib_wsURRdlMczHRgWBe/0tcvaer9b0dwp6gg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/app/public/lib_wsURRdlMczHRgWBe/0tcvaer9b0dwp6gg.png
--------------------------------------------------------------------------------
/packages/app/public/lib_wsURRdlMczHRgWBe/0ud783pd9etcohgd.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/app/public/lib_wsURRdlMczHRgWBe/0ud783pd9etcohgd.png
--------------------------------------------------------------------------------
/packages/app/public/lib_wsURRdlMczHRgWBe/26sfvru7y3df4lpu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/app/public/lib_wsURRdlMczHRgWBe/26sfvru7y3df4lpu.png
--------------------------------------------------------------------------------
/packages/app/public/lib_wsURRdlMczHRgWBe/2hesu5v0s2y9a7h5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/app/public/lib_wsURRdlMczHRgWBe/2hesu5v0s2y9a7h5.png
--------------------------------------------------------------------------------
/packages/app/public/lib_wsURRdlMczHRgWBe/329bez6yoeajgob0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/app/public/lib_wsURRdlMczHRgWBe/329bez6yoeajgob0.png
--------------------------------------------------------------------------------
/packages/app/public/lib_wsURRdlMczHRgWBe/4179bdhw7ckua4hb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/app/public/lib_wsURRdlMczHRgWBe/4179bdhw7ckua4hb.png
--------------------------------------------------------------------------------
/packages/app/public/lib_wsURRdlMczHRgWBe/46j3ppjs675nuhka.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/app/public/lib_wsURRdlMczHRgWBe/46j3ppjs675nuhka.png
--------------------------------------------------------------------------------
/packages/app/public/lib_wsURRdlMczHRgWBe/4k1ngp6h5n5l1u5l.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/app/public/lib_wsURRdlMczHRgWBe/4k1ngp6h5n5l1u5l.jpg
--------------------------------------------------------------------------------
/packages/app/public/lib_wsURRdlMczHRgWBe/4xmsxtzufoulc5af.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/app/public/lib_wsURRdlMczHRgWBe/4xmsxtzufoulc5af.png
--------------------------------------------------------------------------------
/packages/app/public/lib_wsURRdlMczHRgWBe/550nopa7lubbte09.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/app/public/lib_wsURRdlMczHRgWBe/550nopa7lubbte09.jpg
--------------------------------------------------------------------------------
/packages/app/public/lib_wsURRdlMczHRgWBe/7hf72jf59l4e0mh9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/app/public/lib_wsURRdlMczHRgWBe/7hf72jf59l4e0mh9.png
--------------------------------------------------------------------------------
/packages/app/public/lib_wsURRdlMczHRgWBe/7uegl4bozj0pbp5x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/app/public/lib_wsURRdlMczHRgWBe/7uegl4bozj0pbp5x.png
--------------------------------------------------------------------------------
/packages/app/public/lib_wsURRdlMczHRgWBe/83fie8t0fjm8is86.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/app/public/lib_wsURRdlMczHRgWBe/83fie8t0fjm8is86.png
--------------------------------------------------------------------------------
/packages/app/public/lib_wsURRdlMczHRgWBe/90fnji3oxuxld4l5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/app/public/lib_wsURRdlMczHRgWBe/90fnji3oxuxld4l5.png
--------------------------------------------------------------------------------
/packages/app/public/lib_wsURRdlMczHRgWBe/9upukn8zuxn9rewm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/app/public/lib_wsURRdlMczHRgWBe/9upukn8zuxn9rewm.png
--------------------------------------------------------------------------------
/packages/app/public/lib_wsURRdlMczHRgWBe/a17fxhoo1yzgkowd.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/app/public/lib_wsURRdlMczHRgWBe/a17fxhoo1yzgkowd.jpg
--------------------------------------------------------------------------------
/packages/app/public/lib_wsURRdlMczHRgWBe/a69ad1z1iuc16c6j.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/app/public/lib_wsURRdlMczHRgWBe/a69ad1z1iuc16c6j.png
--------------------------------------------------------------------------------
/packages/app/public/lib_wsURRdlMczHRgWBe/a9c854il4fopdsp0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/app/public/lib_wsURRdlMczHRgWBe/a9c854il4fopdsp0.png
--------------------------------------------------------------------------------
/packages/app/public/lib_wsURRdlMczHRgWBe/byv0vxn9ncxss1tj.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/app/public/lib_wsURRdlMczHRgWBe/byv0vxn9ncxss1tj.png
--------------------------------------------------------------------------------
/packages/app/public/lib_wsURRdlMczHRgWBe/ccz1ztuk5ny0qloe.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/app/public/lib_wsURRdlMczHRgWBe/ccz1ztuk5ny0qloe.png
--------------------------------------------------------------------------------
/packages/app/public/lib_wsURRdlMczHRgWBe/cebcodsnth35kyyg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/app/public/lib_wsURRdlMczHRgWBe/cebcodsnth35kyyg.png
--------------------------------------------------------------------------------
/packages/app/public/lib_wsURRdlMczHRgWBe/d8emwluemsveqjxh.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/app/public/lib_wsURRdlMczHRgWBe/d8emwluemsveqjxh.png
--------------------------------------------------------------------------------
/packages/app/public/lib_wsURRdlMczHRgWBe/defjaol1kkd8qw0p.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/app/public/lib_wsURRdlMczHRgWBe/defjaol1kkd8qw0p.png
--------------------------------------------------------------------------------
/packages/app/public/lib_wsURRdlMczHRgWBe/ee54i24u1no8ee51.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/app/public/lib_wsURRdlMczHRgWBe/ee54i24u1no8ee51.png
--------------------------------------------------------------------------------
/packages/app/public/lib_wsURRdlMczHRgWBe/fxwq49em5sfw8gbu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/app/public/lib_wsURRdlMczHRgWBe/fxwq49em5sfw8gbu.png
--------------------------------------------------------------------------------
/packages/app/public/lib_wsURRdlMczHRgWBe/gv2jrclmcqnmpc3y.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/app/public/lib_wsURRdlMczHRgWBe/gv2jrclmcqnmpc3y.png
--------------------------------------------------------------------------------
/packages/app/public/lib_wsURRdlMczHRgWBe/hf1359h01lj90puc.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/app/public/lib_wsURRdlMczHRgWBe/hf1359h01lj90puc.jpg
--------------------------------------------------------------------------------
/packages/app/public/lib_wsURRdlMczHRgWBe/hnw3w49fub3pfo8b.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/app/public/lib_wsURRdlMczHRgWBe/hnw3w49fub3pfo8b.png
--------------------------------------------------------------------------------
/packages/app/public/lib_wsURRdlMczHRgWBe/igfide8bqmwmyoha.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/app/public/lib_wsURRdlMczHRgWBe/igfide8bqmwmyoha.png
--------------------------------------------------------------------------------
/packages/app/public/lib_wsURRdlMczHRgWBe/kfql38xo5bq7u5f3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/app/public/lib_wsURRdlMczHRgWBe/kfql38xo5bq7u5f3.png
--------------------------------------------------------------------------------
/packages/app/public/lib_wsURRdlMczHRgWBe/ki5ehzpo5d6umljx.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/app/public/lib_wsURRdlMczHRgWBe/ki5ehzpo5d6umljx.png
--------------------------------------------------------------------------------
/packages/app/public/lib_wsURRdlMczHRgWBe/lepklk4j8usqgbnu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/app/public/lib_wsURRdlMczHRgWBe/lepklk4j8usqgbnu.png
--------------------------------------------------------------------------------
/packages/app/public/lib_wsURRdlMczHRgWBe/lm5yh6zo4xfktsd4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/app/public/lib_wsURRdlMczHRgWBe/lm5yh6zo4xfktsd4.png
--------------------------------------------------------------------------------
/packages/app/public/lib_wsURRdlMczHRgWBe/n7lvh3tpq34a2b6c.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/app/public/lib_wsURRdlMczHRgWBe/n7lvh3tpq34a2b6c.png
--------------------------------------------------------------------------------
/packages/app/public/lib_wsURRdlMczHRgWBe/oda6gbylddkx0sx4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/app/public/lib_wsURRdlMczHRgWBe/oda6gbylddkx0sx4.jpg
--------------------------------------------------------------------------------
/packages/app/public/lib_wsURRdlMczHRgWBe/pgm18l3l2za5a1nl.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/app/public/lib_wsURRdlMczHRgWBe/pgm18l3l2za5a1nl.jpg
--------------------------------------------------------------------------------
/packages/app/public/lib_wsURRdlMczHRgWBe/ppz6updxo1mb5m1s.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/app/public/lib_wsURRdlMczHRgWBe/ppz6updxo1mb5m1s.png
--------------------------------------------------------------------------------
/packages/app/public/lib_wsURRdlMczHRgWBe/r1t65rm26qvise2c.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/app/public/lib_wsURRdlMczHRgWBe/r1t65rm26qvise2c.png
--------------------------------------------------------------------------------
/packages/app/public/lib_wsURRdlMczHRgWBe/r2kkux1myqgch11c.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/app/public/lib_wsURRdlMczHRgWBe/r2kkux1myqgch11c.jpg
--------------------------------------------------------------------------------
/packages/app/public/lib_wsURRdlMczHRgWBe/rtmq0dmhuzf7k5fn.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/app/public/lib_wsURRdlMczHRgWBe/rtmq0dmhuzf7k5fn.png
--------------------------------------------------------------------------------
/packages/app/public/lib_wsURRdlMczHRgWBe/sa6qfea8vo2smjyp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/app/public/lib_wsURRdlMczHRgWBe/sa6qfea8vo2smjyp.png
--------------------------------------------------------------------------------
/packages/app/public/lib_wsURRdlMczHRgWBe/sm5tz6r6vwjsy3fn.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/app/public/lib_wsURRdlMczHRgWBe/sm5tz6r6vwjsy3fn.png
--------------------------------------------------------------------------------
/packages/app/public/lib_wsURRdlMczHRgWBe/sstwil0urefp64f5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/app/public/lib_wsURRdlMczHRgWBe/sstwil0urefp64f5.png
--------------------------------------------------------------------------------
/packages/app/public/lib_wsURRdlMczHRgWBe/t8254hkl9zfudy6e.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/app/public/lib_wsURRdlMczHRgWBe/t8254hkl9zfudy6e.png
--------------------------------------------------------------------------------
/packages/app/public/lib_wsURRdlMczHRgWBe/u4bku1qbk3x61o2z.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/app/public/lib_wsURRdlMczHRgWBe/u4bku1qbk3x61o2z.png
--------------------------------------------------------------------------------
/packages/app/public/lib_wsURRdlMczHRgWBe/wrgo0xbslsvxx7ho.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/app/public/lib_wsURRdlMczHRgWBe/wrgo0xbslsvxx7ho.jpg
--------------------------------------------------------------------------------
/packages/app/public/lib_wsURRdlMczHRgWBe/xhjg9gn5lpi674e6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/app/public/lib_wsURRdlMczHRgWBe/xhjg9gn5lpi674e6.png
--------------------------------------------------------------------------------
/packages/app/public/lib_wsURRdlMczHRgWBe/zon4mxn9vxoqd8r2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/app/public/lib_wsURRdlMczHRgWBe/zon4mxn9vxoqd8r2.jpg
--------------------------------------------------------------------------------
/packages/app/public/livepeer-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/app/public/livepeer-logo.png
--------------------------------------------------------------------------------
/packages/app/public/livepeer_black.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/app/public/livepeer_black.png
--------------------------------------------------------------------------------
/packages/app/public/login-background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/app/public/login-background.png
--------------------------------------------------------------------------------
/packages/app/public/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/app/public/logo.png
--------------------------------------------------------------------------------
/packages/app/public/logo_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/app/public/logo_dark.png
--------------------------------------------------------------------------------
/packages/app/public/streameth_banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/app/public/streameth_banner.png
--------------------------------------------------------------------------------
/packages/app/public/streameth_twitter_banner.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/app/public/streameth_twitter_banner.jpeg
--------------------------------------------------------------------------------
/packages/app/public/studio_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/app/public/studio_logo.png
--------------------------------------------------------------------------------
/packages/app/public/success.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/app/public/success.png
--------------------------------------------------------------------------------
/packages/app/public/twitter-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/app/public/twitter-white.png
--------------------------------------------------------------------------------
/packages/app/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "forceConsistentCasingInFileNames": true,
9 | "noEmit": true,
10 | "esModuleInterop": true,
11 | "module": "esnext",
12 | "moduleResolution": "Bundler",
13 | "resolveJsonModule": true,
14 | "isolatedModules": true,
15 | "jsx": "preserve",
16 | "incremental": true,
17 | "experimentalDecorators": true,
18 | "emitDecoratorMetadata": true,
19 | "strictPropertyInitialization": false,
20 | "downlevelIteration": true,
21 | "plugins": [
22 | {
23 | "name": "next"
24 | }
25 | ],
26 | "paths": {
27 | "@/*": ["./*"]
28 | }
29 | },
30 | "include": [
31 | "next-env.d.ts",
32 | "**/*.ts",
33 | "**/*.tsx",
34 | ".next/types/**/*.ts",
35 | ],
36 | "exclude": ["node_modules", "packages"]
37 | }
38 |
--------------------------------------------------------------------------------
/packages/contracts/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .env
3 |
4 | # Hardhat files
5 | /cache
6 | /artifacts
7 |
8 | # TypeChain files
9 | /typechain
10 | /typechain-types
11 |
12 | # solidity-coverage files
13 | /coverage
14 | /coverage.json
15 |
--------------------------------------------------------------------------------
/packages/contracts/README.md:
--------------------------------------------------------------------------------
1 | # NFT Contract`
2 |
3 | ### VideoNFT implementation https://basescan.org/address/0xaB29f1B8DfF6De1ad4aC19de32990683da002a27#code
4 |
5 | ### VideoNFT Factory https://basescan.org/address/0x9F519A0e442f5e8FB1492638b0f3aA621182A9DA#code
6 |
7 | ```shell
8 | npx hardhat help
9 | npx hardhat test
10 | REPORT_GAS=true npx hardhat test
11 | npx hardhat compile
12 | npx hardhat node
13 | npx hardhat run scripts/deploy.ts
14 | npx hardhat run scripts/deploy.ts --network
15 | ```
16 |
17 | ### Environment Variables
18 |
19 | Create a `.env` file at the root of the project and include the following variables:
20 |
21 | ```bash
22 | ALCHEMY_API_KEY_URL= # RPC URL
23 | PRIVATE_KEY=
24 | BASESCAN_API_KEY= # generate an API key from basescan.org
25 | ```
26 |
--------------------------------------------------------------------------------
/packages/contracts/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@streameth/contracts",
3 | "version": "1.0.0",
4 | "main": "index.js",
5 | "license": "MIT",
6 | "private": true,
7 | "dependencies": {
8 | "@nomiclabs/hardhat-waffle": "^2.0.6",
9 | "@openzeppelin/contracts": "^5.0.2",
10 | "@openzeppelin/contracts-upgradeable": "^5.0.2",
11 | "hardhat": "^2.21.0"
12 | },
13 | "devDependencies": {
14 | "@nomicfoundation/hardhat-chai-matchers": "^2.0.0",
15 | "@nomicfoundation/hardhat-ethers": "^3.0.0",
16 | "@nomicfoundation/hardhat-network-helpers": "^1.0.0",
17 | "@nomicfoundation/hardhat-toolbox": "^4.0.0",
18 | "@nomicfoundation/hardhat-verify": "^2.0.0",
19 | "@typechain/ethers-v6": "^0.5.0",
20 | "@typechain/hardhat": "^9.0.0",
21 | "@types/chai": "^4.2.0",
22 | "@types/mocha": ">=9.1.0",
23 | "@types/node": ">=18.0.0",
24 | "chai": "^4.2.0",
25 | "ethers": "^6.4.0",
26 | "hardhat-gas-reporter": "^1.0.8",
27 | "solidity-coverage": "^0.8.0",
28 | "ts-node": ">=8.0.0",
29 | "typechain": "^8.3.0",
30 | "typescript": ">=4.5.0"
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/packages/contracts/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2020",
4 | "module": "commonjs",
5 | "esModuleInterop": true,
6 | "forceConsistentCasingInFileNames": true,
7 | "strict": true,
8 | "skipLibCheck": true,
9 | "resolveJsonModule": true
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/packages/reel-creator/.dockerignore:
--------------------------------------------------------------------------------
1 | .dockerignore
2 |
3 | # Not needed as we are bundling the whole app
4 | node_modules
5 | # Log files
6 | logs
7 | *.log
8 | npm-debug.log*
9 | pnpm-debug.log*
10 | yarn-debug.log*
11 | yarn-error.log*
12 | .next
13 | .git
14 |
15 |
--------------------------------------------------------------------------------
/packages/reel-creator/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next",
3 | "plugins": ["@remotion"],
4 | "overrides": [
5 | {
6 | "files": ["remotion/**/*.{ts,tsx}"],
7 | "extends": ["plugin:@remotion/recommended"]
8 | }
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/packages/reel-creator/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | public/*
4 | public
5 | # dependencies
6 | /node_modules
7 | /.pnp
8 | .pnp.js
9 |
10 | # testing
11 | /coverage
12 |
13 | # next.js
14 | /.next/
15 | /out/
16 |
17 | # production
18 | /build
19 |
20 | # misc
21 | .DS_Store
22 | *.pem
23 |
24 | # debug
25 | npm-debug.log*
26 | yarn-debug.log*
27 | yarn-error.log*
28 | .pnpm-debug.log*
29 |
30 | # local env files
31 | .env*.local
32 |
33 | # vercel
34 | .vercel
35 |
36 | # typescript
37 | *.tsbuildinfo
38 | next-env.d.ts
39 | out/
40 | .env
41 | build
--------------------------------------------------------------------------------
/packages/reel-creator/.npmrc:
--------------------------------------------------------------------------------
1 | auto-install-peers=true
2 |
--------------------------------------------------------------------------------
/packages/reel-creator/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:18-alpine
2 |
3 | WORKDIR /app
4 |
5 | RUN apk add --no-cache ffmpeg
6 |
7 | # Copy workspace files
8 | COPY package.json yarn.lock ./
9 |
10 | # Ensure the directory structure exists
11 | RUN mkdir -p packages/reel-creator
12 |
13 | # Copy the reel-creator package files
14 | COPY packages/reel-creator/package.json ./packages/reel-creator/
15 |
16 | # Install all dependencies at the workspace root
17 | RUN yarn install --frozen-lockfile
18 |
19 | # Copy the source code
20 | COPY packages/reel-creator ./packages/reel-creator
21 |
22 | WORKDIR /app/packages/reel-creator
23 |
24 | # Build using npx to ensure next is found
25 | ENV NODE_ENV=production
26 | RUN npx next build
27 |
28 | # Clean install only production dependencies
29 | RUN yarn install --frozen-lockfile --production
30 |
31 | # Start the application
32 | CMD ["node", ".next/standalone/server.js"]
33 |
--------------------------------------------------------------------------------
/packages/reel-creator/Dockerfile.dev:
--------------------------------------------------------------------------------
1 | FROM node:18-alpine
2 |
3 | WORKDIR /app
4 |
5 | RUN apk add --no-cache ffmpeg
6 |
7 | # Copy package files first for better caching
8 | COPY package.json yarn.lock ./
9 | COPY packages/reel-creator/package.json ./packages/reel-creator/
10 |
11 | # Install all dependencies (including dev dependencies)
12 | RUN yarn install --frozen-lockfile
13 |
14 | # Copy source code
15 | COPY packages/reel-creator ./packages/reel-creator
16 |
17 | WORKDIR /app/packages/reel-creator
18 |
19 | # Remove production-specific commands
20 | # RUN npx next build
21 | # RUN yarn install --frozen-lockfile --production
22 |
23 | # Start in dev mode with environment variables
24 | ENV NODE_ENV=development
25 | CMD ["yarn", "dev"]
26 |
--------------------------------------------------------------------------------
/packages/reel-creator/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/reel-creator/app/favicon.ico
--------------------------------------------------------------------------------
/packages/reel-creator/app/hooks/useCurrentPlayerFrame.tsx:
--------------------------------------------------------------------------------
1 | import { CallbackListener, PlayerRef } from "@remotion/player";
2 | import { useCallback, useSyncExternalStore } from "react";
3 |
4 | export const useCurrentPlayerFrame = (ref: React.RefObject) => {
5 | const subscribe = useCallback(
6 | (onStoreChange: () => void) => {
7 | const { current } = ref;
8 | if (!current) {
9 | return () => undefined;
10 | }
11 | const updater: CallbackListener<"frameupdate"> = ({ detail }) => {
12 | onStoreChange();
13 | };
14 | current.addEventListener("frameupdate", updater);
15 | return () => {
16 | current.removeEventListener("frameupdate", updater);
17 | };
18 | },
19 | [ref]
20 | );
21 |
22 | const data = useSyncExternalStore(
23 | subscribe,
24 | () => ref.current?.getCurrentFrame() ?? 0,
25 | () => 0
26 | );
27 |
28 | return data;
29 | };
30 |
--------------------------------------------------------------------------------
/packages/reel-creator/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import "../styles/global.css";
2 | import { Metadata } from "next";
3 | import { TimelineProvider } from '@/context/TimelineContext';
4 | import { EditorProvider } from '../context/EditorContext';
5 |
6 | export const metadata: Metadata = {
7 | title: "Remotion and Next.js",
8 | description: "Remotion and Next.js",
9 | viewport: "width=device-width, initial-scale=1, maximum-scale=1",
10 | };
11 |
12 | export default function RootLayout({
13 | children,
14 | }: {
15 | children: React.ReactNode;
16 | }) {
17 | return (
18 |
19 |
20 |
21 | {children}
22 |
23 |
24 |
25 | );
26 | }
27 |
--------------------------------------------------------------------------------
/packages/reel-creator/app/page.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 | import React, {useRef} from 'react';
3 | import VideoEditorSidebar from '../components/EditorSidebar';
4 | import PlayerComponent from '../components/Player';
5 | import Timeline from '@/components/timeline';
6 | import { PlayerRef } from '@remotion/player';
7 |
8 | export default function VideoEditorLayout() {
9 |
10 | return (
11 |
22 |
23 | );
24 | }
--------------------------------------------------------------------------------
/packages/reel-creator/components.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema.json",
3 | "style": "default",
4 | "rsc": true,
5 | "tsx": true,
6 | "tailwind": {
7 | "config": "tailwind.config.ts",
8 | "css": "styles/global.css",
9 | "baseColor": "neutral",
10 | "cssVariables": false,
11 | "prefix": ""
12 | },
13 | "aliases": {
14 | "components": "@/components",
15 | "utils": "@/lib/utils",
16 | "ui": "@/components/ui",
17 | "lib": "@/lib",
18 | "hooks": "@/hooks"
19 | }
20 | }
--------------------------------------------------------------------------------
/packages/reel-creator/components/AlignEnd.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | export const AlignEnd: React.FC<{
4 | children: React.ReactNode;
5 | }> = ({ children }) => {
6 | return {children}
;
7 | };
8 |
--------------------------------------------------------------------------------
/packages/reel-creator/components/Container.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | export const InputContainer: React.FC<{
4 | children: React.ReactNode;
5 | }> = ({ children }) => {
6 | return (
7 |
8 | {children}
9 |
10 | );
11 | };
12 |
--------------------------------------------------------------------------------
/packages/reel-creator/components/Error.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | export const ErrorComp: React.FC<{
4 | message: string;
5 | }> = ({ message }) => {
6 | return (
7 |
8 |
22 |
Error: {message}
23 |
24 | );
25 | };
26 |
--------------------------------------------------------------------------------
/packages/reel-creator/components/Input.tsx:
--------------------------------------------------------------------------------
1 | import React, { useCallback } from "react";
2 |
3 | export const Input: React.FC<{
4 | text: any;
5 | setText: React.Dispatch>;
6 | disabled?: boolean;
7 | }> = ({ text, setText, disabled }) => {
8 | const onChange: React.ChangeEventHandler = useCallback(
9 | (e) => {
10 | setText(e.currentTarget.value);
11 | },
12 | [setText],
13 | );
14 |
15 | return (
16 |
23 | );
24 | };
25 |
--------------------------------------------------------------------------------
/packages/reel-creator/components/Labels.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useTimeline } from '@/context/TimelineContext';
3 |
4 | const TimelineLabels: React.FC = () => {
5 | const { events } = useTimeline();
6 |
7 | return (
8 |
9 | {events.map((event) => (
10 |
14 | { event.id}
15 |
16 | ))}
17 |
18 | );
19 | };
20 |
21 | export default TimelineLabels;
22 |
--------------------------------------------------------------------------------
/packages/reel-creator/components/Modal.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {
3 | Dialog,
4 | DialogContent,
5 | DialogDescription,
6 | DialogHeader,
7 | DialogTitle,
8 | } from "@/components/ui/dialog";
9 |
10 | interface DialogProps {
11 | isOpen: boolean;
12 | onClose: () => void;
13 | children: React.ReactNode;
14 | title?: string;
15 | description?: string;
16 | }
17 |
18 | const CustomDialog: React.FC = ({ isOpen, onClose, children, title, description }) => {
19 | return (
20 |
31 | );
32 | };
33 |
34 | export default CustomDialog;
--------------------------------------------------------------------------------
/packages/reel-creator/components/ProgressBar.tsx:
--------------------------------------------------------------------------------
1 | import React, { useMemo } from "react";
2 |
3 | export const ProgressBar: React.FC<{
4 | progress: number;
5 | }> = ({ progress }) => {
6 | const fill: React.CSSProperties = useMemo(() => {
7 | return {
8 | width: `${progress * 100}%`,
9 | };
10 | }, [progress]);
11 |
12 | return (
13 |
21 | );
22 | };
23 |
--------------------------------------------------------------------------------
/packages/reel-creator/components/Spacing.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | export const Spacing: React.FC = () => {
4 | return ;
5 | };
6 |
--------------------------------------------------------------------------------
/packages/reel-creator/components/sidebar-components/AnimationSettings.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useTimeline } from "@/context/TimelineContext";
3 | import { Button } from "../ui/button";
4 | export default function AnimationSettings() {
5 | const { events, addEvent } = useTimeline();
6 | return (
7 |
8 |
Animation Settings
9 |
25 |
26 | );
27 | }
28 |
--------------------------------------------------------------------------------
/packages/reel-creator/components/sidebar-components/BrandingSettings.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default function BrandingSettings() {
4 | return (
5 |
6 |
Branding Settings
7 | {/* Add branding settings here */}
8 |
9 | );
10 | }
--------------------------------------------------------------------------------
/packages/reel-creator/components/sidebar-components/LayoutSettings.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useEditorContext } from '../../context/EditorContext';
3 | import { Label } from "@/components/ui/label";
4 | import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
5 |
6 | export default function LayoutSettings() {
7 | const { selectedAspectRatio, aspectRatios, setSelectedAspectRatio } = useEditorContext();
8 |
9 | return (
10 |
11 |
Layout Settings
12 |
13 |
14 |
15 | {aspectRatios.map((ratio) => (
16 |
17 |
18 |
19 |
20 | ))}
21 |
22 |
23 | {/* Add more layout settings here */}
24 |
25 | );
26 | }
--------------------------------------------------------------------------------
/packages/reel-creator/components/timeline/TimelineMarkers.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const TimelineMarkers = ({
4 | maxLength,
5 | timelineWidth,
6 | }: {
7 | maxLength: number;
8 | timelineWidth: number;
9 | }): React.ReactNode => {
10 | const getMarkerPosition = (time: number) =>
11 | (time / maxLength) * timelineWidth;
12 |
13 | const renderSecondMarkers = () => {
14 | const markers = [];
15 | for (let i = 0; i <= maxLength; i += 30) {
16 | const position = getMarkerPosition(i);
17 | markers.push(
18 |
23 |
24 | {`${i}s`}
25 |
26 |
27 |
28 | );
29 | }
30 | return markers;
31 | };
32 |
33 | return (
34 |
35 | {renderSecondMarkers()}
36 |
37 | );
38 | };
39 |
40 | export default TimelineMarkers;
41 |
--------------------------------------------------------------------------------
/packages/reel-creator/components/ui/collapsible.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
4 |
5 | const Collapsible = CollapsiblePrimitive.Root
6 |
7 | const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger
8 |
9 | const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent
10 |
11 | export { Collapsible, CollapsibleTrigger, CollapsibleContent }
12 |
--------------------------------------------------------------------------------
/packages/reel-creator/components/ui/input.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | export interface InputProps
6 | extends React.InputHTMLAttributes {}
7 |
8 | const Input = React.forwardRef(
9 | ({ className, type, ...props }, ref) => {
10 | return (
11 |
20 | )
21 | }
22 | )
23 | Input.displayName = "Input"
24 |
25 | export { Input }
26 |
--------------------------------------------------------------------------------
/packages/reel-creator/components/ui/label.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as LabelPrimitive from "@radix-ui/react-label"
5 | import { cva, type VariantProps } from "class-variance-authority"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const labelVariants = cva(
10 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
11 | )
12 |
13 | const Label = React.forwardRef<
14 | React.ElementRef,
15 | React.ComponentPropsWithoutRef &
16 | VariantProps
17 | >(({ className, ...props }, ref) => (
18 |
23 | ))
24 | Label.displayName = LabelPrimitive.Root.displayName
25 |
26 | export { Label }
27 |
--------------------------------------------------------------------------------
/packages/reel-creator/helpers/api-response.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from "next/server";
2 | import { z, ZodType } from "zod";
3 |
4 | export type ApiResponse =
5 | | {
6 | type: "error";
7 | message: string;
8 | }
9 | | {
10 | type: "success";
11 | data: Res;
12 | };
13 |
14 | export const executeApi =
15 | (
16 | schema: Req,
17 | handler: (req: Request, body: z.infer) => Promise,
18 | ) =>
19 | async (req: Request) => {
20 | try {
21 | const payload = await req.json();
22 | const parsed = schema.parse(payload);
23 | const data = await handler(req, parsed);
24 | return NextResponse.json({
25 | type: "success",
26 | data: data,
27 | });
28 | } catch (err) {
29 | return NextResponse.json(
30 | { type: "error", message: (err as Error).message },
31 | {
32 | status: 500,
33 | },
34 | );
35 | }
36 | };
37 |
--------------------------------------------------------------------------------
/packages/reel-creator/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { clsx, type ClassValue } from "clsx";
2 | import { twMerge } from "tailwind-merge";
3 |
4 | export function cn(...inputs: ClassValue[]) {
5 | return twMerge(clsx(inputs));
6 | }
7 |
--------------------------------------------------------------------------------
/packages/reel-creator/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | output: "standalone",
4 | };
5 |
6 | module.exports = nextConfig;
7 |
--------------------------------------------------------------------------------
/packages/reel-creator/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/packages/reel-creator/remotion.config.ts:
--------------------------------------------------------------------------------
1 | // See all configuration options: https://remotion.dev/docs/config
2 | // Each option also is available as a CLI flag: https://remotion.dev/docs/cli
3 |
4 | // Note: When using the Node.JS APIs, the config file doesn't apply. Instead, pass options directly to the APIs
5 |
6 | import { Config } from "@remotion/cli/config";
7 | import { webpackOverride } from "./remotion/webpack-override.mjs";
8 |
9 | Config.setVideoImageFormat("jpeg");
10 |
11 | Config.overrideWebpackConfig(webpackOverride);
12 |
--------------------------------------------------------------------------------
/packages/reel-creator/remotion/Root.tsx:
--------------------------------------------------------------------------------
1 | import EditorComposition from "./Editor";
2 |
3 | export const RemotionRoot: React.FC = () => {
4 | return (
5 | <>
6 |
7 | >
8 | );
9 | };
10 |
--------------------------------------------------------------------------------
/packages/reel-creator/remotion/index.ts:
--------------------------------------------------------------------------------
1 | import { registerRoot } from "remotion";
2 | import { RemotionRoot } from "./Root";
3 | import "../styles/global.css";
4 |
5 | registerRoot(RemotionRoot);
6 |
--------------------------------------------------------------------------------
/packages/reel-creator/remotion/webpack-override.mjs:
--------------------------------------------------------------------------------
1 | import { enableTailwind } from "@remotion/tailwind";
2 |
3 | /**
4 | * @param {import('webpack').Configuration} currentConfig
5 | */
6 | export const webpackOverride = (currentConfig) => {
7 | return enableTailwind(currentConfig);
8 | };
9 |
--------------------------------------------------------------------------------
/packages/reel-creator/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "forceConsistentCasingInFileNames": true,
9 | "noEmit": true,
10 | "esModuleInterop": true,
11 | "module": "esnext",
12 | "moduleResolution": "Bundler",
13 | "resolveJsonModule": true,
14 | "isolatedModules": true,
15 | "jsx": "preserve",
16 | "incremental": true,
17 | "experimentalDecorators": true,
18 | "emitDecoratorMetadata": true,
19 | "strictPropertyInitialization": false,
20 | "downlevelIteration": true,
21 | "plugins": [
22 | {
23 | "name": "next"
24 | }
25 | ],
26 | "paths": {
27 | "@/*": ["./*"]
28 | }
29 | },
30 | "include": [
31 | "next-env.d.ts",
32 | "**/*.ts",
33 | "**/*.tsx",
34 | ".next/types/**/*.ts", "remotion/Editor", "remotion/Editor", "app/data/transcription.js",
35 | ],
36 | "exclude": ["node_modules", "packages"]
37 | }
38 |
--------------------------------------------------------------------------------
/packages/reel-creator/types/fluent-ffmpeg.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'fluent-ffmpeg';
--------------------------------------------------------------------------------
/packages/reel-creator/types/schema.ts:
--------------------------------------------------------------------------------
1 | import { z } from "zod";
2 | import { ValidEditorProps } from "./constants";
3 |
4 | export const RenderRequest = z.object({
5 | id: z.string(),
6 | inputProps: ValidEditorProps,
7 | });
8 |
9 | export const ProgressRequest = z.object({
10 | bucketName: z.string(),
11 | id: z.string(),
12 | });
13 |
14 | export type ProgressResponse =
15 | | {
16 | type: "error";
17 | message: string;
18 | }
19 | | {
20 | type: "progress";
21 | progress: number;
22 | }
23 | | {
24 | type: "done";
25 | url: string;
26 | size: number;
27 | };
28 |
--------------------------------------------------------------------------------
/packages/reel-creator/vercel.json:
--------------------------------------------------------------------------------
1 | {
2 | "buildCommand": "node deploy.mjs && next build"
3 | }
4 |
--------------------------------------------------------------------------------
/packages/server/.gitignore:
--------------------------------------------------------------------------------
1 | dist
2 | debug
3 | node_modules
4 | .env
5 | src/error
6 | tmp/*
7 |
--------------------------------------------------------------------------------
/packages/server/.mocharc.json:
--------------------------------------------------------------------------------
1 | {
2 | "require": ["esbuild-register", "test/global.ts"],
3 | "extension": ["ts"],
4 | "spec": "test/**/*.spec.ts"
5 | }
--------------------------------------------------------------------------------
/packages/server/.npmrc:
--------------------------------------------------------------------------------
1 | engine-strict=true
2 |
--------------------------------------------------------------------------------
/packages/server/.prettierignore:
--------------------------------------------------------------------------------
1 | src/routes/*
2 | src/swagger/*
3 | node_modules
--------------------------------------------------------------------------------
/packages/server/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "trailingComma": "all",
4 | "plugins": ["prettier-plugin-tailwindcss"]
5 | }
6 |
7 |
--------------------------------------------------------------------------------
/packages/server/src/Dockerfile.dev:
--------------------------------------------------------------------------------
1 | FROM node:18-alpine
2 |
3 | WORKDIR /app
4 |
5 | # Install Python and other build dependencies
6 | RUN apk add --no-cache python3 make g++ ffmpeg
7 |
8 | # Copy root workspace files
9 | COPY package.json yarn.lock ./
10 | COPY packages/server/package.json ./packages/server/
11 |
12 | # Install dependencies (including devDependencies)
13 | RUN yarn install --frozen-lockfile
14 |
15 | # Install nodemon globally
16 | RUN yarn global add nodemon
17 |
18 | EXPOSE 3400
19 |
20 | # Use nodemon for hot reloading
21 | CMD ["yarn", "dev:server"]
22 |
--------------------------------------------------------------------------------
/packages/server/src/controllers/user.controller.ts:
--------------------------------------------------------------------------------
1 | import { IUser } from '@interfaces/user.interface';
2 | import UserService from '@services/user.service';
3 | import { IStandardResponse, SendApiResponse } from '@utils/api.response';
4 | import {
5 | Controller,
6 | Get,
7 | Path,
8 | Route,
9 | Security,
10 | SuccessResponse,
11 | Tags,
12 | } from 'tsoa';
13 |
14 | @Tags('User')
15 | @Route('users')
16 | export class UserController extends Controller {
17 | private userService = new UserService();
18 |
19 | // @Security('jwt')
20 | @SuccessResponse('200')
21 | @Get('{walletAddress}')
22 | async getUserById(
23 | @Path() walletAddress: string,
24 | ): Promise> {
25 | const user = await this.userService.get(walletAddress);
26 | return SendApiResponse('user fetched', user);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/packages/server/src/databases/index.ts:
--------------------------------------------------------------------------------
1 | import { config } from '@config';
2 | const { host, user, name, password } = config.db;
3 |
4 | export const dbConnection = {
5 | url:
6 | process.env.NODE_ENV === 'development'
7 | ? `mongodb+srv://${user}:${password}@${host}/${name}?authSource=admin`
8 | : `mongodb://${user}:${password}@${host}/${name}?authSource=admin&retryWrites=true&w=majority`,
9 | options: {
10 | useNewUrlParser: true,
11 | useUnifiedTopology: true,
12 | serverSelectionTimeoutMS: 5000,
13 | socketTimeoutMS: 45000,
14 | },
15 | };
16 |
--------------------------------------------------------------------------------
/packages/server/src/databases/storage/index.ts:
--------------------------------------------------------------------------------
1 | import FsController from './fs';
2 | import DbController from './db';
3 | import mongoose from 'mongoose';
4 | import { IStorageController } from '@interfaces/storage.interface';
5 |
6 | export type StoreType = 'fs' | 'db';
7 | export default class BaseController {
8 | private storage: IStorageController;
9 |
10 | constructor(storeType: StoreType, model?: mongoose.Model) {
11 | switch (storeType) {
12 | case 'fs':
13 | this.storage = new FsController();
14 | break;
15 | case 'db':
16 | this.storage = new DbController(model);
17 | break;
18 | default:
19 | throw new Error(`Unsupported store type: ${storeType}`);
20 | }
21 | }
22 |
23 | get store(): IStorageController {
24 | return this.storage;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/packages/server/src/dtos/auth/auth.dto.ts:
--------------------------------------------------------------------------------
1 | import { AuthType, IAuth } from '@interfaces/auth.interface';
2 | import { IsNotEmpty, IsOptional, IsString } from 'class-validator';
3 |
4 | export class AuthDto implements IAuth {
5 | @IsNotEmpty()
6 | @IsString()
7 | token!: string;
8 |
9 | @IsOptional()
10 | @IsString()
11 | type?: AuthType;
12 |
13 | @IsOptional()
14 | @IsString()
15 | email?: string;
16 | }
17 |
--------------------------------------------------------------------------------
/packages/server/src/dtos/chat/create-chat.dto.ts:
--------------------------------------------------------------------------------
1 | import { IChat, IFrom } from '@interfaces/chat.interface';
2 | import { IsNotEmpty, IsString } from 'class-validator';
3 |
4 | export class CreateChatDto implements IChat {
5 | @IsNotEmpty()
6 | @IsString()
7 | stageId!: string;
8 |
9 | @IsNotEmpty()
10 | @IsString()
11 | from!: IFrom;
12 |
13 | @IsNotEmpty()
14 | @IsString()
15 | message!: string;
16 |
17 | @IsNotEmpty()
18 | @IsString()
19 | timestamp!: number;
20 | }
21 |
--------------------------------------------------------------------------------
/packages/server/src/dtos/marker/create-marker.dto.ts:
--------------------------------------------------------------------------------
1 | import { IMarker } from '@interfaces/marker.interface';
2 | import { ISpeaker } from '@interfaces/speaker.interface';
3 | import {
4 | IsArray,
5 | IsNotEmpty,
6 | IsNumber,
7 | IsOptional,
8 | IsString,
9 | } from 'class-validator';
10 |
11 | export class CreateMarkerDto implements IMarker {
12 | @IsNotEmpty()
13 | @IsString()
14 | name: string;
15 |
16 | @IsOptional()
17 | @IsString()
18 | description?: string;
19 |
20 | @IsNotEmpty()
21 | @IsString()
22 | organizationId: string;
23 |
24 | @IsNotEmpty()
25 | @IsString()
26 | stageId: string;
27 |
28 | @IsNotEmpty()
29 | @IsNumber()
30 | start: number;
31 |
32 | @IsNotEmpty()
33 | @IsNumber()
34 | end: number;
35 |
36 | @IsNotEmpty()
37 | @IsString()
38 | date: string;
39 |
40 | @IsNotEmpty()
41 | @IsString()
42 | color: string;
43 |
44 | @IsOptional()
45 | @IsArray()
46 | speakers?: ISpeaker[];
47 |
48 | @IsNotEmpty()
49 | @IsNumber()
50 | startClipTime: number;
51 |
52 | @IsNotEmpty()
53 | @IsNumber()
54 | endClipTime: number;
55 |
56 | @IsOptional()
57 | @IsString()
58 | pretalxSessionCode?: string;
59 |
60 | @IsOptional()
61 | @IsString()
62 | talkType?: string;
63 | }
64 |
--------------------------------------------------------------------------------
/packages/server/src/dtos/marker/update-marker.dto.ts:
--------------------------------------------------------------------------------
1 | import { IMarker } from '@interfaces/marker.interface';
2 | import {
3 | IsArray,
4 | IsNotEmpty,
5 | IsNumber,
6 | IsOptional,
7 | IsString,
8 | } from 'class-validator';
9 |
10 | export class UpdateMarkerDto {
11 | @IsNotEmpty()
12 | @IsString()
13 | organizationId: string;
14 |
15 | @IsNotEmpty()
16 | @IsArray()
17 | markers: Omit[];
18 | }
19 |
--------------------------------------------------------------------------------
/packages/server/src/dtos/nft/create-colletion.dto.ts:
--------------------------------------------------------------------------------
1 | import {
2 | INftCollection,
3 | NftCollectionType,
4 | } from '@interfaces/nft.collection.interface';
5 | import { IsArray, IsNotEmpty, IsString } from 'class-validator';
6 | import { Types } from 'mongoose';
7 |
8 | export class CreateNftCollectionDto implements INftCollection {
9 | @IsNotEmpty()
10 | @IsString()
11 | name: string;
12 |
13 | @IsNotEmpty()
14 | @IsString()
15 | description: string;
16 |
17 | @IsNotEmpty()
18 | @IsString()
19 | thumbnail: string;
20 |
21 | @IsNotEmpty()
22 | @IsString()
23 | type: NftCollectionType;
24 |
25 | @IsNotEmpty()
26 | @IsString()
27 | organizationId: string | Types.ObjectId;
28 |
29 | @IsNotEmpty()
30 | @IsArray()
31 | videos: {
32 | index: number;
33 | type: string;
34 | sessionId?: string;
35 | stageId?: string;
36 | ipfsURI: string;
37 | }[];
38 |
39 | @IsNotEmpty()
40 | @IsString()
41 | ipfsPath?: string;
42 | }
43 |
--------------------------------------------------------------------------------
/packages/server/src/dtos/nft/update-collection.dto.ts:
--------------------------------------------------------------------------------
1 | import { NftCollectionType } from '@interfaces/nft.collection.interface';
2 | import { IsArray, IsBoolean, IsOptional, IsString } from 'class-validator';
3 | import { Types } from 'mongoose';
4 |
5 | export class UpdateNftCollectionDto {
6 | @IsString()
7 | name: string;
8 |
9 | @IsOptional()
10 | @IsString()
11 | description?: string;
12 |
13 | @IsOptional()
14 | @IsString()
15 | thumbnail?: string;
16 |
17 | @IsOptional()
18 | @IsString()
19 | contractAddress?: string;
20 |
21 | @IsOptional()
22 | @IsString()
23 | ipfsPath?: string;
24 |
25 | @IsOptional()
26 | @IsString()
27 | type?: NftCollectionType;
28 |
29 | @IsOptional()
30 | @IsString()
31 | organizationId?: string | Types.ObjectId;
32 |
33 | @IsOptional()
34 | @IsArray()
35 | videos?: {
36 | index?: number;
37 | type: string;
38 | sessionId?: string;
39 | stageId?: string;
40 | ipfsURI?: string;
41 | }[];
42 | }
43 |
--------------------------------------------------------------------------------
/packages/server/src/dtos/organization/create-organization.dto.ts:
--------------------------------------------------------------------------------
1 | import { IOrganization, ISocials } from '@interfaces/organization.interface';
2 | import {
3 | IsArray,
4 | IsEmail,
5 | IsNotEmpty,
6 | IsOptional,
7 | IsString,
8 | } from 'class-validator';
9 |
10 | export class CreateOrganizationDto implements Omit {
11 | @IsNotEmpty()
12 | @IsString()
13 | name!: string;
14 |
15 | @IsOptional()
16 | @IsString()
17 | description?: string;
18 |
19 | @IsNotEmpty()
20 | @IsString()
21 | logo!: string;
22 |
23 | @IsNotEmpty()
24 | @IsString()
25 | @IsEmail()
26 | email!: string;
27 |
28 | @IsNotEmpty()
29 | @IsString()
30 | address: string;
31 |
32 | @IsOptional()
33 | @IsString()
34 | banner?: string;
35 |
36 | @IsOptional()
37 | @IsString()
38 | url?: string;
39 |
40 | @IsOptional()
41 | @IsArray()
42 | socials?: ISocials[];
43 | }
44 |
--------------------------------------------------------------------------------
/packages/server/src/dtos/organization/orgid.dto.ts:
--------------------------------------------------------------------------------
1 | import { IsNotEmpty, IsString } from 'class-validator';
2 |
3 | export class OrgIdDto {
4 | @IsNotEmpty()
5 | @IsString()
6 | organizationId!: string;
7 | }
8 |
--------------------------------------------------------------------------------
/packages/server/src/dtos/organization/update-organization.dto.ts:
--------------------------------------------------------------------------------
1 | import { IsEmail, IsOptional, IsString } from 'class-validator';
2 |
3 | export class UpdateOrganizationDto {
4 | @IsOptional()
5 | @IsString()
6 | name!: string;
7 |
8 | @IsOptional()
9 | @IsString()
10 | description?: string;
11 |
12 | @IsOptional()
13 | @IsString()
14 | logo!: string;
15 |
16 | @IsOptional()
17 | @IsString()
18 | @IsEmail()
19 | email!: string;
20 |
21 | @IsString()
22 | @IsEmail()
23 | address: string;
24 |
25 | @IsOptional()
26 | @IsString()
27 | banner?: string;
28 |
29 | @IsOptional()
30 | @IsString()
31 | organizationId?: string;
32 |
33 | @IsOptional()
34 | @IsString()
35 | url?: string;
36 | }
37 |
--------------------------------------------------------------------------------
/packages/server/src/dtos/playlist/create-playlist.dto.ts:
--------------------------------------------------------------------------------
1 | import { IsString, IsOptional, IsBoolean, IsArray, IsMongoId } from 'class-validator';
2 |
3 | export class CreatePlaylistDto {
4 | @IsString()
5 | name: string;
6 |
7 | @IsOptional()
8 | @IsString()
9 | description?: string;
10 |
11 | @IsArray()
12 | @IsMongoId({ each: true })
13 | sessions: string[];
14 |
15 | @IsOptional()
16 | @IsBoolean()
17 | isPublic?: boolean;
18 | }
--------------------------------------------------------------------------------
/packages/server/src/dtos/playlist/update-playlist.dto.ts:
--------------------------------------------------------------------------------
1 | import { IsString, IsOptional, IsBoolean, IsArray, IsMongoId } from 'class-validator';
2 |
3 | export class UpdatePlaylistDto {
4 | @IsOptional()
5 | @IsString()
6 | name?: string;
7 |
8 | @IsOptional()
9 | @IsString()
10 | description?: string;
11 |
12 | @IsOptional()
13 | @IsArray()
14 | @IsMongoId({ each: true })
15 | sessions?: string[];
16 |
17 | @IsOptional()
18 | @IsBoolean()
19 | isPublic?: boolean;
20 | }
--------------------------------------------------------------------------------
/packages/server/src/dtos/schedule-importer/create-import.dto.ts:
--------------------------------------------------------------------------------
1 | import {
2 | IScheduleImporter,
3 | ImportType,
4 | } from '@interfaces/schedule-importer.interface';
5 | import { IsNotEmpty, IsOptional, IsString } from 'class-validator';
6 |
7 | export class IScheduleImporterDto
8 | implements
9 | Pick
10 | {
11 | @IsNotEmpty()
12 | @IsString()
13 | url: string;
14 |
15 | @IsNotEmpty()
16 | @IsString()
17 | type: ImportType;
18 |
19 | @IsNotEmpty()
20 | @IsString()
21 | organizationId: string;
22 |
23 | @IsOptional()
24 | @IsString()
25 | stageId: string;
26 | }
27 |
--------------------------------------------------------------------------------
/packages/server/src/dtos/session/upload-session.dto.ts:
--------------------------------------------------------------------------------
1 | import { IUploadSession } from '@interfaces/upload.session.interface';
2 | import { IsNotEmpty, IsOptional, IsString } from 'class-validator';
3 |
4 | export class UploadSessionDto implements IUploadSession {
5 | @IsString()
6 | @IsOptional()
7 | socialId?: string;
8 |
9 | @IsString()
10 | @IsOptional()
11 | organizationId?: string;
12 |
13 | @IsNotEmpty()
14 | @IsString()
15 | sessionId: string;
16 |
17 | @IsNotEmpty()
18 | @IsString()
19 | type: string;
20 |
21 | @IsOptional()
22 | @IsString()
23 | text?: string;
24 |
25 | @IsOptional()
26 | @IsString()
27 | refreshToken?: string;
28 | }
29 |
--------------------------------------------------------------------------------
/packages/server/src/dtos/speaker/create-speaker.dto.ts:
--------------------------------------------------------------------------------
1 | import { ISpeaker } from '@interfaces/speaker.interface';
2 | import { IsNotEmpty, IsOptional, IsString } from 'class-validator';
3 |
4 | export class CreateSpeakerDto implements ISpeaker {
5 | @IsNotEmpty()
6 | @IsString()
7 | name: string;
8 |
9 | @IsNotEmpty()
10 | @IsString()
11 | bio: string;
12 |
13 | @IsNotEmpty()
14 | @IsString()
15 | eventId: string;
16 |
17 | @IsOptional()
18 | @IsString()
19 | twitter?: string;
20 |
21 | @IsOptional()
22 | @IsString()
23 | github?: string;
24 |
25 | @IsOptional()
26 | @IsString()
27 | website?: string;
28 |
29 | @IsOptional()
30 | @IsString()
31 | photo?: string;
32 |
33 | @IsOptional()
34 | @IsString()
35 | company?: string;
36 |
37 | @IsNotEmpty()
38 | @IsString()
39 | organizationId: string;
40 | }
41 |
--------------------------------------------------------------------------------
/packages/server/src/dtos/stage/create-hls.dto.ts:
--------------------------------------------------------------------------------
1 | import { IStage } from '@interfaces/stage.interface';
2 | import { IsNotEmpty, IsString } from 'class-validator';
3 |
4 | export class CreateHlsStageDto implements IStage {
5 | @IsNotEmpty()
6 | @IsString()
7 | name: string;
8 |
9 | @IsNotEmpty()
10 | @IsString()
11 | organizationId: string;
12 |
13 | @IsNotEmpty()
14 | @IsString()
15 | url: string;
16 | }
17 |
--------------------------------------------------------------------------------
/packages/server/src/dtos/stage/create-stage.dto.ts:
--------------------------------------------------------------------------------
1 | import { IPlugin, IStage } from '@interfaces/stage.interface';
2 | import {
3 | IsArray,
4 | IsBoolean,
5 | IsNotEmpty,
6 | IsNumber,
7 | IsOptional,
8 | IsString,
9 | } from 'class-validator';
10 | export class CreateStageDto implements IStage {
11 | @IsNotEmpty()
12 | @IsString()
13 | name: string;
14 |
15 | @IsOptional()
16 | @IsString()
17 | eventId?: string;
18 |
19 | @IsOptional()
20 | @IsArray()
21 | plugins?: IPlugin[];
22 |
23 | @IsOptional()
24 | @IsNumber()
25 | order?: number;
26 |
27 | @IsNotEmpty()
28 | @IsString()
29 | organizationId: string;
30 |
31 | slug?: string;
32 |
33 | @IsOptional()
34 | @IsString()
35 | streamDate?: Date;
36 |
37 | @IsOptional()
38 | @IsString()
39 | streamEndDate?: Date;
40 |
41 | @IsOptional()
42 | @IsString()
43 | thumbnail?: string;
44 |
45 | @IsOptional()
46 | @IsBoolean()
47 | isMultipleDate?: boolean;
48 | }
49 |
--------------------------------------------------------------------------------
/packages/server/src/dtos/stage/livestream.dto.ts:
--------------------------------------------------------------------------------
1 | import { ILiveStream } from '@interfaces/stage.interface';
2 | import { IsNotEmpty, IsString } from 'class-validator';
3 |
4 | export class CreateLiveStreamDto implements ILiveStream {
5 | @IsNotEmpty()
6 | @IsString()
7 | stageId: string;
8 |
9 | @IsNotEmpty()
10 | @IsString()
11 | socialId: string;
12 |
13 | @IsNotEmpty()
14 | @IsString()
15 | socialType: string;
16 |
17 | @IsNotEmpty()
18 | @IsString()
19 | organizationId: string;
20 | }
21 |
--------------------------------------------------------------------------------
/packages/server/src/dtos/state/create-state.dto.ts:
--------------------------------------------------------------------------------
1 | import {
2 | IState,
3 | SheetType,
4 | StateStatus,
5 | StateType,
6 | } from '@interfaces/state.interface';
7 | import { IsNotEmpty, IsOptional, IsString } from 'class-validator';
8 | import { Types } from 'mongoose';
9 |
10 | export class CreateStateDto implements IState {
11 | @IsOptional()
12 | @IsString()
13 | eventId?: string | Types.ObjectId;
14 |
15 | @IsNotEmpty()
16 | @IsString()
17 | organizationId: string | Types.ObjectId;
18 |
19 | @IsOptional()
20 | @IsString()
21 | sessionId?: string | Types.ObjectId;
22 |
23 | @IsOptional()
24 | @IsString()
25 | eventSlug?: string;
26 |
27 | @IsOptional()
28 | @IsString()
29 | sessionSlug?: string;
30 |
31 | @IsOptional()
32 | @IsString()
33 | sheetType?: SheetType;
34 |
35 | @IsNotEmpty()
36 | @IsString()
37 | status?: StateStatus;
38 |
39 | @IsNotEmpty()
40 | @IsString()
41 | type?: StateType;
42 | }
43 |
--------------------------------------------------------------------------------
/packages/server/src/dtos/state/update-state.dto.ts:
--------------------------------------------------------------------------------
1 | import { SheetType, StateStatus, StateType } from '@interfaces/state.interface';
2 | import { IsNotEmpty, IsOptional, IsString } from 'class-validator';
3 | import { Types } from 'mongoose';
4 |
5 | export class UpdateStateDto {
6 | @IsOptional()
7 | @IsString()
8 | eventId?: string | Types.ObjectId;
9 |
10 | @IsOptional()
11 | @IsString()
12 | organizationId?: string | Types.ObjectId;
13 |
14 | @IsOptional()
15 | @IsString()
16 | sessionId?: string | Types.ObjectId;
17 |
18 | @IsOptional()
19 | @IsString()
20 | eventSlug?: string;
21 |
22 | @IsOptional()
23 | @IsString()
24 | sessionSlug?: string;
25 |
26 | @IsOptional()
27 | @IsString()
28 | sheetType?: SheetType;
29 |
30 | @IsOptional()
31 | @IsString()
32 | status?: StateStatus;
33 |
34 | @IsOptional()
35 | @IsString()
36 | type?: StateType;
37 | }
38 |
--------------------------------------------------------------------------------
/packages/server/src/dtos/stream/create-clip.dto.ts:
--------------------------------------------------------------------------------
1 | import { IClip } from '@interfaces/clip.interface';
2 | import {
3 | IsNotEmpty,
4 | IsNumber,
5 | IsObject,
6 | IsOptional,
7 | IsString,
8 | } from 'class-validator';
9 |
10 | export class CreateClipDto implements IClip {
11 | @IsNotEmpty()
12 | @IsString()
13 | clipUrl!: string;
14 |
15 | @IsNotEmpty()
16 | @IsString()
17 | sessionId!: string;
18 |
19 | @IsNotEmpty()
20 | @IsNumber()
21 | start!: number;
22 |
23 | @IsNotEmpty()
24 | @IsNumber()
25 | end!: number;
26 |
27 | @IsNotEmpty()
28 | @IsString()
29 | organizationId!: string;
30 |
31 | @IsOptional()
32 | @IsString()
33 | isEditorEnabled?: boolean;
34 |
35 | @IsOptional()
36 | @IsObject()
37 | editorOptions?: {
38 | frameRate: number;
39 | events: Array<{
40 | label: string;
41 | sessionId?: string;
42 | videoUrl?: string;
43 | }>;
44 | selectedAspectRatio: string;
45 | captionEnabled: boolean;
46 | captionPosition: string;
47 | captionLinesPerPage: number;
48 | captionFont: string;
49 | captionColor: string;
50 | };
51 | }
52 |
--------------------------------------------------------------------------------
/packages/server/src/dtos/stream/create-multistream.dto.ts:
--------------------------------------------------------------------------------
1 | import { IMultiStream } from '@interfaces/stream.interface';
2 | import { IsNotEmpty, IsString } from 'class-validator';
3 |
4 | export class CreateMultiStreamDto implements IMultiStream {
5 | @IsNotEmpty()
6 | @IsString()
7 | name: string;
8 |
9 | @IsNotEmpty()
10 | @IsString()
11 | streamId: string;
12 |
13 | @IsNotEmpty()
14 | @IsString()
15 | targetStreamKey: string;
16 |
17 | @IsNotEmpty()
18 | @IsString()
19 | targetURL: string;
20 |
21 | @IsNotEmpty()
22 | @IsString()
23 | organizationId: string;
24 | }
25 |
--------------------------------------------------------------------------------
/packages/server/src/dtos/stream/delete-multistream.dto.ts:
--------------------------------------------------------------------------------
1 | import { IsNotEmpty, IsString } from 'class-validator';
2 |
3 | export class DeleteMultiStreamDto {
4 | @IsNotEmpty()
5 | @IsString()
6 | streamId: string;
7 |
8 | @IsNotEmpty()
9 | @IsString()
10 | targetId: string;
11 |
12 | @IsNotEmpty()
13 | @IsString()
14 | organizationId: string;
15 | }
16 |
--------------------------------------------------------------------------------
/packages/server/src/dtos/stripe/create-checkout-session.dto.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Data transfer object for creating a Stripe checkout session
3 | */
4 | export class CreateCheckoutSessionDto {
5 | /**
6 | * The organization's ID (required)
7 | */
8 | organizationId!: string;
9 |
10 | /**
11 | * The monthly subscription price (required)
12 | */
13 | totalPrice!: number;
14 |
15 | /**
16 | * Optional subscription tier identifier
17 | */
18 | tier?: string;
19 |
20 | /**
21 | * Legacy parameters - kept for backwards compatibility but not used
22 | * @deprecated Use totalPrice as monthly subscription price instead
23 | */
24 | streamingDays?: number;
25 |
26 | /**
27 | * Legacy parameters - kept for backwards compatibility but not used
28 | * @deprecated Use totalPrice as monthly subscription price instead
29 | */
30 | numberOfStages?: number;
31 | }
--------------------------------------------------------------------------------
/packages/server/src/dtos/stripe/create-portal-session.dto.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Data transfer object for creating a Stripe customer portal session
3 | */
4 | export class CreatePortalSessionDto {
5 | /**
6 | * The organization's ID (required)
7 | */
8 | organizationId!: string;
9 |
10 | /**
11 | * The URL to return to after the customer completes the portal flow (required)
12 | */
13 | returnUrl!: string;
14 | }
--------------------------------------------------------------------------------
/packages/server/src/dtos/support/create-ticket.dto.ts:
--------------------------------------------------------------------------------
1 | import { ISupport } from '@interfaces/support.interface';
2 | import { IsNotEmpty, IsOptional, IsString } from 'class-validator';
3 |
4 | export class CreateSupportTicketDto implements ISupport {
5 | @IsNotEmpty()
6 | @IsString()
7 | message!: string;
8 |
9 | @IsOptional()
10 | @IsString()
11 | telegram?: string;
12 |
13 | @IsOptional()
14 | @IsString()
15 | email?: string;
16 |
17 | @IsOptional()
18 | @IsString()
19 | image?: string;
20 | }
21 |
--------------------------------------------------------------------------------
/packages/server/src/dtos/user/user.dto.ts:
--------------------------------------------------------------------------------
1 | import { IUser } from '@interfaces/user.interface';
2 | import { IsNotEmpty, IsString } from 'class-validator';
3 |
4 | export class UserDto implements Pick {
5 | @IsNotEmpty()
6 | @IsString()
7 | token!: string;
8 | }
9 |
--------------------------------------------------------------------------------
/packages/server/src/exceptions/HttpException.ts:
--------------------------------------------------------------------------------
1 | export class HttpException extends Error {
2 | public status: number;
3 | public message: string;
4 |
5 | constructor(status: number, message: string) {
6 | super(message);
7 | this.status = status;
8 | this.message = message;
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/packages/server/src/http/auth.http:
--------------------------------------------------------------------------------
1 | # baseURL
2 | @baseURL = http://localhost:3000
3 |
4 | ###
5 | # User Signup
6 | POST {{ baseURL }}/signup
7 | Content-Type: application/json
8 |
9 | {
10 | "email": "example@email.com",
11 | "password": "password"
12 | }
13 |
14 | ###
15 | # User Login
16 | POST {{ baseURL }}/login
17 | Content-Type: application/json
18 |
19 | {
20 | "email": "example@email.com",
21 | "password": "password"
22 | }
23 |
24 | ###
25 | # User Logout
26 | POST {{ baseURL }}/logout
27 | Content-Type: application/json
28 |
--------------------------------------------------------------------------------
/packages/server/src/http/users.http:
--------------------------------------------------------------------------------
1 | # baseURL
2 | @baseURL = http://localhost:3000
3 |
4 | ###
5 | # Find All Users
6 | GET {{ baseURL }}/users
7 |
8 | ###
9 | # Find User By Id
10 | GET {{ baseURL }}/users/1
11 |
12 | ###
13 | # Create User
14 | POST {{ baseURL }}/users
15 | Content-Type: application/json
16 |
17 | {
18 | "email": "example@email.com",
19 | "password": "password"
20 | }
21 |
22 | ###
23 | # Modify User By Id
24 | PUT {{ baseURL }}/users/1
25 | Content-Type: application/json
26 |
27 | {
28 | "email": "example@email.com",
29 | "password": "password"
30 | }
31 |
32 | ###
33 | # Delete User By Id
34 | DELETE {{ baseURL }}/users/1
35 |
--------------------------------------------------------------------------------
/packages/server/src/interfaces/auth.interface.ts:
--------------------------------------------------------------------------------
1 | export enum AuthType {
2 | email = 'email',
3 | google = 'google',
4 | }
5 |
6 | export interface IAuth {
7 | token?: string;
8 | type?: AuthType;
9 | email?: string;
10 | }
11 |
--------------------------------------------------------------------------------
/packages/server/src/interfaces/chat.interface.ts:
--------------------------------------------------------------------------------
1 | import { Document, Types } from 'mongoose';
2 |
3 | export interface IFrom {
4 | identity: string;
5 | }
6 |
7 | export interface IChat {
8 | stageId: Types.ObjectId | string;
9 | message: string;
10 | from: IFrom;
11 | timestamp: number;
12 | }
13 |
14 | export interface IChatModel extends IChat, Document {}
15 |
--------------------------------------------------------------------------------
/packages/server/src/interfaces/clip.editor.interface.ts:
--------------------------------------------------------------------------------
1 | import { Types } from 'mongoose';
2 |
3 | export enum ClipEditorStatus {
4 | pending = 'pending',
5 | failed = 'failed',
6 | rendering = 'rendering',
7 | rendered = 'rendered',
8 | uploading = 'uploading',
9 | completed = 'completed',
10 | }
11 | export interface IClipEditor {
12 | _id?: Types.ObjectId;
13 | renderId: string;
14 | organizationId: Types.ObjectId;
15 | stageId: Types.ObjectId;
16 | frameRate: number;
17 | events: Array<{
18 | label: string;
19 | sessionId?: string;
20 | videoUrl?: string;
21 | }>;
22 | selectedAspectRatio: string;
23 | captionEnabled: boolean;
24 | captionPosition: string;
25 | captionLinesPerPage: number;
26 | captionFont: string;
27 | captionColor: string;
28 | clipSessionId: Types.ObjectId;
29 | status: ClipEditorStatus;
30 | statusMessage: string;
31 | }
32 |
--------------------------------------------------------------------------------
/packages/server/src/interfaces/clip.interface.ts:
--------------------------------------------------------------------------------
1 | export interface IClip {
2 | clipUrl: string;
3 | sessionId: string;
4 | start: number;
5 | end: number;
6 | organizationId?: string;
7 | stageId?: string;
8 | isEditorEnabled?: boolean;
9 | editorOptions?: {
10 | frameRate: number;
11 | events: Array<{
12 | label: string;
13 | sessionId?: string;
14 | videoUrl?: string;
15 | }>;
16 | selectedAspectRatio: string;
17 | captionEnabled: boolean;
18 | captionPosition: string;
19 | captionLinesPerPage: number;
20 | captionFont: string;
21 | captionColor: string;
22 | };
23 | clipSessionId?: string;
24 | clipEditorId?: string;
25 | }
26 |
--------------------------------------------------------------------------------
/packages/server/src/interfaces/livepeer.interface.ts:
--------------------------------------------------------------------------------
1 | export enum LivepeerEvent {
2 | assetReady = 'asset.ready',
3 | assetFailed = 'asset.failed',
4 | streamReady = 'stream.ready',
5 | streamIdle = 'stream.idle',
6 | streamStarted = 'stream.started',
7 | recordingReady = 'recording.ready',
8 | //Add whatever you need
9 | }
10 |
11 | interface LivepeerPayload {
12 | id: string;
13 | snapshot: Object;
14 | }
15 |
16 | export interface ILivepeer {
17 | id: string;
18 | webhookId: string;
19 | createdAt: number;
20 | timestamp: number;
21 | event: LivepeerEvent;
22 | payload: LivepeerPayload;
23 | }
24 |
25 | export interface LivepeerRecording {
26 | id?: string;
27 | playbackId?: string;
28 | recordingUrl?: string;
29 | mp4Url?: string;
30 | name?: string;
31 | createdAt?: number;
32 | lastSeen?: number;
33 | }
34 |
35 | export interface MultistreamTarget {
36 | id: string;
37 | name: string;
38 | userId: string;
39 | disabled: boolean;
40 | createdAt: number;
41 | }
42 | export interface LivepeerSDKResponse {
43 | contentType: string;
44 | statusCode: number;
45 | rawResponse: Response;
46 | multistreamTarget?: MultistreamTarget;
47 | error?: Record;
48 | }
--------------------------------------------------------------------------------
/packages/server/src/interfaces/livepeer.webhook.interface.ts:
--------------------------------------------------------------------------------
1 | interface Profile {
2 | fps: number;
3 | gop: string;
4 | name: string;
5 | width: number;
6 | height: number;
7 | bitrate: number;
8 | profile: string;
9 | }
10 | export interface RecordingSessionPayload {
11 | id: string;
12 | kind: string;
13 | name: string;
14 | issues: null;
15 | record: boolean;
16 | userId: string;
17 | lastSeen: number;
18 | parentId: string;
19 | profiles: Profile[];
20 | createdAt: number;
21 | isHealthy: boolean;
22 | projectId: string;
23 | ingestRate: number;
24 | playbackId: string;
25 | sourceBytes: number;
26 | outgoingRate: number;
27 | sourceSegments: number;
28 | recordingStatus: string;
29 | transcodedBytes: number;
30 | transcodedSegments: number;
31 | sourceSegmentsDuration: number;
32 | transcodedSegmentsDuration: number;
33 | recordingUrl: string;
34 | mp4Url: string;
35 | assetId: string;
36 | recordingId: string;
37 | }
38 |
--------------------------------------------------------------------------------
/packages/server/src/interfaces/marker.interface.ts:
--------------------------------------------------------------------------------
1 | import { Types } from 'mongoose';
2 | import { ISpeaker } from './speaker.interface';
3 |
4 | export interface IMarker {
5 | _id?: Types.ObjectId;
6 | sessionId?: Types.ObjectId | string;
7 | name: string;
8 | description?: string;
9 | organizationId: Types.ObjectId | string;
10 | stageId: Types.ObjectId | string;
11 | start: number;
12 | end: number;
13 | date: string;
14 | color: string;
15 | speakers?: ISpeaker[];
16 | slug?: string;
17 | startClipTime: number;
18 | endClipTime: number;
19 | pretalxSessionCode?: string;
20 | talkType?: string;
21 | }
22 |
--------------------------------------------------------------------------------
/packages/server/src/interfaces/nft.collection.interface.ts:
--------------------------------------------------------------------------------
1 | import { Types } from 'mongoose';
2 |
3 | export enum NftCollectionType {
4 | single = 'single',
5 | multiple = 'multiple',
6 | }
7 | export interface INftCollection {
8 | _id?: Types.ObjectId;
9 | name: string;
10 | description?: string;
11 | thumbnail?: string;
12 | type?: NftCollectionType;
13 | organizationId?: Types.ObjectId | string;
14 | videos?: {
15 | index?: number;
16 | type: string;
17 | sessionId?: string;
18 | stageId?: string;
19 | ipfsURI?: string;
20 | }[];
21 | contractAddress?: string;
22 | ipfsPath?: string;
23 | }
24 |
25 | export interface INftCollectionModel
26 | extends Omit,
27 | Document {}
28 |
--------------------------------------------------------------------------------
/packages/server/src/interfaces/playlist.interface.ts:
--------------------------------------------------------------------------------
1 | import { Document, Types } from 'mongoose';
2 |
3 | export interface IPlaylist {
4 | _id?: Types.ObjectId;
5 | organizationId: Types.ObjectId;
6 | name: string;
7 | description?: string;
8 | sessions: Types.ObjectId[];
9 | isPublic: boolean;
10 | }
11 |
12 | export interface IPlaylistCreate {
13 | name: string;
14 | description?: string;
15 | sessions: string[];
16 | isPublic?: boolean;
17 | }
18 |
19 | export type IPlaylistUpdate = Partial;
20 |
21 | export interface IPlaylistModel extends Omit, Document {}
--------------------------------------------------------------------------------
/packages/server/src/interfaces/remotion.webhook.interface.ts:
--------------------------------------------------------------------------------
1 | export interface RemotionPayload {
2 | type: string;
3 | renderId: string;
4 | expectedBucketOwner: string;
5 | bucketName: string;
6 | customData?: { compositionId?: string };
7 | outputUrl?: string;
8 | lambdaErrors?: Array;
9 | outputFile?: string;
10 | timeToFinish?: number;
11 | costs?: {
12 | currency: string;
13 | disclaimer: string;
14 | estimatedCost: number;
15 | estimatedDisplayCost: string;
16 | };
17 | errors?: Array<{
18 | message: string;
19 | name: string;
20 | stack: string;
21 | }>;
22 | }
23 |
24 | export interface EditorProps {
25 | frameRate: number;
26 | events: Array<{
27 | id: string;
28 | label: string;
29 | type: 'media' | 'text' | 'audio';
30 | url: string;
31 | animation?: string;
32 | transcript?: {
33 | language: string;
34 | text: string;
35 | words: Array<{
36 | word: string;
37 | start: number;
38 | end: number;
39 | }>;
40 | };
41 | }>;
42 | selectedAspectRatio: string;
43 | captionEnabled: boolean;
44 | captionPosition: string;
45 | captionLinesPerPage: string;
46 | captionFont: string;
47 | captionColor: string;
48 | }
49 |
--------------------------------------------------------------------------------
/packages/server/src/interfaces/schedule-importer.interface.ts:
--------------------------------------------------------------------------------
1 | import { Types } from 'mongoose';
2 | import { ISession } from './session.interface';
3 | import { IStage } from './stage.interface';
4 |
5 | export enum ImportType {
6 | gsheet = 'gsheet',
7 | pretalx = 'pretalx',
8 | }
9 |
10 | export enum ImportStatus {
11 | pending = 'pending',
12 | completed = 'completed',
13 | failed = 'failed',
14 | }
15 | export interface IScheduleImportMetadata {
16 | sessions: ISession[];
17 | stages: IStage[];
18 | }
19 | export interface IScheduleImporter {
20 | url: string;
21 | type: ImportType;
22 | status: ImportStatus;
23 | organizationId: Types.ObjectId | string;
24 | stageId?: Types.ObjectId | string;
25 | metadata: IScheduleImportMetadata;
26 | }
27 |
--------------------------------------------------------------------------------
/packages/server/src/interfaces/speaker.interface.ts:
--------------------------------------------------------------------------------
1 | import { Document, Types } from 'mongoose';
2 |
3 | export interface ISpeaker {
4 | _id?: string;
5 | name: string;
6 | bio: string;
7 | eventId?: Types.ObjectId | string;
8 | twitter?: string;
9 | github?: string;
10 | website?: string;
11 | photo?: string;
12 | company?: string;
13 | slug?: string;
14 | organizationId: Types.ObjectId | string;
15 | }
16 |
17 | export interface ISpeakerModel extends Omit, Document {}
18 |
--------------------------------------------------------------------------------
/packages/server/src/interfaces/storage.interface.ts:
--------------------------------------------------------------------------------
1 | export interface IStorageController {
2 | create: (query?: string, data?: T, path?: string) => Promise;
3 | update: (id: string, data: T, query?: string) => Promise;
4 | findById: (id: string, fields?: {}) => Promise;
5 | findOne?: (query: {}, path?: string) => Promise;
6 | findAll: (
7 | query?: {},
8 | fields?: {},
9 | path?: string,
10 | skip?: number,
11 | pageSize?: number,
12 | ) => Promise>;
13 | findAllAndSort?: (
14 | query?: {},
15 | path?: string,
16 | skip?: number,
17 | pageSize?: number,
18 | ) => Promise>;
19 | countDocuments?: (query: {}) => Promise;
20 | delete: (id: string) => Promise;
21 | }
22 |
--------------------------------------------------------------------------------
/packages/server/src/interfaces/stream.interface.ts:
--------------------------------------------------------------------------------
1 | export interface IMultiStream {
2 | name: string;
3 | streamId: string;
4 | targetStreamKey: string;
5 | targetURL: string;
6 | organizationId: string;
7 | socialId?: string;
8 | socialType?: string;
9 | broadcastId?: string;
10 | }
11 |
--------------------------------------------------------------------------------
/packages/server/src/interfaces/support.interface.ts:
--------------------------------------------------------------------------------
1 | import { Document, Types } from 'mongoose';
2 |
3 | export interface ISupport {
4 | message: string;
5 | telegram?: string;
6 | email?: string;
7 | image?: string;
8 | }
9 |
10 | export interface ISupportModel extends ISupport, Document {}
11 |
--------------------------------------------------------------------------------
/packages/server/src/interfaces/upload.session.interface.ts:
--------------------------------------------------------------------------------
1 | import { Types } from 'mongoose';
2 |
3 | export interface IUploadSession {
4 | socialId?: string;
5 | organizationId?: string | Types.ObjectId;
6 | sessionId: string | Types.ObjectId;
7 | token?: { key?: string; secret: string };
8 | type: string;
9 | text?: string;
10 | refreshToken?: string;
11 | }
12 |
--------------------------------------------------------------------------------
/packages/server/src/interfaces/user.interface.ts:
--------------------------------------------------------------------------------
1 | import { Document } from 'mongoose';
2 | import { IOrganization } from './organization.interface';
3 |
4 | export enum UserRole {
5 | user = 'user',
6 | admin = 'admin',
7 | }
8 | export interface IUser {
9 | organizations?: IOrganization[];
10 | role?: UserRole;
11 | token?: string;
12 | did?: string;
13 | email?: string;
14 | }
15 | export interface IUserModel extends IUser, Document {}
16 |
--------------------------------------------------------------------------------
/packages/server/src/middlewares/multer.middleware.ts:
--------------------------------------------------------------------------------
1 | import multer from 'multer';
2 |
3 | // Configure multer with increased file size limits for video uploads
4 | export const multerConfig = multer({
5 | storage: multer.memoryStorage(),
6 | limits: {
7 | fileSize: 20 * 1024 * 1024, // 20MB limit
8 | },
9 | });
10 |
--------------------------------------------------------------------------------
/packages/server/src/models/chat.model.ts:
--------------------------------------------------------------------------------
1 | import { IChatModel } from '@interfaces/chat.interface';
2 | import { Schema, model } from 'mongoose';
3 |
4 | const ChatSchema = new Schema({
5 | stageId: { type: Schema.Types.ObjectId, ref: 'Stage' },
6 | message: { type: String, default: '', required: true },
7 | from: {
8 | identity: { type: String, default: '', required: true },
9 | },
10 | timestamp: { type: Number, required: true },
11 | });
12 |
13 | const Chat = model('Chat', ChatSchema);
14 | export default Chat;
15 |
--------------------------------------------------------------------------------
/packages/server/src/models/nft.collection.model.ts:
--------------------------------------------------------------------------------
1 | import {
2 | INftCollectionModel,
3 | NftCollectionType,
4 | } from '@interfaces/nft.collection.interface';
5 | import { Schema, model } from 'mongoose';
6 |
7 | const NftSchema = new Schema(
8 | {
9 | name: { type: String, default: '' },
10 | description: { type: String, default: '' },
11 | thumbnail: { type: String, default: '' },
12 | type: { type: String, enum: Object.keys(NftCollectionType) },
13 | organizationId: { type: Schema.Types.ObjectId, ref: 'Organization' },
14 | videos: [
15 | {
16 | index: { type: Number, default: 0 },
17 | type: { type: String, default: '' },
18 | stageId: { type: String, default: '' },
19 | sessionId: { type: String, default: '' },
20 | ipfsURI: { type: String, default: '' },
21 | },
22 | ],
23 | contractAddress: { type: String, default: '' },
24 | ipfsPath: { type: String, default: '' },
25 | },
26 |
27 | {
28 | timestamps: true,
29 | },
30 | );
31 |
32 | const NftCollection = model('Nft-Collection', NftSchema);
33 | export default NftCollection;
34 |
--------------------------------------------------------------------------------
/packages/server/src/models/playlist.model.ts:
--------------------------------------------------------------------------------
1 | import { IPlaylistModel } from '@interfaces/playlist.interface';
2 | import { Schema, model } from 'mongoose';
3 |
4 | const PlaylistSchema = new Schema(
5 | {
6 | organizationId: { type: Schema.Types.ObjectId, required: true, ref: 'Organization', index: true },
7 | name: { type: String, required: true, maxlength: 255 },
8 | description: { type: String },
9 | sessions: [{ type: Schema.Types.ObjectId, ref: 'Session' }],
10 | isPublic: { type: Boolean, default: false },
11 | },
12 | {
13 | timestamps: true,
14 | },
15 | );
16 |
17 | const Playlist = model('Playlist', PlaylistSchema);
18 |
19 | export default Playlist;
--------------------------------------------------------------------------------
/packages/server/src/models/schedule.model.ts:
--------------------------------------------------------------------------------
1 | import {
2 | ImportStatus,
3 | ImportType,
4 | IScheduleImporter,
5 | } from '@interfaces/schedule-importer.interface';
6 | import { model, Schema } from 'mongoose';
7 |
8 | const ScheduleImportSchema = new Schema(
9 | {
10 | url: { type: String, required: true },
11 | type: { type: String, enum: Object.keys(ImportType), required: true },
12 | status: {
13 | type: String,
14 | enum: Object.keys(ImportStatus),
15 | default: ImportStatus.pending,
16 | required: true,
17 | },
18 | organizationId: { type: Schema.Types.ObjectId, ref: 'Organization' },
19 | metadata: { type: Object, required: true },
20 | },
21 | {
22 | timestamps: true,
23 | },
24 | );
25 |
26 | const ScheduleImport = model(
27 | 'schedule-import',
28 | ScheduleImportSchema,
29 | );
30 | export default ScheduleImport;
31 |
--------------------------------------------------------------------------------
/packages/server/src/models/speaker.model.ts:
--------------------------------------------------------------------------------
1 | import { ISpeakerModel } from '@interfaces/speaker.interface';
2 | import { Schema, model } from 'mongoose';
3 |
4 | const SpeakerSchema = new Schema(
5 | {
6 | name: { type: String, default: '', required: true },
7 | bio: { type: String, default: '', required: true },
8 | eventId: { type: String, default: '' },
9 | twitter: { type: String, default: '' },
10 | github: { type: String, default: '' },
11 | website: { type: String, default: '' },
12 | photo: { type: String, default: '' },
13 | company: { type: String, default: '' },
14 | slug: { type: String, default: '', index: true },
15 | },
16 | {
17 | timestamps: true,
18 | },
19 | );
20 | const Speaker = model('Speaker', SpeakerSchema);
21 | export default Speaker;
22 |
--------------------------------------------------------------------------------
/packages/server/src/models/state.model.ts:
--------------------------------------------------------------------------------
1 | import {
2 | IStateModel,
3 | SheetType,
4 | SocialType,
5 | StateStatus,
6 | StateType,
7 | } from '@interfaces/state.interface';
8 | import { Schema, model } from 'mongoose';
9 |
10 | const StateSchema = new Schema(
11 | {
12 | eventId: { type: Schema.Types.ObjectId, ref: 'Event' },
13 | organizationId: { type: Schema.Types.ObjectId, ref: 'Organization' },
14 | sessionId: { type: Schema.Types.ObjectId, ref: 'Session' },
15 | eventSlug: { type: String, default: '' },
16 | sessionSlug: { type: String, default: '' },
17 | sheetType: { type: String, enum: Object.keys(SheetType) },
18 | socialType: { type: String, enum: Object.keys(SocialType) },
19 | status: {
20 | type: String,
21 | default: StateStatus.pending,
22 | enum: Object.keys(StateStatus),
23 | },
24 | type: { type: String, enum: Object.keys(StateType) },
25 | },
26 | {
27 | timestamps: true,
28 | },
29 | );
30 |
31 | const State = model('State', StateSchema);
32 | export default State;
33 |
--------------------------------------------------------------------------------
/packages/server/src/models/support.model.ts:
--------------------------------------------------------------------------------
1 | import { ISupportModel } from '@interfaces/support.interface';
2 | import { Schema, model } from 'mongoose';
3 |
4 | const SupportSchema = new Schema({
5 | message: { type: String, default: '', required: true },
6 | telegram: { type: String, default: '' },
7 | email: { type: String, default: '' },
8 | image: { type: String, default: '' },
9 | });
10 |
11 | const Support = model('Support', SupportSchema);
12 | export default Support;
13 |
--------------------------------------------------------------------------------
/packages/server/src/models/user.model.ts:
--------------------------------------------------------------------------------
1 | import { IUserModel, UserRole } from '@interfaces/user.interface';
2 | import { Schema, model } from 'mongoose';
3 |
4 | const UserSchema = new Schema(
5 | {
6 | did: {
7 | type: String,
8 | required: true,
9 | index: true,
10 | unique: true,
11 | default: '',
12 | },
13 | email: { type: String, required: true, index: true, unique: true },
14 | organizations: [{ type: Schema.Types.ObjectId, ref: 'Organization' }],
15 | role: { type: String, enum: Object.keys(UserRole), default: UserRole.user },
16 | },
17 | {
18 | timestamps: true,
19 | },
20 | );
21 |
22 | const User = model('User', UserSchema);
23 | export default User;
24 |
--------------------------------------------------------------------------------
/packages/server/src/nodemon.json:
--------------------------------------------------------------------------------
1 | {
2 | "watch": ["src", ".env"],
3 | "ext": "js,ts,json",
4 | "ignore": [
5 | "src/logs/*",
6 | "src/swagger/*",
7 | "src/routes/*",
8 | "src/**/*.{spec,test}.ts"
9 | ],
10 | "exec": "yarn generate:routes && node -r esbuild-runner/register ./src/server.ts --env=development",
11 | "env": {
12 | "NODE_ENV": "development"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/packages/server/src/server.ts:
--------------------------------------------------------------------------------
1 | import App from './app';
2 |
3 | const app = new App();
4 |
5 | app.listen();
6 |
--------------------------------------------------------------------------------
/packages/server/src/services/chat.service.ts:
--------------------------------------------------------------------------------
1 | import BaseController from '@databases/storage';
2 | import { IChat } from '@interfaces/chat.interface';
3 | import Chat from '@models/chat.model';
4 |
5 | export default class ChatService {
6 | private path: string;
7 | private controller: BaseController;
8 | constructor() {
9 | this.path = 'chats';
10 | this.controller = new BaseController('db', Chat);
11 | }
12 |
13 | async create(data: IChat): Promise {
14 | return await this.controller.store.create(data.stageId.toString(), data);
15 | }
16 |
17 | async getAllChatByStageId(stageId: string): Promise> {
18 | return await this.controller.store.findAll({
19 | stageId: stageId,
20 | });
21 | }
22 | }
--------------------------------------------------------------------------------
/packages/server/src/services/support.service.ts:
--------------------------------------------------------------------------------
1 | import { config } from '@config';
2 | import BaseController from '@databases/storage';
3 | import { ISupport } from '@interfaces/support.interface';
4 | import Support from '@models/support.model';
5 | import { Telegraf } from 'telegraf';
6 |
7 | export default class SupportService {
8 | private path: string;
9 | private controller: BaseController;
10 | constructor() {
11 | this.path = 'support';
12 | this.controller = new BaseController('db', Support);
13 | }
14 |
15 | async create(data: ISupport): Promise {
16 | const bot = new Telegraf(config.telegram.apiKey);
17 | await bot.telegram.sendMessage(
18 | config.telegram.chatId,
19 | `Message: ${data.message}, Image: ${data.image ?? ''}, Sender Email: ${
20 | data.email ?? ''
21 | }, Sender Telegram: ${data.telegram ?? ''}`,
22 | );
23 | return await this.controller.store.create(' ', data);
24 | }
25 |
26 | async getAll(): Promise> {
27 | return await this.controller.store.findAll({});
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/packages/server/src/services/user.service.ts:
--------------------------------------------------------------------------------
1 | import BaseController from '@databases/storage';
2 | import { HttpException } from '@exceptions/HttpException';
3 | import { IUser } from '@interfaces/user.interface';
4 | import User from '@models/user.model';
5 |
6 | export default class UserService {
7 | private path: string;
8 | private controller: BaseController;
9 | constructor() {
10 | this.path = 'users';
11 | this.controller = new BaseController('db', User);
12 | }
13 |
14 | async create(data: IUser): Promise {
15 | return this.controller.store.create('', data, this.path);
16 | }
17 |
18 | async get(email: string): Promise {
19 | const findUser = await User.findOne({
20 | email: email,
21 | }).populate('organizations');
22 | if (!findUser) throw new HttpException(404, 'User not found');
23 | return findUser;
24 | }
25 |
26 | async findOne(query: {}): Promise {
27 | return await this.controller.store.findOne(query);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/packages/server/src/utils/ai.transcribes.ts:
--------------------------------------------------------------------------------
1 | import OpenAI from 'openai';
2 | import { config } from '@config';
3 | import { promises as fs, createReadStream } from 'fs';
4 |
5 | export class WhisperAPI {
6 | private openai: OpenAI;
7 |
8 | constructor() {
9 | this.openai = new OpenAI({
10 | apiKey: config.openai.apiKey,
11 | });
12 | }
13 |
14 | async transcribe(
15 | filePath: string,
16 | ): Promise {
17 | const fileStream = createReadStream(filePath);
18 | console.log('Transcribing file:', filePath);
19 | console.log('File size:', (await fs.stat(filePath)).size);
20 | try {
21 | const response = await this.openai.audio.transcriptions.create({
22 | file: fileStream,
23 | model: 'whisper-1',
24 | language: 'en',
25 | response_format: 'verbose_json',
26 | timestamp_granularities: ['word'],
27 | });
28 | // await fs.unlink(filePath);
29 | return response;
30 | } catch (error) {
31 | console.error('Whisper transcription error:', error);
32 | throw error;
33 | }
34 | }
35 | }
36 |
37 | export default new WhisperAPI();
38 |
--------------------------------------------------------------------------------
/packages/server/src/utils/api.response.ts:
--------------------------------------------------------------------------------
1 | export interface IStandardResponse {
2 | status: string;
3 | message: string;
4 | data?: T;
5 | }
6 |
7 | export const SendApiResponse = (
8 | message: string,
9 | data?: T,
10 | status: string = 'success',
11 | ): IStandardResponse => {
12 | return {
13 | status,
14 | message,
15 | data,
16 | };
17 | };
18 |
--------------------------------------------------------------------------------
/packages/server/src/utils/mail.service.ts:
--------------------------------------------------------------------------------
1 | import { config } from '@config';
2 | import nodemailer from 'nodemailer';
3 | const { host, port, user, pass } = config.mail;
4 | interface MailInfo {
5 | recipient: string;
6 | subject: string;
7 | text?: string;
8 | html?: string;
9 | from: string;
10 | }
11 | export default class EmailService {
12 | private mail = nodemailer.createTransport({
13 | host: host,
14 | port: port,
15 | auth: {
16 | user: user,
17 | pass: pass,
18 | },
19 | });
20 | async simpleSend({ recipient, subject, text, from, html }: MailInfo) {
21 | const mailOptions = {
22 | from: from,
23 | to: recipient,
24 | subject: subject,
25 | text: text,
26 | html: html,
27 | };
28 | return this.mail.sendMail(mailOptions);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/packages/server/src/utils/pulse.cron.ts:
--------------------------------------------------------------------------------
1 | import { config } from '@config';
2 | import Pulse from '@pulsecron/pulse';
3 | import { logger } from './logger';
4 | import { dbConnection } from '@databases/index';
5 | const pulse = new Pulse({
6 | db: { address: dbConnection.url, collection: 'agendajobs' },
7 | defaultConcurrency: 20,
8 | maxConcurrency: 40,
9 | resumeOnRestart: true,
10 | });
11 |
12 | pulse.on('fail', (job) => {
13 | logger.warn('Job failed', job.attrs.name);
14 | });
15 |
16 | export default pulse;
17 |
--------------------------------------------------------------------------------
/packages/server/test/auth.ts:
--------------------------------------------------------------------------------
1 | import { ethers } from 'ethers';
2 | import { config } from '@config';
3 |
4 | const { SiweMessage, generateNonce } = require('siwe');
5 | const wallet = ethers.Wallet.createRandom();
6 | const walletAddress = wallet.address;
7 |
8 | export const generateWalletInfo = async () => {
9 | const nonce = generateNonce();
10 | const message = new SiweMessage({
11 | nonce: nonce,
12 | chainId: 1,
13 | address: walletAddress,
14 | version: '1',
15 | domain: config.testUrl,
16 | uri: config.testUrl,
17 | statement: 'Sign in with Ethereum to the app.',
18 | });
19 |
20 | const signature = await wallet.signMessage(message.prepareMessage());
21 |
22 | return {
23 | nonce: nonce,
24 | signature: signature,
25 | message: message.prepareMessage(),
26 | walletAddress: walletAddress,
27 | };
28 | };
29 |
--------------------------------------------------------------------------------
/packages/server/test/global.ts:
--------------------------------------------------------------------------------
1 | // import { use } from 'chai';
2 | // import chaiHttp from 'chai-http'
3 | // const chai = use(chaiHttp);
4 |
5 | // export const expect = chai.expect
6 | // export default chai;
7 |
--------------------------------------------------------------------------------
/packages/server/test/session.spec.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamethorg/streameth-platform/03748cd9551060f66889c1136d3693394199dd32/packages/server/test/session.spec.ts
--------------------------------------------------------------------------------
/packages/server/test/utils/generateRandomString.ts:
--------------------------------------------------------------------------------
1 | import crypto from 'crypto';
2 |
3 | export const generateRandomString = (length: number) => {
4 | return crypto
5 | .randomBytes(Math.ceil(length / 2))
6 | .toString('hex')
7 | .slice(0, length);
8 | };
9 |
--------------------------------------------------------------------------------
/packages/server/tsconfig.prod.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "sourceMap": false
5 | },
6 | "include": ["src/**/*", "workers"],
7 | "exclude": [
8 | "src/tests/*",
9 | "spec",
10 | "src/**/*.mock.ts",
11 | "src/public/",
12 | ]
13 | }
--------------------------------------------------------------------------------
/packages/server/workers/clips/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:18-alpine
2 |
3 | WORKDIR /app
4 |
5 | # Install Python and other build dependencies
6 | RUN apk add --no-cache python3 make g++
7 | RUN apk add --no-cache ffmpeg
8 |
9 | # Copy root workspace files
10 | COPY package.json yarn.lock ./
11 | COPY packages/server/package.json ./packages/server/
12 |
13 | # Install production dependencies only
14 | RUN yarn install --frozen-lockfile --production
15 |
16 | # Copy application files
17 | COPY packages/server ./packages/server
18 | RUN yarn build:server
19 |
20 | # Use production command
21 | CMD ["yarn", "start:clips"]
--------------------------------------------------------------------------------
/packages/server/workers/clips/Dockerfile.dev:
--------------------------------------------------------------------------------
1 | FROM node:18-alpine
2 |
3 | WORKDIR /app
4 |
5 | # Install Python and other build dependencies
6 | RUN apk add --no-cache python3 make g++
7 | RUN apk add --no-cache ffmpeg
8 |
9 | # Copy root workspace files
10 | COPY package.json yarn.lock ./
11 | COPY packages/server/package.json ./packages/server/
12 |
13 | # Install dependencies (including devDependencies)
14 | RUN yarn install --frozen-lockfile
15 | RUN yarn global add nodemon
16 |
17 | # Use nodemon for hot reloading
18 | CMD ["yarn", "dev:clips"]
19 |
--------------------------------------------------------------------------------
/packages/server/workers/clips/nodemon.json:
--------------------------------------------------------------------------------
1 | {
2 | "watch": [
3 | "src/workers",
4 | ".env"
5 | ],
6 | "ext": "js,ts,json",
7 | "ignore": [
8 | "src/logs/*",
9 | "src/swagger/*",
10 | "src/routes/*",
11 | "src/**/*.{spec,test}.ts"
12 | ],
13 | "exec": "node -r esbuild-runner/register ./workers/clips/index.ts --env=development",
14 | "env": {
15 | "NODE_ENV": "development"
16 | }
17 | }
--------------------------------------------------------------------------------
/packages/server/workers/session-transcriptions/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:18-alpine
2 |
3 | WORKDIR /app
4 |
5 | # Install Python and other build dependencies
6 | RUN apk add --no-cache python3 make g++
7 | RUN apk add --no-cache ffmpeg
8 |
9 | # Copy root workspace files
10 | COPY package.json yarn.lock ./
11 | COPY packages/server/package.json ./packages/server/
12 |
13 | # Install production dependencies only
14 | RUN yarn install --frozen-lockfile --production
15 |
16 | # Copy application files
17 | COPY packages/server ./packages/server
18 | RUN yarn build:server
19 | # Use production command
20 | CMD ["yarn", "start:session-transcriptions"]
21 |
--------------------------------------------------------------------------------
/packages/server/workers/session-transcriptions/Dockerfile.dev:
--------------------------------------------------------------------------------
1 | FROM node:18-alpine
2 |
3 | WORKDIR /app
4 |
5 | # Install Python and other build dependencies
6 | RUN apk add --no-cache python3 make g++
7 | RUN apk add --no-cache ffmpeg
8 |
9 | # Copy root workspace files
10 | COPY package.json yarn.lock ./
11 | COPY packages/server/package.json ./packages/server/
12 |
13 | # Install dependencies (including devDependencies)
14 | RUN yarn install --frozen-lockfile
15 | RUN yarn global add nodemon
16 |
17 | # Use nodemon for hot reloading
18 | CMD ["yarn", "dev:session-transcriptions"]
19 |
--------------------------------------------------------------------------------
/packages/server/workers/session-transcriptions/nodemon.json:
--------------------------------------------------------------------------------
1 | {
2 | "watch": [
3 | "src/workers",
4 | ".env"
5 | ],
6 | "ext": "js,ts,json",
7 | "ignore": [
8 | "src/logs/*",
9 | "src/swagger/*",
10 | "src/routes/*",
11 | "src/**/*.{spec,test}.ts"
12 | ],
13 | "exec": "node -r esbuild-runner/register ./workers/session-transcriptions/index.ts --env=development",
14 | "env": {
15 | "NODE_ENV": "development"
16 | }
17 | }
--------------------------------------------------------------------------------
/packages/server/workers/session-translations/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:18-alpine
2 |
3 | WORKDIR /app
4 |
5 | # Install Python and other build dependencies
6 | RUN apk add --no-cache python3 make g++
7 | RUN apk add --no-cache ffmpeg
8 |
9 | # Copy root workspace files
10 | COPY package.json yarn.lock ./
11 | COPY packages/server/package.json ./packages/server/
12 |
13 | # Install production dependencies only
14 | RUN yarn install --frozen-lockfile --production
15 |
16 | # Copy application files
17 | COPY packages/server ./packages/server
18 | RUN yarn build:server
19 |
20 | # Use production command
21 | CMD ["yarn", "start:session-translations"]
--------------------------------------------------------------------------------
/packages/server/workers/session-translations/Dockerfile.dev:
--------------------------------------------------------------------------------
1 | FROM node:18-alpine
2 |
3 | WORKDIR /app
4 |
5 | # Install Python and other build dependencies
6 | RUN apk add --no-cache python3 make g++
7 | RUN apk add --no-cache ffmpeg
8 |
9 | # Copy root workspace files
10 | COPY package.json yarn.lock ./
11 | COPY packages/server/package.json ./packages/server/
12 |
13 | # Install dependencies (including devDependencies)
14 | RUN yarn install --frozen-lockfile
15 | RUN yarn global add nodemon
16 |
17 | # Use nodemon for hot reloading
18 | CMD ["yarn", "dev:session-translations"]
--------------------------------------------------------------------------------
/packages/server/workers/session-translations/nodemon.json:
--------------------------------------------------------------------------------
1 | {
2 | "watch": [
3 | "src/workers",
4 | ".env"
5 | ],
6 | "ext": "js,ts,json",
7 | "ignore": [
8 | "src/logs/*",
9 | "src/swagger/*",
10 | "src/routes/*",
11 | "src/**/*.{spec,test}.ts"
12 | ],
13 | "exec": "node -r esbuild-runner/register ./workers/session-translations/index.ts --env=development",
14 | "env": {
15 | "NODE_ENV": "development"
16 | }
17 | }
--------------------------------------------------------------------------------
/packages/server/workers/stage-transcriptions/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:18-alpine
2 |
3 | WORKDIR /app
4 |
5 | # Install Python and other build dependencies
6 | RUN apk add --no-cache python3 make g++
7 | RUN apk add --no-cache ffmpeg
8 |
9 | # Copy root workspace files
10 | COPY package.json yarn.lock ./
11 | COPY packages/server/package.json ./packages/server/
12 |
13 | # Install production dependencies only
14 | RUN yarn install --frozen-lockfile --production
15 |
16 | # Copy application files
17 | COPY packages/server ./packages/server
18 | RUN yarn build:server
19 | # Use production command
20 | CMD ["yarn", "start:stage-transcriptions"]
21 |
--------------------------------------------------------------------------------
/packages/server/workers/stage-transcriptions/Dockerfile.dev:
--------------------------------------------------------------------------------
1 | FROM node:18-alpine
2 |
3 | WORKDIR /app
4 |
5 | # Install Python and other build dependencies
6 | RUN apk add --no-cache python3 make g++
7 | RUN apk add --no-cache ffmpeg
8 |
9 | # Copy root workspace files
10 | COPY package.json yarn.lock ./
11 | COPY packages/server/package.json ./packages/server/
12 |
13 | # Install dependencies (including devDependencies)
14 | RUN yarn install --frozen-lockfile
15 | RUN yarn global add nodemon
16 |
17 | # Use nodemon for hot reloading
18 | CMD ["yarn", "dev:stage-transcriptions"]
19 |
--------------------------------------------------------------------------------
/packages/server/workers/stage-transcriptions/nodemon.json:
--------------------------------------------------------------------------------
1 | {
2 | "watch": [
3 | "src/workers",
4 | ".env"
5 | ],
6 | "ext": "js,ts,json",
7 | "ignore": [
8 | "src/logs/*",
9 | "src/swagger/*",
10 | "src/routes/*",
11 | "src/**/*.{spec,test}.ts"
12 | ],
13 | "exec": "node -r esbuild-runner/register ./workers/stage-transcriptions/index.ts --env=development",
14 | "env": {
15 | "NODE_ENV": "development"
16 | }
17 | }
--------------------------------------------------------------------------------
/packages/server/workers/video-importer/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:18-alpine
2 |
3 | WORKDIR /app
4 |
5 | # Install Python and other build dependencies
6 | RUN apk add --no-cache python3 make g++
7 | RUN apk add --no-cache ffmpeg
8 |
9 | # Copy root workspace files
10 | COPY package.json yarn.lock ./
11 | COPY packages/server/package.json ./packages/server/
12 |
13 | # Install production dependencies only
14 | RUN yarn install --frozen-lockfile --production
15 |
16 | # Copy application files
17 | COPY packages/server ./packages/server
18 | RUN yarn build:server
19 |
20 | # Use production command
21 | CMD ["yarn", "start:video-importer"]
--------------------------------------------------------------------------------
/packages/server/workers/video-importer/Dockerfile.dev:
--------------------------------------------------------------------------------
1 | FROM node:18-alpine
2 |
3 | WORKDIR /app
4 |
5 | # Install Python and other build dependencies
6 | RUN apk add --no-cache python3 make g++
7 | RUN apk add --no-cache ffmpeg
8 |
9 | # Copy root workspace files
10 | COPY package.json yarn.lock ./
11 | COPY packages/server/package.json ./packages/server/
12 |
13 | # Install dependencies (including devDependencies)
14 | RUN yarn install --frozen-lockfile
15 | RUN yarn global add nodemon
16 |
17 | # Use nodemon for hot reloading
18 | CMD ["yarn", "dev:video-importer"]
19 |
--------------------------------------------------------------------------------
/packages/server/workers/video-importer/nodemon.json:
--------------------------------------------------------------------------------
1 | {
2 | "watch": [
3 | "src/workers",
4 | ".env"
5 | ],
6 | "ext": "js,ts,json",
7 | "ignore": [
8 | "src/logs/*",
9 | "src/swagger/*",
10 | "src/routes/*",
11 | "src/**/*.{spec,test}.ts"
12 | ],
13 | "exec": "node -r esbuild-runner/register ./workers/video-importer/index.ts --env=development",
14 | "env": {
15 | "NODE_ENV": "development"
16 | }
17 | }
--------------------------------------------------------------------------------
/packages/video-uploader/.gitignore:
--------------------------------------------------------------------------------
1 | .env
2 | tmp
3 | src/debug
4 | src/error
5 | dist
--------------------------------------------------------------------------------
/packages/video-uploader/Dockerfile.dev:
--------------------------------------------------------------------------------
1 | FROM node:18-slim
2 |
3 | # Install FFmpeg and other dependencies
4 | RUN apt-get update && \
5 | apt-get install -y ffmpeg && \
6 | apt-get clean && \
7 | rm -rf /var/lib/apt/lists/*
8 |
9 | # Create app directory
10 | WORKDIR /usr/src/app
11 |
12 | # Copy package files
13 | COPY package*.json ./
14 |
15 | # Install dependencies using Yarn
16 | RUN yarn install
17 |
18 | # Install TypeScript and ts-node globally
19 | RUN yarn global add typescript tsc-alias ts-node
20 |
21 | # Copy source code
22 | COPY . .
23 |
24 | # Create tmp directory for audio files
25 | RUN mkdir -p tmp
26 |
27 | # Create logs directory
28 | RUN mkdir -p /usr/src/app/logs
29 |
30 | # Build TypeScript code using Yarn
31 | RUN yarn build
32 |
33 | # Use an entrypoint script to ensure environment variables are available
34 | COPY docker-entrypoint.sh /usr/local/bin/
35 | RUN chmod +x /usr/local/bin/docker-entrypoint.sh
36 |
37 | ENTRYPOINT ["docker-entrypoint.sh"]
38 | CMD ["yarn", "audio"]
--------------------------------------------------------------------------------
/packages/video-uploader/docker-entrypoint.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | # Print environment variables for debugging (optional)
5 | # env
6 |
7 | # Execute the command passed to docker run
8 | exec "$@"
--------------------------------------------------------------------------------
/packages/video-uploader/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@streameth/video-uploader",
3 | "version": "1.0.0",
4 | "private": true,
5 | "scripts": {
6 | "video": "ts-node --esm ./src/video-consumer.ts",
7 | "audio": "ts-node ./src/audio-consumer.ts",
8 | "build": "tsc --build tsconfig.prod.json && tsc-alias -p tsconfig.prod.json",
9 | "format": "prettier --write \"src/**/*.ts\""
10 | },
11 | "license": "MIT",
12 | "devDependencies": {
13 | "@aws-sdk/client-s3": "^3.0.0",
14 | "@types/amqplib": "^0.10.5",
15 | "@types/node": "^20.14.6",
16 | "esbuild": "^0.21.5",
17 | "esbuild-runner": "^2.2.2",
18 | "prettier": "^3.3.2",
19 | "tsc-alias": "^1.0.0",
20 | "typescript": "^5.0.0",
21 | "ts-node": "^10.9.1"
22 | },
23 | "dependencies": {
24 | "@types/node-fetch": "^2.6.12",
25 | "amqplib": "^0.10.4",
26 | "dotenv": "^16.4.5",
27 | "fluent-ffmpeg": "^2.1.2",
28 | "form-data": "^4.0.1",
29 | "google-auth-library": "^9.11.0",
30 | "googleapis": "^140.0.0",
31 | "mongodb": "^6.7.0",
32 | "node-fetch": "2",
33 | "oauth-1.0a": "^2.2.6",
34 | "winston": "^3.10.0",
35 | "winston-daily-rotate-file": "^5.0.0",
36 | "streameth-new-server": "*"
37 |
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/packages/video-uploader/src/utils/errors.ts:
--------------------------------------------------------------------------------
1 | export const errorMessageToStatusCode = (message: string): number => {
2 | if (message.includes('Server returned 404 Not Found')) {
3 | return 404;
4 | }
5 | if (message === 'audio duration calculation failed') {
6 | return 400;
7 | }
8 | if (message === 'audio conversion failed') {
9 | return 400;
10 | }
11 | if (message === 'service unavailable') {
12 | return 503;
13 | }
14 | if (message === 'Unauthorized') {
15 | return 401;
16 | }
17 | return 500;
18 | };
19 |
--------------------------------------------------------------------------------
/packages/video-uploader/src/utils/mq.ts:
--------------------------------------------------------------------------------
1 | // import amqp, { Connection } from "amqplib";
2 | // import { config } from "dotenv";
3 | // import { logger } from "./logger";
4 | // config();
5 |
6 | // let connection: Connection | null = null;
7 |
8 | // export async function getConnection() {
9 | // if (connection) return connection;
10 |
11 | // try {
12 | // connection = await amqp.connect({
13 | // protocol: "amqp",
14 | // hostname: process.env.MQ_HOST,
15 | // port: Number(process.env.MQ_PORT),
16 | // username: process.env.MQ_USERNAME,
17 | // password: process.env.MQ_SECRET,
18 | // vhost: "/",
19 | // });
20 |
21 | // connection.on("error", (e) => {
22 | // logger.error("RabbitMQ connection error:", e);
23 | // connection = null; // Reset connection on error
24 | // });
25 |
26 | // connection.on("close", () => {
27 | // logger.info("RabbitMQ connection closed");
28 | // connection = null; // Reset connection when closed
29 | // });
30 |
31 | // return connection;
32 | // } catch (error) {
33 | // logger.error("Failed to connect to RabbitMQ:", error);
34 | // throw error;
35 | // }
36 | // }
37 |
38 | // export default {
39 | // getConnection
40 | // };
41 |
--------------------------------------------------------------------------------
/packages/video-uploader/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compileOnSave": false,
3 | "compilerOptions": {
4 | "target": "ES2021",
5 | "lib": ["ES2021"],
6 | "typeRoots": ["node_modules/@types"],
7 | "allowSyntheticDefaultImports": true,
8 | "experimentalDecorators": true,
9 | "emitDecoratorMetadata": true,
10 | "forceConsistentCasingInFileNames": true,
11 | "moduleResolution": "node",
12 | "module": "commonjs",
13 | "pretty": true,
14 | "sourceMap": true,
15 | "declaration": true,
16 | "allowJs": true,
17 | "noEmit": false,
18 | "esModuleInterop": true,
19 | "resolveJsonModule": true,
20 | "importHelpers": true,
21 | "baseUrl": "./",
22 | "outDir": "./dist",
23 | "paths": {}
24 | },
25 | "include": ["src/**/*"],
26 | "exclude": ["node_modules"]
27 | }
28 |
--------------------------------------------------------------------------------
/packages/video-uploader/tsconfig.prod.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "sourceMap": false
5 | }
6 | }
7 |
--------------------------------------------------------------------------------