diff --git a/.corenode b/.corenode index d92b1b00..6ad21024 100755 --- a/.corenode +++ b/.corenode @@ -1,3 +1,3 @@ { - "version": "0.31.1" + "version": "0.33.0" } diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml deleted file mode 100755 index f183adc3..00000000 --- a/.github/workflows/changelog.yml +++ /dev/null @@ -1,13 +0,0 @@ - -name: Create Changelogs -on: - push: - branches: [ master ] -jobs: - create_changelog: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - - name: Create Changelogs - uses: heineiuo/create-changelogs@v0.2.8 diff --git a/.github/workflows/trello_issue.yml b/.github/workflows/trello_issue.yml deleted file mode 100755 index 02263052..00000000 --- a/.github/workflows/trello_issue.yml +++ /dev/null @@ -1,18 +0,0 @@ -name: Trello Issue List -on: - issues: - types: [opened] -env: - TRELLO_KEY: ${{ secrets.TRELLO_KEY }} - TRELLO_TOKEN: ${{ secrets.TRELLO_TOKEN }} - -jobs: - issue_send: - name: Send Issue to Trello - runs-on: ubuntu-latest - steps: - - name: Runs trello manage - uses: sisodiya2421/trello-manage@master - with: - repo-name: Comty Development - trello-username: ${{ secrets.TRELLO_USERNAME }} diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml deleted file mode 100755 index dc7e8b50..00000000 --- a/.github/workflows/validate.yml +++ /dev/null @@ -1,17 +0,0 @@ -name: Validate code - -on: - push: - branches: - - '**' - -jobs: - test: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v1 - with: - node-version: 12 - - run: npm ci - - run: npm test \ No newline at end of file diff --git a/package.json b/package.json index f524c4b8..243c2d0b 100755 --- a/package.json +++ b/package.json @@ -22,5 +22,5 @@ "devDependencies": { "concurrently": "^7.5.0" }, - "version": "0.31.1" + "version": "0.33.0" } diff --git a/packages/app/constants/defaultSettings.json b/packages/app/constants/defaultSettings.json index 291e27d4..d0b0873d 100755 --- a/packages/app/constants/defaultSettings.json +++ b/packages/app/constants/defaultSettings.json @@ -19,7 +19,11 @@ "language": "en", "sidebarKeys": [ "home", + "tv", + "music", + "events", + "groups", "marketplace", - "groups" + "dev" ] } \ No newline at end of file diff --git a/packages/app/constants/routes.json b/packages/app/constants/routes.json index 1eeb4750..e278adf9 100755 --- a/packages/app/constants/routes.json +++ b/packages/app/constants/routes.json @@ -14,10 +14,31 @@ "reachable": true }, { - "id": "saved", - "path": "/saved", - "title": "Saved", - "icon": "Archive", + "id": "events", + "path": "/events", + "title": "Events", + "icon": "MdLocalActivity", + "reachable": true + }, + { + "id": "tv", + "path": "/tv", + "title": "Tv", + "icon": "Tv", + "reachable": true + }, + { + "id": "music", + "path": "/music", + "title": "Music", + "icon": "MdMusicVideo", + "reachable": true + }, + { + "id": "groups", + "path": "/groups", + "title": "Groups", + "icon": "Users", "reachable": true }, { @@ -28,23 +49,10 @@ "reachable": true }, { - "id": "streams", - "path": "/streams", - "title": "Streams", - "icon": "Tv", - "reachable": true - }, - { - "id": "streaming_control", - "path": "/streaming_control", - "title": "Streaming Control", - "icon": "Video" - }, - { - "id": "groups", - "path": "/groups", - "title": "Groups", - "icon": "Users", + "id": "dev", + "path": "/dev", + "title": "Development", + "icon": "MdOutlineCode", "reachable": true } ] \ No newline at end of file diff --git a/packages/app/package.json b/packages/app/package.json index c9596703..14423f06 100755 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "comty", - "version": "0.31.1", + "version": "0.33.0", "license": "LGPL-2.1", "main": "electron/main", "author": "RageStudio", diff --git a/packages/app/public/apple-touch-icon.png b/packages/app/public/apple-touch-icon.png index fd009a7b..d6a2e99f 100644 Binary files a/packages/app/public/apple-touch-icon.png and b/packages/app/public/apple-touch-icon.png differ diff --git a/packages/app/public/favicon.ico b/packages/app/public/favicon.ico index 73adbb34..2156b823 100644 Binary files a/packages/app/public/favicon.ico and b/packages/app/public/favicon.ico differ diff --git a/packages/app/public/icon-192-maskable.png b/packages/app/public/icon-192-maskable.png index ecb54e31..851de9d6 100644 Binary files a/packages/app/public/icon-192-maskable.png and b/packages/app/public/icon-192-maskable.png differ diff --git a/packages/app/public/icon-192.png b/packages/app/public/icon-192.png index ab0a6bab..6f91de5b 100644 Binary files a/packages/app/public/icon-192.png and b/packages/app/public/icon-192.png differ diff --git a/packages/app/public/icon-512-maskable.png b/packages/app/public/icon-512-maskable.png index 10ac0b7c..5c7dbf11 100644 Binary files a/packages/app/public/icon-512-maskable.png and b/packages/app/public/icon-512-maskable.png differ diff --git a/packages/app/public/icon-512.png b/packages/app/public/icon-512.png index 5935d10c..8e57eb15 100644 Binary files a/packages/app/public/icon-512.png and b/packages/app/public/icon-512.png differ diff --git a/packages/app/src/components/UserRegister/index.jsx b/packages/app/src/components/UserRegister/index.jsx index e1ac3400..45c59e7c 100755 --- a/packages/app/src/components/UserRegister/index.jsx +++ b/packages/app/src/components/UserRegister/index.jsx @@ -85,7 +85,10 @@ const steps = [ setValidCharacters(hasValidCharacters(username)) const timer = setTimeout(async () => { - if (!validCharacters) return + if (!validCharacters) { + setLoading(false) + return + } const request = await app.api.customRequest("main", { method: "GET", @@ -120,7 +123,7 @@ const steps = [ autoCorrect="off" autoCapitalize="none" onPressEnter={submit} - placeholder="@newuser" + placeholder="newuser" value={username} onChange={handleUpdate} status={username.length == 0 ? "default" : loading ? "default" : (isValid() ? "success" : "error")} diff --git a/packages/app/src/models/livestream/index.js b/packages/app/src/models/livestream/index.js index ce658117..228a3a69 100644 --- a/packages/app/src/models/livestream/index.js +++ b/packages/app/src/models/livestream/index.js @@ -27,6 +27,12 @@ export default class Livestream { return data } + static async getCategories() { + const request = await Livestream.bridge.get.streamingCategories() + + return request + } + static async getStreamInfo(payload) { let { username } = payload ?? {} diff --git a/packages/app/src/pages/home/[type].jsx b/packages/app/src/pages/home/[type].jsx index e8edf9c4..2b3825b4 100755 --- a/packages/app/src/pages/home/[type].jsx +++ b/packages/app/src/pages/home/[type].jsx @@ -6,36 +6,10 @@ import { Icons, createIconRender } from "components/Icons" import { HashtagTrendings, FeaturedEventsAnnouncements, ConnectedFriends } from "components" -import FeedBrowser from "./components/feed" -import ExploreBrowser from "./components/explore" -import LivestreamsBrowser from "./components/livestreams" -import SavedPostsBrowser from "./components/savedPosts" +import Tabs from "./tabs" import "./index.less" -const Tabs = { - "feed": { - title: "Feed", - icon: "Rss", - component: FeedBrowser - }, - "explore": { - title: "Explore", - icon: "Search", - component: ExploreBrowser - }, - "savedPosts": { - title: "Saved posts", - icon: "Bookmark", - component: SavedPostsBrowser - }, - "livestreams": { - title: "Livestreams", - icon: "Tv", - component: LivestreamsBrowser - }, -} - export default class Dashboard extends React.Component { state = { activeTab: this.props.match.params.type ?? "feed" @@ -80,7 +54,7 @@ export default class Dashboard extends React.Component { } render() { - return
+ return
+ return
{ + return
+ +
+} \ No newline at end of file diff --git a/packages/app/src/pages/home/components/trendings/index.less b/packages/app/src/pages/home/components/trendings/index.less new file mode 100644 index 00000000..db49f864 --- /dev/null +++ b/packages/app/src/pages/home/components/trendings/index.less @@ -0,0 +1 @@ +.trendingsBrowser {} \ No newline at end of file diff --git a/packages/app/src/pages/home/index.jsx b/packages/app/src/pages/home/index.jsx index 138fffe9..0c9a5808 100644 --- a/packages/app/src/pages/home/index.jsx +++ b/packages/app/src/pages/home/index.jsx @@ -1,7 +1,7 @@ import React from "react" export default () => { - app.setLocation("home/feed") + app.setLocation("/home/feed") return <> } \ No newline at end of file diff --git a/packages/app/src/pages/home/index.less b/packages/app/src/pages/home/index.less index 95c3abdc..66c22bab 100755 --- a/packages/app/src/pages/home/index.less +++ b/packages/app/src/pages/home/index.less @@ -1,4 +1,4 @@ -.dashboard { +.postingDashboard { display: grid; grid-template-columns: 10vw 1fr 0.5fr; diff --git a/packages/app/src/pages/home/tabs.jsx b/packages/app/src/pages/home/tabs.jsx new file mode 100644 index 00000000..16b19096 --- /dev/null +++ b/packages/app/src/pages/home/tabs.jsx @@ -0,0 +1,27 @@ +import FeedTab from "./components/feed" +import ExploreTab from "./components/explore" +import TrendingsTab from "./components/trendings" +import SavedPostsTab from "./components/savedPosts" + +export default { + "feed": { + title: "Feed", + icon: "Rss", + component: FeedTab + }, + "explore": { + title: "Explore", + icon: "Search", + component: ExploreTab + }, + "trendings": { + title: "Trendings", + icon: "TrendingUp", + component: TrendingsTab + }, + "savedPosts": { + title: "Saved posts", + icon: "Bookmark", + component: SavedPostsTab + } +} \ No newline at end of file diff --git a/packages/app/src/pages/tv/[type].jsx b/packages/app/src/pages/tv/[type].jsx new file mode 100644 index 00000000..709efe9c --- /dev/null +++ b/packages/app/src/pages/tv/[type].jsx @@ -0,0 +1,87 @@ +import React from "react" +import * as antd from "antd" +import classnames from "classnames" + +import { Icons, createIconRender } from "components/Icons" + +import Tabs from "./tabs" + +import "./index.less" + +export default class TVDashboard extends React.Component { + state = { + activeTab: this.props.match.params.type ?? "feed" + } + + primaryPanelRef = React.createRef() + + componentDidMount() { + app.eventBus.emit("style.compactMode", false) + } + + renderActiveTab() { + const tab = Tabs[this.state.activeTab] + + if (!tab) { + return + } + + return React.createElement(tab.component) + } + + handleTabChange = (key) => { + if (this.state.activeTab === key) return + + // set to primary panel fade-opacity-leave class + this.primaryPanelRef.current.classList.add("fade-opacity-leave") + + setTimeout(() => { + this.setState({ activeTab: key }) + // update location + app.history.replace(key) + }, 200) + + // remove fade-opacity-leave class after animation + setTimeout(() => { + this.primaryPanelRef.current.classList.remove("fade-opacity-leave") + }, 300) + } + + render() { + return
+
+ {this.renderActiveTab()} +
+ +
+
+

TV

+ this.handleTabChange(key)} + > + {Object.keys(Tabs).map((key) => { + const tab = Tabs[key] + + return + {tab.title} + + })} + +
+
+
+ } +} \ No newline at end of file diff --git a/packages/app/src/pages/live_control/index.jsx b/packages/app/src/pages/tv/components/controlPanel/index.jsx similarity index 61% rename from packages/app/src/pages/live_control/index.jsx rename to packages/app/src/pages/tv/components/controlPanel/index.jsx index c307e7f9..d1dbd192 100644 --- a/packages/app/src/pages/live_control/index.jsx +++ b/packages/app/src/pages/tv/components/controlPanel/index.jsx @@ -3,7 +3,7 @@ import * as antd from "antd" import { Icons } from "components/Icons" -import Livestream from "../../models/livestream" +import Livestream from "../../../../models/livestream" import "./index.less" @@ -32,6 +32,131 @@ const StreamingKeyView = (props) => {
} +const LivestreamsCategoriesSelector = (props) => { + const [categories, setCategories] = React.useState([]) + const [loading, setLoading] = React.useState(true) + + const loadData = async () => { + setLoading(true) + + const categories = await Livestream.getCategories().catch((err) => { + console.error(err) + + app.message.error("Failed to load categories") + + return null + }) + + console.log(`Loaded categories >`, categories) + + setLoading(false) + + if (categories) { + setCategories(categories) + } + } + + React.useEffect(() => { + loadData() + }, []) + + if (loading) { + return + } + + return props.updateStreamInfo("category", value)} + > + { + categories.map((category) => { + return {category?.label ?? "No category"} + }) + } + +} + +const StreamInfoEditor = (props) => { + const [streamInfo, setStreamInfo] = React.useState(props.defaultStreamInfo ?? {}) + + const updateStreamInfo = (key, value) => { + setStreamInfo({ + ...streamInfo, + [key]: value, + }) + } + + const saveStreamInfo = async () => { + if (typeof props.onSave === "function") { + return await props.onSave(streamInfo) + } + + // peform default save + const result = await Livestream.updateLivestreamInfo(streamInfo).catch((err) => { + console.error(err) + + app.message.error("Failed to update stream info") + + return false + }) + + if (result) { + app.message.success("Stream info updated") + } + + if (typeof props.onSaveComplete === "function") { + await props.onSaveComplete(result) + } + + return result + } + + return
+
+ + Title + +
+ updateStreamInfo("title", e.target.value)} + /> +
+
+
+ + Description + +
+ updateStreamInfo("description", e.target.value)} + /> +
+
+
+ + Category + +
+ +
+
+ + Save + +
+} + export default (props) => { const [streamInfo, setStreamInfo] = React.useState({}) const [addresses, setAddresses] = React.useState({}) @@ -39,6 +164,19 @@ export default (props) => { const [isConnected, setIsConnected] = React.useState(false) const [streamingKey, setStreamingKey] = React.useState(null) + const onClickEditInfo = () => { + app.ModalController.open(() => { + if (result) { + app.ModalController.close() + + fetchStreamInfo() + } + }} + />) + } + const regenerateStreamingKey = async () => { antd.Modal.confirm({ title: "Regenerate streaming key", @@ -125,15 +263,35 @@ export default (props) => {
+
+ + Description + + +

+ {streamInfo?.description ?? "No description"} +

+
+
Category

- {streamInfo?.category ?? "No category"} + {streamInfo?.category?.label ?? "No category"}

+ +
+ } + onClick={onClickEditInfo} + > + Edit info + +
diff --git a/packages/app/src/pages/live_control/index.less b/packages/app/src/pages/tv/components/controlPanel/index.less similarity index 82% rename from packages/app/src/pages/live_control/index.less rename to packages/app/src/pages/tv/components/controlPanel/index.less index ce9dda92..e73da2bb 100644 --- a/packages/app/src/pages/live_control/index.less +++ b/packages/app/src/pages/tv/components/controlPanel/index.less @@ -2,13 +2,15 @@ display: flex; flex-direction: column; + width: 100%; + transition: all 0.3s ease-in-out; .header { display: flex; flex-direction: row; - height: 20vh; + height: fit-content; padding: 15px; @@ -21,7 +23,7 @@ .preview { height: 100%; - max-width: 400px; + width: 300px; img { width: 100%; @@ -38,6 +40,7 @@ flex-direction: column; padding: 20px 0; + width: 100%; .status { margin-bottom: 20px; @@ -47,6 +50,7 @@ .config { display: flex; + flex-direction: column; padding: 0 40px; @@ -83,7 +87,7 @@ .title { display: inline-flex; flex-direction: row; - + justify-content: space-between; align-items: center; @@ -114,8 +118,29 @@ div { display: inline-flex; flex-direction: row; - + align-items: center; justify-content: center; } +} + +.streamInfoEditor { + display: flex; + flex-direction: column; + + .field { + display: flex; + flex-direction: column; + + margin-bottom: 20px; + + .value { + margin-top: 5px; + margin-left: 20px; + + .ant-select { + min-width: 200px; + } + } + } } \ No newline at end of file diff --git a/packages/app/src/pages/home/components/livestreams/index.jsx b/packages/app/src/pages/tv/components/explore/index.jsx similarity index 85% rename from packages/app/src/pages/home/components/livestreams/index.jsx rename to packages/app/src/pages/tv/components/explore/index.jsx index 7691ee4d..9f6f0bc1 100644 --- a/packages/app/src/pages/home/components/livestreams/index.jsx +++ b/packages/app/src/pages/tv/components/explore/index.jsx @@ -1,11 +1,10 @@ import React from "react" +import Livestream from "models/livestream" import * as antd from "antd" import { UserPreview } from "components" import { Icons } from "components/Icons" -import Livestream from "../../../../models/livestream" - import "./index.less" const LivestreamItem = (props) => { @@ -34,7 +33,7 @@ const LivestreamItem = (props) => {

{livestream.info?.description ?? "No description"}

- {livestream.info?.catagory ?? "No category"} + {livestream.info?.category?.label ?? "No category"}
@@ -71,10 +70,6 @@ export default (props) => { app.setLocation(`/live/${livestream.username}`) } - const onClickControlPanel = () => { - app.setLocation("/live_control") - } - const renderList = () => { if (loading) { return @@ -105,19 +100,10 @@ export default (props) => { Livestreams - -
- } - onClick={onClickControlPanel} - > - Control Panel - -
{renderList()}
-} +} \ No newline at end of file diff --git a/packages/app/src/pages/home/components/livestreams/index.less b/packages/app/src/pages/tv/components/explore/index.less similarity index 100% rename from packages/app/src/pages/home/components/livestreams/index.less rename to packages/app/src/pages/tv/components/explore/index.less diff --git a/packages/app/src/pages/tv/components/feed/index.jsx b/packages/app/src/pages/tv/components/feed/index.jsx new file mode 100644 index 00000000..3dce8530 --- /dev/null +++ b/packages/app/src/pages/tv/components/feed/index.jsx @@ -0,0 +1,14 @@ +import React from "react" +import { Result } from "antd" + +import "./index.less" + +export default (props) => { + return
+ +
+} \ No newline at end of file diff --git a/packages/app/src/pages/tv/components/feed/index.less b/packages/app/src/pages/tv/components/feed/index.less new file mode 100644 index 00000000..112802cc --- /dev/null +++ b/packages/app/src/pages/tv/components/feed/index.less @@ -0,0 +1 @@ +.livestreamsFeed {} \ No newline at end of file diff --git a/packages/app/src/pages/tv/index.jsx b/packages/app/src/pages/tv/index.jsx new file mode 100644 index 00000000..76411c7b --- /dev/null +++ b/packages/app/src/pages/tv/index.jsx @@ -0,0 +1,7 @@ +import React from "react" + +export default () => { + app.setLocation("/tv/feed") + + return <> +} \ No newline at end of file diff --git a/packages/app/src/pages/tv/index.less b/packages/app/src/pages/tv/index.less new file mode 100644 index 00000000..5889fc82 --- /dev/null +++ b/packages/app/src/pages/tv/index.less @@ -0,0 +1,47 @@ +.tvDashboard { + display: grid; + + grid-template-columns: 3fr 1fr; + grid-template-rows: 1fr; + grid-column-gap: 10px; + grid-row-gap: 0px; + + width: 100%; + + padding-left: 30px; + + .panel { + position: sticky; + top: 0; + + height: fit-content; + + display: flex; + flex-direction: column; + + align-items: center; + + >div { + margin-bottom: 15px; + } + + .card { + background-color: var(--background-color-accent); + border-radius: 12px; + padding: 20px; + + min-width: 20vw; + + h1, + h2 { + font-family: "Space Grotesk", sans-serif; + } + } + } + + .ant-menu { + svg { + margin-right: 0 !important; + } + } +} \ No newline at end of file diff --git a/packages/app/src/pages/tv/tabs.jsx b/packages/app/src/pages/tv/tabs.jsx new file mode 100644 index 00000000..9c2fb8d3 --- /dev/null +++ b/packages/app/src/pages/tv/tabs.jsx @@ -0,0 +1,21 @@ +import FeedTab from "./components/feed" +import ExploreTab from "./components/explore" +import ControlPanelTab from "./components/controlPanel" + +export default { + "feed": { + title: "Feed", + icon: "Rss", + component: FeedTab + }, + "explore": { + title: "Explore", + icon: "Search", + component: ExploreTab + }, + "controlPanel": { + title: "Control Panel", + icon: "Settings", + component: ControlPanelTab + } +} \ No newline at end of file diff --git a/packages/server/package.json b/packages/server/package.json index d3ef419f..e110b4ad 100755 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "@comty/server", - "version": "0.31.1", + "version": "0.33.0", "main": "dist/index.js", "scripts": { "build": "corenode-cli build", diff --git a/packages/server/src/controllers/StreamingController/index.js b/packages/server/src/controllers/StreamingController/index.js index 9e9bc3e6..66a42c0d 100755 --- a/packages/server/src/controllers/StreamingController/index.js +++ b/packages/server/src/controllers/StreamingController/index.js @@ -3,19 +3,17 @@ import { nanoid } from "nanoid" import lodash from "lodash" import axios from "axios" -import { User, StreamingKey, StreamingInfo } from "../../models" +import { Schematized } from "../../lib" +import { User, StreamingKey, StreamingInfo, StreamingCategory } from "../../models" -const streamingIngestServer = process.env.STREAMING_INGEST_SERVER -const streamingServerAPIAddress = process.env.STREAMING_API_SERVER -const streamingServerAPIProtocol = streamingServerAPIAddress.startsWith("https") ? "https" : "http" +const streamingIngestServer = process.env.STREAMING_INGEST_SERVER ?? "" +const streamingServerAPIAddress = process.env.STREAMING_API_SERVER ?? "" -const streamingServerAPIUri = `${streamingServerAPIProtocol}://${streamingServerAPIAddress.split("://")[1]}` +const streamingServerAPIUri = `${streamingServerAPIAddress.startsWith("https") ? "https" : "http"}://${streamingServerAPIAddress.split("://")[1]}` const FILTER_KEYS = ["stream"] export default class StreamingController extends Controller { - streamings = [] - methods = { genereteKey: async (user_id) => { // this will generate a new key for the user @@ -34,78 +32,61 @@ export default class StreamingController extends Controller { return streamingKey }, - regenerateStreamingList: async () => { + fetchStreams: async () => { // fetch all streams from api - let streams = await axios.get(`${streamingServerAPIUri}/api/v1/streams`).catch((err) => { - console.log(err) + let { data } = await axios.get(`${streamingServerAPIUri}/api/v1/streams`).catch((err) => { + console.error(err) return false }) - if (streams) { - streams = streams.data.streams + let streamings = [] - // FIXME: this method is not totally async - streams.forEach((stream) => { - // check if the stream is already in the list - const streamInList = this.streamings.find((s) => s.stream === stream.name) + if (!data) return streamings - if (!streamInList) { - // if not, add it - this.methods.pushToLocalList({ - stream: stream.name, - app: stream.app, - }).catch((err) => { - // sorry for you - }) - } + streamings = data.streams + + streamings = streamings.map(async (stream) => { + stream = await this.methods.generateStreamFromStreamkey(stream.name) + + let info = await StreamingInfo.findOne({ + user_id: stream.user_id }) - } - }, - pushToLocalList: async (payload) => { - const { stream, app } = payload - const username = app.split("/")[1] - const user_id = await User.findOne({ username }).then((user) => user._id) + if (info) { + stream.info = info.toObject() - const streamingKey = await StreamingKey.findOne({ - key: stream + stream.info.category = await StreamingCategory.findOne({ + key: stream.info.category + }) + } + + return stream }) - if (!streamingKey) { - throw new Error("Invalid streaming key") - } + streamings = await Promise.all(streamings) - if (username !== streamingKey.username) { - throw new Error("Invalid streaming key for this username") - } + return streamings.map((stream) => { + return lodash.omit(stream, FILTER_KEYS) + }) + }, + generateStreamFromStreamkey: async (streamKey) => { + // generate a stream from a streamkey + const streamingKey = await StreamingKey.findOne({ + key: streamKey + }) + + if (!streamingKey) return false const streaming = { - stream, - user_id: user_id.toString(), + user_id: streamingKey.user_id, username: streamingKey.username, sources: { - rtmp: `${streamingIngestServer}/live/${username}`, - hls: `${streamingServerAPIAddress}/live/${username}/src.m3u8`, - flv: `${streamingServerAPIAddress}/live/${username}/src.flv`, + rtmp: `${streamingIngestServer}/live/${streamingKey.username}`, + hls: `${streamingServerAPIAddress}/live/${streamingKey.username}/src.m3u8`, + flv: `${streamingServerAPIAddress}/live/${streamingKey.username}/src.flv`, } } - this.streamings.push(streaming) - - return streaming - }, - removeFromLocalList: async (payload) => { - const { stream } = payload - - // remove from streamings array - const streaming = this.streamings.find((streaming) => streaming.stream === stream) - - if (!streaming) { - throw new Error("Stream not found") - } - - this.streamings = this.streamings.filter((streaming) => streaming.stream !== stream) - return streaming }, handleInfoUpdate: async (payload) => { @@ -147,28 +128,15 @@ export default class StreamingController extends Controller { } get = { + "/streaming/categories": async (req, res) => { + const categories = await StreamingCategory.find() + + return res.json(categories) + }, "/streams": async (req, res) => { - await this.methods.regenerateStreamingList() + const remoteStreams = await this.methods.fetchStreams() - let data = this.streamings.map((stream) => { - return lodash.omit(stream, FILTER_KEYS) - }) - - data = data.map(async (stream) => { - let info = await StreamingInfo.findOne({ - user_id: stream.user_id - }) - - if (info) { - stream.info = info.toObject() - } - - return stream - }) - - data = await Promise.all(data) - - return res.json(data) + return res.json(remoteStreams) }, "/stream/info": { middleware: ["withAuthentication"], @@ -189,11 +157,32 @@ export default class StreamingController extends Controller { user_id = user_id["_id"].toString() } - const info = await StreamingInfo.findOne({ + let info = await StreamingInfo.findOne({ user_id, }) - return res.json(info) + if (!info) { + info = new StreamingInfo({ + user_id, + }) + + await info.save() + } + + const category = await StreamingCategory.findOne({ + key: info.category + }).catch((err) => { + console.error(err) + return {} + }) ?? {} + + return res.json({ + ...info.toObject(), + ["category"]: { + key: category?.key ?? "unknown", + label: category?.label ?? "Unknown", + } + }) } }, "/streaming/addresses": { @@ -218,8 +207,10 @@ export default class StreamingController extends Controller { "/streaming/:username": async (req, res) => { const { username } = req.params + const streamings = await this.methods.fetchStreams() + // search on this.streamings - const streaming = this.streamings.find((streaming) => streaming.username === username) + const streaming = streamings.find((streaming) => streaming.username === username) if (streaming) { return res.json(lodash.omit(streaming, FILTER_KEYS)) @@ -257,6 +248,36 @@ export default class StreamingController extends Controller { }, } + put = { + "/streaming/category": { + middlewares: ["withAuthentication", "onlyAdmin"], + fn: Schematized({ + required: ["key", "label"] + }, async (req, res) => { + const { key, label } = req.selection + + const existingCategory = await StreamingCategory.findOne({ + key + }) + + if (existingCategory) { + return res.status(400).json({ + error: "Category already exists" + }) + } + + const category = new StreamingCategory({ + key, + label, + }) + + await category.save() + + return res.json(category) + }) + } + } + post = { "/streaming/update_info": { middlewares: ["withAuthentication"], @@ -285,25 +306,22 @@ export default class StreamingController extends Controller { } }, "/streaming/publish": async (req, res) => { - const { app, stream, tcUrl } = req.body + const { stream } = req.body + + const streaming = await this.methods.generateStreamFromStreamkey(stream).catch((err) => { + console.error(err) - const streaming = await this.methods.pushToLocalList({ - app, - stream, - tcUrl - }).catch((err) => { res.status(500).json({ - code: 1, - error: err.message + error: `Cannot generate stream: ${err.message}`, }) - return false + return null }) if (streaming) { - global.wsInterface.io.emit(`streaming.new`, { - username: streaming.username, - }) + global.wsInterface.io.emit(`streaming.new`, streaming) + + global.wsInterface.io.emit(`streaming.new.${streaming.username}`, streaming) return res.json({ code: 0, @@ -314,21 +332,16 @@ export default class StreamingController extends Controller { "/streaming/unpublish": async (req, res) => { const { stream } = req.body - const streaming = await this.methods.removeFromLocalList({ - stream - }).catch((err) => { - res.status(500).json({ - code: 2, - status: err.message - }) + const streaming = await this.methods.generateStreamFromStreamkey(stream).catch((err) => { + console.error(err) - return false + return null }) if (streaming) { - global.wsInterface.io.emit(`streaming.end`, { - username: streaming.username, - }) + global.wsInterface.io.emit(`streaming.end`, streaming) + + global.wsInterface.io.emit(`streaming.end.${streaming.username}`, streaming) return res.json({ code: 0, diff --git a/packages/server/src/models/index.js b/packages/server/src/models/index.js index 5e0978a8..0462d351 100755 --- a/packages/server/src/models/index.js +++ b/packages/server/src/models/index.js @@ -37,6 +37,7 @@ export const Playlist = mongoose.model("Playlist", schemas.Playlist, "playlists" // streamings export const StreamingKey = mongoose.model("StreamingKey", schemas.StreamingKey, "streamingKeys") export const StreamingInfo = mongoose.model("StreamingInfo", schemas.StreamingInfo, "streamingInfos") +export const StreamingCategory = mongoose.model("StreamingCategory", schemas.StreamingCategory, "streamingCategories") // others export const FeaturedWallpaper = mongoose.model("FeaturedWallpaper", schemas.FeaturedWallpaper, "featuredWallpapers") diff --git a/packages/server/src/schemas/index.js b/packages/server/src/schemas/index.js index f76ebf53..47b3f69a 100755 --- a/packages/server/src/schemas/index.js +++ b/packages/server/src/schemas/index.js @@ -18,4 +18,5 @@ export { default as FeaturedWallpaper } from "./featuredWallpaper" export { default as FeaturedEvent } from "./featuredEvent" export { default as StreamingKey } from "./streamingKey" -export { default as StreamingInfo } from "./streamingInfo" \ No newline at end of file +export { default as StreamingInfo } from "./streamingInfo" +export { default as StreamingCategory } from "./streamingCategory" \ No newline at end of file diff --git a/packages/server/src/schemas/streamingCategory/index.js b/packages/server/src/schemas/streamingCategory/index.js new file mode 100644 index 00000000..5a3d7beb --- /dev/null +++ b/packages/server/src/schemas/streamingCategory/index.js @@ -0,0 +1,10 @@ +export default { + key: { + type: String, + required: true, + }, + label: { + type: String, + required: true, + }, +} \ No newline at end of file diff --git a/packages/wrapper/package.json b/packages/wrapper/package.json index e982c218..3fe3d881 100755 --- a/packages/wrapper/package.json +++ b/packages/wrapper/package.json @@ -1,6 +1,6 @@ { "name": "wrapper", - "version": "0.31.1", + "version": "0.33.0", "main": "./src/index.js", "license": "MIT", "scripts": {