From 06aee646cae9c98b0335b07d1334d3a68e5cfae9 Mon Sep 17 00:00:00 2001
From: SrGooglo <srgooglo@ragestudio.net>
Date: Mon, 15 Apr 2024 16:29:22 +0000
Subject: [PATCH] merge from local

---
 packages/app/constants/routes.js              |   5 +
 packages/app/src/components/Login/index.jsx   |  14 +-
 .../app/src/components/PagePanels/index.jsx   |   2 +-
 packages/app/src/cores/player/player.core.js  |  10 +-
 packages/app/src/cores/player/services.js     |  13 +-
 .../music/components/dashboard/index.jsx      |   7 -
 .../pages/music/components/library/index.jsx  |  38 +-
 .../pages/music/components/library/index.less |  14 +
 .../app/src/pages/music/dashboard/index.jsx   |  15 +
 .../app/src/pages/music/dashboard/index.less  |   8 +
 .../dashboard/releases/index.jsx              |   2 +-
 .../dashboard/releases/index.less             |   0
 packages/app/src/pages/music/tabs.jsx         |  17 -
 .../pages/music/track/[track_id]/index.jsx    |  40 ++
 .../pages/music/track/[track_id]/index.less   |   6 +
 packages/app/src/pages/violation/index.jsx    |   9 +
 packages/server/classes/AuthToken/index.js    |  17 +-
 .../server/classes/ChunkFileUpload/index.js   |   2 +-
 .../server/db_models/tosViolations/index.js   |  17 +
 packages/server/db_models/track/index.js      |   8 +-
 .../middlewares/withAuthentication/index.js   |  35 +-
 .../server/services/auth/routes/auth/post.js  |  11 +-
 .../services/music/classes/Room/index.js      | 345 ------------------
 .../music/classes/RoomsController/index.js    |  99 -----
 .../services/music/classes/track/index.js     |   5 +
 .../music/classes/track/methods/create.js     |  84 +++++
 .../music/classes/track/methods/delete.js     |   9 +
 .../music/classes/track/methods/get.js        |  30 ++
 packages/server/services/music/package.json   |   3 +-
 .../music/tracks/[track_id]/data/get.js       |  11 +
 .../routes/music/tracks/[track_id]/delete.js  |  21 ++
 .../services/music/routes/music/tracks/put.js |  16 +
 .../endpoints/deleteStreamingProfile.js       |  42 ---
 .../endpoints/getProfileFromStreamKey.js      |  19 -
 .../endpoints/getProfilesVisibility.js        |  24 --
 .../endpoints/getStreamingCategories.js       |  17 -
 .../endpoints/getStreamingProfiles.js         |  50 ---
 .../endpoints/getStreams.js                   |  23 --
 .../endpoints/handleStreamPublish.js          |  58 ---
 .../endpoints/handleStreamUnpublish.js        |  29 --
 .../endpoints/postStreamingProfile.js         |  64 ----
 .../endpoints/regenerateStreamingKey.js       |  37 --
 .../services/tv/StreamingController/index.js  |  39 --
 43 files changed, 409 insertions(+), 906 deletions(-)
 delete mode 100755 packages/app/src/pages/music/components/dashboard/index.jsx
 create mode 100755 packages/app/src/pages/music/dashboard/index.jsx
 create mode 100644 packages/app/src/pages/music/dashboard/index.less
 rename packages/app/src/pages/music/{components => }/dashboard/releases/index.jsx (99%)
 rename packages/app/src/pages/music/{components => }/dashboard/releases/index.less (100%)
 create mode 100644 packages/app/src/pages/music/track/[track_id]/index.jsx
 create mode 100644 packages/app/src/pages/music/track/[track_id]/index.less
 create mode 100644 packages/app/src/pages/violation/index.jsx
 create mode 100644 packages/server/db_models/tosViolations/index.js
 delete mode 100755 packages/server/services/music/classes/Room/index.js
 delete mode 100755 packages/server/services/music/classes/RoomsController/index.js
 create mode 100644 packages/server/services/music/classes/track/index.js
 create mode 100644 packages/server/services/music/classes/track/methods/create.js
 create mode 100644 packages/server/services/music/classes/track/methods/delete.js
 create mode 100644 packages/server/services/music/classes/track/methods/get.js
 create mode 100644 packages/server/services/music/routes/music/tracks/[track_id]/data/get.js
 create mode 100644 packages/server/services/music/routes/music/tracks/[track_id]/delete.js
 create mode 100644 packages/server/services/music/routes/music/tracks/put.js
 delete mode 100755 packages/server/services/tv/StreamingController/endpoints/deleteStreamingProfile.js
 delete mode 100755 packages/server/services/tv/StreamingController/endpoints/getProfileFromStreamKey.js
 delete mode 100755 packages/server/services/tv/StreamingController/endpoints/getProfilesVisibility.js
 delete mode 100755 packages/server/services/tv/StreamingController/endpoints/getStreamingCategories.js
 delete mode 100755 packages/server/services/tv/StreamingController/endpoints/getStreamingProfiles.js
 delete mode 100755 packages/server/services/tv/StreamingController/endpoints/getStreams.js
 delete mode 100755 packages/server/services/tv/StreamingController/endpoints/handleStreamPublish.js
 delete mode 100755 packages/server/services/tv/StreamingController/endpoints/handleStreamUnpublish.js
 delete mode 100755 packages/server/services/tv/StreamingController/endpoints/postStreamingProfile.js
 delete mode 100755 packages/server/services/tv/StreamingController/endpoints/regenerateStreamingKey.js
 delete mode 100755 packages/server/services/tv/StreamingController/index.js

diff --git a/packages/app/constants/routes.js b/packages/app/constants/routes.js
index 6e223bfa..2980f84f 100755
--- a/packages/app/constants/routes.js
+++ b/packages/app/constants/routes.js
@@ -88,6 +88,11 @@ export default [
         centeredContent: true,
         extendedContent: true,
     },
+    {
+        path: "/violation",
+        useLayout: "minimal",
+        public: true,
+    },
     // THIS MUST BE THE LAST ROUTE
     {
         path: "/",
diff --git a/packages/app/src/components/Login/index.jsx b/packages/app/src/components/Login/index.jsx
index 6e59ad78..ba7ffceb 100755
--- a/packages/app/src/components/Login/index.jsx
+++ b/packages/app/src/components/Login/index.jsx
@@ -40,7 +40,7 @@ export default class Login extends React.Component {
         loginInputs: {},
         error: null,
         phase: 0,
-        mfa_required: null
+        mfa_required: null,
     }
 
     formRef = React.createRef()
@@ -60,6 +60,18 @@ export default class Login extends React.Component {
         this.toggleLoading(true)
 
         await AuthModel.login(payload, this.onDone).catch((error) => {
+            if (error.response.data){
+                if (error.response.data.violation) {
+                    this.props.close({
+                        unlock: true
+                    })
+
+                    return app.history.push("/violation", {
+                        violation: error.response.data.violation
+                    })
+                }
+            }
+
             console.error(error, error.response)
 
             this.toggleLoading(false)
diff --git a/packages/app/src/components/PagePanels/index.jsx b/packages/app/src/components/PagePanels/index.jsx
index d31e8c5e..8c256bb1 100755
--- a/packages/app/src/components/PagePanels/index.jsx
+++ b/packages/app/src/components/PagePanels/index.jsx
@@ -23,7 +23,7 @@ export const Panel = (props) => {
 
 export class PagePanelWithNavMenu extends React.Component {
     state = {
-        activeTab: this.props.defaultTab ?? new URLSearchParams(window.location.search).get("type") ?? this.props.tabs[0].key,
+        activeTab: new URLSearchParams(window.location.search).get("type") ?? this.props.defaultTab ?? this.props.tabs[0].key,
         renders: [],
     }
 
diff --git a/packages/app/src/cores/player/player.core.js b/packages/app/src/cores/player/player.core.js
index 9f0cf305..adde1603 100755
--- a/packages/app/src/cores/player/player.core.js
+++ b/packages/app/src/cores/player/player.core.js
@@ -115,16 +115,16 @@ export default class Player extends Core {
 
     internalEvents = {
         "player.state.update:loading": () => {
-            app.cores.sync.music.dispatchEvent("music.player.state.update", this.state)
+            //app.cores.sync.music.dispatchEvent("music.player.state.update", this.state)
         },
         "player.state.update:track_manifest": () => {
-            app.cores.sync.music.dispatchEvent("music.player.state.update", this.state)
+            //app.cores.sync.music.dispatchEvent("music.player.state.update", this.state)
         },
         "player.state.update:playback_status": () => {
-            app.cores.sync.music.dispatchEvent("music.player.state.update", this.state)
+            //app.cores.sync.music.dispatchEvent("music.player.state.update", this.state)
         },
         "player.seeked": (to) => {
-            app.cores.sync.music.dispatchEvent("music.player.seek", to)
+            //app.cores.sync.music.dispatchEvent("music.player.seek", to)
         },
     }
 
@@ -587,7 +587,7 @@ export default class Player extends Core {
 
             if (playlist.some((item) => typeof item === "string")) {
                 this.console.log("Resolving missing manifests by ids...")
-                playlist = await this.getTracksByIds(playlist)
+                playlist = await ServicesHandlers.default.resolveMany(playlist)
             }
 
             playlist = playlist.slice(startIndex)
diff --git a/packages/app/src/cores/player/services.js b/packages/app/src/cores/player/services.js
index e275cb88..5d7e5d8e 100755
--- a/packages/app/src/cores/player/services.js
+++ b/packages/app/src/cores/player/services.js
@@ -3,7 +3,18 @@ import MusicModel from "comty.js/models/music"
 
 export default {
     "default": {
-        resolve: () => { },
+        resolve: async (track_id) => {
+            return await MusicModel.getTrackData(track_id)
+        },
+        resolveMany: async (track_ids, options) => {
+            const response = await MusicModel.getTrackData(track_ids, options)
+
+            if (response.list) {
+                return response
+            }
+
+            return [response]
+        },
         toggleLike: async (manifest, to) => {
             return await MusicModel.toggleTrackLike(manifest, to)
         }
diff --git a/packages/app/src/pages/music/components/dashboard/index.jsx b/packages/app/src/pages/music/components/dashboard/index.jsx
deleted file mode 100755
index 537bec02..00000000
--- a/packages/app/src/pages/music/components/dashboard/index.jsx
+++ /dev/null
@@ -1,7 +0,0 @@
-import React from "react"
-
-export default (props) => {
-    return <div>
-        Dashboard
-    </div>
-}
\ No newline at end of file
diff --git a/packages/app/src/pages/music/components/library/index.jsx b/packages/app/src/pages/music/components/library/index.jsx
index f4679531..b4d598d7 100755
--- a/packages/app/src/pages/music/components/library/index.jsx
+++ b/packages/app/src/pages/music/components/library/index.jsx
@@ -139,11 +139,7 @@ const PlaylistItem = (props) => {
 }
 
 const OwnPlaylists = (props) => {
-    const [L_Playlists, R_Playlists, E_Playlists, M_Playlists] = app.cores.api.useRequest(MusicModel.getFavoritePlaylists, {
-        services: {
-            tidal: app.cores.sync.getActiveLinkedServices().tidal
-        }
-    })
+    const [L_Playlists, R_Playlists, E_Playlists, M_Playlists] = app.cores.api.useRequest(MusicModel.getFavoritePlaylists)
 
     if (E_Playlists) {
         console.error(E_Playlists)
@@ -183,8 +179,36 @@ const OwnPlaylists = (props) => {
     </div>
 }
 
-export default () => {
+const Library = (props) => {
     return <div className="music-library">
+        <div className="music-library_header">
+            <h1>Library</h1>
+
+            <antd.Segmented
+                options={[
+                    {
+                        value: "tracks",
+                        label: "Tracks",
+                        icon: <Icons.MdMusicNote />
+                    },
+                    {
+                        value: "playlist",
+                        label: "Playlists",
+                        icon: <Icons.MdPlaylistPlay />
+                    },
+                ]}
+            />
+        </div>
+        <PlaylistItem
+            type="action"
+            data={{
+                icon: <Icons.MdPlaylistAdd />,
+                title: "Create new",
+            }}
+            onClick={OpenPlaylistCreator}
+        />
         <OwnPlaylists />
     </div>
-}
\ No newline at end of file
+}
+
+export default Library
\ No newline at end of file
diff --git a/packages/app/src/pages/music/components/library/index.less b/packages/app/src/pages/music/components/library/index.less
index ef57c627..f35126f1 100755
--- a/packages/app/src/pages/music/components/library/index.less
+++ b/packages/app/src/pages/music/components/library/index.less
@@ -170,4 +170,18 @@
     flex-direction: column;
 
     width: 100%;
+
+    gap: 15px;
+
+    .music-library_header {
+        display: flex;
+        flex-direction: row;
+
+        align-items: center;
+        justify-content: space-between;
+
+        h1 {
+            margin: 0;
+        }
+    }
 }
\ No newline at end of file
diff --git a/packages/app/src/pages/music/dashboard/index.jsx b/packages/app/src/pages/music/dashboard/index.jsx
new file mode 100755
index 00000000..d623623e
--- /dev/null
+++ b/packages/app/src/pages/music/dashboard/index.jsx
@@ -0,0 +1,15 @@
+import React from "react"
+
+import "./index.less"
+
+export default (props) => {
+    return <div className="music-dashboard">
+        <div className="music-dashboard_header">
+            <h1>Your Dashboard</h1>
+        </div>
+
+        <div className="music-dashboard_content">
+        
+        </div>
+    </div>
+}
\ No newline at end of file
diff --git a/packages/app/src/pages/music/dashboard/index.less b/packages/app/src/pages/music/dashboard/index.less
new file mode 100644
index 00000000..04a7f541
--- /dev/null
+++ b/packages/app/src/pages/music/dashboard/index.less
@@ -0,0 +1,8 @@
+.music-dashboard {
+    display: flex;
+    flex-direction: column;
+
+    width: 100%;
+
+    .music-dashboard_header {}
+}
\ No newline at end of file
diff --git a/packages/app/src/pages/music/components/dashboard/releases/index.jsx b/packages/app/src/pages/music/dashboard/releases/index.jsx
similarity index 99%
rename from packages/app/src/pages/music/components/dashboard/releases/index.jsx
rename to packages/app/src/pages/music/dashboard/releases/index.jsx
index 3e8965c8..299244e3 100755
--- a/packages/app/src/pages/music/components/dashboard/releases/index.jsx
+++ b/packages/app/src/pages/music/dashboard/releases/index.jsx
@@ -5,7 +5,7 @@ import { Icons } from "components/Icons"
 import { ImageViewer } from "components"
 import Searcher from "components/Searcher"
 
-import ReleaseCreator from "../../../creator"
+import ReleaseCreator from "../../creator"
 
 import MusicModel from "models/music"
 
diff --git a/packages/app/src/pages/music/components/dashboard/releases/index.less b/packages/app/src/pages/music/dashboard/releases/index.less
similarity index 100%
rename from packages/app/src/pages/music/components/dashboard/releases/index.less
rename to packages/app/src/pages/music/dashboard/releases/index.less
diff --git a/packages/app/src/pages/music/tabs.jsx b/packages/app/src/pages/music/tabs.jsx
index 9939960d..fc1d3e38 100755
--- a/packages/app/src/pages/music/tabs.jsx
+++ b/packages/app/src/pages/music/tabs.jsx
@@ -1,9 +1,6 @@
 import LibraryTab from "./components/library"
 import FavoritesTab from "./components/favorites"
 import ExploreTab from "./components/explore"
-import DashboardTab from "./components/dashboard"
-
-import ReleasesTab from "./components/dashboard/releases"
 
 export default [
     {
@@ -30,18 +27,4 @@ export default [
         icon: "Radio",
         disabled: true
     },
-    {
-        key: "artist_panel",
-        label: "Creator Panel",
-        icon: "MdSpaceDashboard",
-        component: DashboardTab,
-        children: [
-            {
-                key: "artist_panel.releases",
-                label: "Releases",
-                icon: "MdUpcoming",
-                component: ReleasesTab,
-            }
-        ]
-    },
 ]
\ No newline at end of file
diff --git a/packages/app/src/pages/music/track/[track_id]/index.jsx b/packages/app/src/pages/music/track/[track_id]/index.jsx
new file mode 100644
index 00000000..6fdfcc41
--- /dev/null
+++ b/packages/app/src/pages/music/track/[track_id]/index.jsx
@@ -0,0 +1,40 @@
+import React from "react"
+import * as antd from "antd"
+
+import PlaylistView from "components/Music/PlaylistView"
+
+import MusicService from "models/music"
+
+import "./index.less"
+
+const TrackPage = (props) => {
+    const { track_id } = props.params
+
+    const [loading, result, error, makeRequest] = app.cores.api.useRequest(MusicService.getTrackData, track_id)
+
+    if (error) {
+        return <antd.Result
+            status="warning"
+            title="Error"
+            subTitle={error.message}
+        />
+    }
+
+    if (loading) {
+        return <antd.Skeleton active />
+    }
+
+    return <div className="track-page">
+        <PlaylistView
+            playlist={{
+                title: result.title,
+                cover: result.cover_url,
+                list: [result]
+            }}
+            centered={app.isMobile}
+            hasMore={false}
+        />
+    </div>
+}
+
+export default TrackPage
diff --git a/packages/app/src/pages/music/track/[track_id]/index.less b/packages/app/src/pages/music/track/[track_id]/index.less
new file mode 100644
index 00000000..d4b5bf40
--- /dev/null
+++ b/packages/app/src/pages/music/track/[track_id]/index.less
@@ -0,0 +1,6 @@
+.track-page {
+    display: flex;
+    flex-direction: column;
+
+    width: 100%;
+}
\ No newline at end of file
diff --git a/packages/app/src/pages/violation/index.jsx b/packages/app/src/pages/violation/index.jsx
new file mode 100644
index 00000000..aa1e90da
--- /dev/null
+++ b/packages/app/src/pages/violation/index.jsx
@@ -0,0 +1,9 @@
+import React from "react"
+
+const ViolationPage = (props) => {
+    return <div>
+        <h1>Violation</h1>
+    </div>
+}
+
+export default ViolationPage
\ No newline at end of file
diff --git a/packages/server/classes/AuthToken/index.js b/packages/server/classes/AuthToken/index.js
index 801093b0..61dd939d 100644
--- a/packages/server/classes/AuthToken/index.js
+++ b/packages/server/classes/AuthToken/index.js
@@ -1,5 +1,5 @@
 import jwt from "jsonwebtoken"
-import { Session, RegenerationToken, User } from "../../db_models"
+import { Session, RegenerationToken, User, TosViolations } from "../../db_models"
 
 export default class Token {
     static get strategy() {
@@ -69,6 +69,21 @@ export default class Token {
 
             result.data = decoded
 
+            // check account tos violation
+            const violation = await TosViolations.findOne({ user_id: decoded.user_id })
+
+            if (violation) {
+                console.log("violation", violation)
+
+                result.valid = false
+                result.banned = {
+                    reason: violation.reason,
+                    expire_at: violation.expire_at,
+                }
+
+                return result
+            }
+
             const sessions = await Session.find({ user_id: decoded.user_id })
             const currentSession = sessions.find((session) => session.token === token)
 
diff --git a/packages/server/classes/ChunkFileUpload/index.js b/packages/server/classes/ChunkFileUpload/index.js
index cc5280b9..ace09795 100755
--- a/packages/server/classes/ChunkFileUpload/index.js
+++ b/packages/server/classes/ChunkFileUpload/index.js
@@ -169,7 +169,7 @@ export async function uploadChunkFile(req, {
 }) {
     return await new Promise(async (resolve, reject) => {
         if (!checkChunkUploadHeaders(req.headers)) {
-            reject(new OperationErrorError(400, "Missing header(s)"))
+            reject(new OperationError(400, "Missing header(s)"))
             return
         }
 
diff --git a/packages/server/db_models/tosViolations/index.js b/packages/server/db_models/tosViolations/index.js
new file mode 100644
index 00000000..b18f9d83
--- /dev/null
+++ b/packages/server/db_models/tosViolations/index.js
@@ -0,0 +1,17 @@
+export default {
+    name: "TosViolations",
+    collection: "tos_violations",
+    schema: {
+        user_id: {
+            type: "string",
+            required: true,
+        },
+        reason: {
+            type: "string",
+            required: true,
+        },
+        expire_at: {
+            type: "date",
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/server/db_models/track/index.js b/packages/server/db_models/track/index.js
index 576ddc3c..74c08bd3 100755
--- a/packages/server/db_models/track/index.js
+++ b/packages/server/db_models/track/index.js
@@ -9,8 +9,8 @@ export default {
         album: {
             type: String,
         },
-        artist: {
-            type: String,
+        artists: {
+            type: Array,
         },
         source: {
             type: String,
@@ -31,10 +31,6 @@ export default {
             type: String,
             default: "https://storage.ragestudio.net/comty-static-assets/default_song.png"
         },
-        thumbnail: {
-            type: String,
-            default: "https://storage.ragestudio.net/comty-static-assets/default_song.png"
-        },
         videoCanvas: {
             type: String,
         },
diff --git a/packages/server/middlewares/withAuthentication/index.js b/packages/server/middlewares/withAuthentication/index.js
index 80e1fb30..57c1ddc8 100755
--- a/packages/server/middlewares/withAuthentication/index.js
+++ b/packages/server/middlewares/withAuthentication/index.js
@@ -3,19 +3,23 @@ import SecureEntry from "../../classes/SecureEntry"
 import AuthToken from "../../classes/AuthToken"
 
 export default async (req, res) => {
-    function reject(description) {
-        return res.status(401).json({ error: `${description ?? "Invalid session"}` })
+    function reject(data) {
+        return res.status(401).json(data)
     }
 
     try {
         const tokenAuthHeader = req.headers?.authorization?.split(" ")
 
         if (!tokenAuthHeader) {
-            return reject("Missing token header")
+            return reject({
+                error: "Missing token header"
+            })
         }
 
         if (!tokenAuthHeader[1]) {
-            return reject("Recived header, missing token")
+            return reject({
+                error: "Recived header, missing token"
+            })
         }
 
         switch (tokenAuthHeader[0]) {
@@ -25,7 +29,7 @@ export default async (req, res) => {
                 const validation = await AuthToken.validate(token)
 
                 if (!validation.valid) {
-                    return reject(validation.error)
+                    return reject(validation)
                 }
 
                 req.auth = {
@@ -41,7 +45,9 @@ export default async (req, res) => {
                 const [client_id, token] = tokenAuthHeader[1].split(":")
 
                 if (client_id === "undefined" || token === "undefined") {
-                    return reject("Invalid server token")
+                    return reject({
+                        error: "Invalid server token"
+                    })
                 }
 
                 const secureEntries = new SecureEntry(authorizedServerTokens)
@@ -52,11 +58,15 @@ export default async (req, res) => {
                 })
 
                 if (!serverTokenEntry) {
-                    return reject("Invalid server token")
+                    return reject({
+                        error: "Invalid server token"
+                    })
                 }
 
                 if (serverTokenEntry !== token) {
-                    return reject("Missmatching server token")
+                    return reject({
+                        error: "Missmatching server token"
+                    })
                 }
 
                 req.user = {
@@ -68,11 +78,16 @@ export default async (req, res) => {
                 return
             }
             default: {
-                return reject("Invalid token type")
+                return reject({
+                    error: "Invalid token type"
+                })
             }
         }
     } catch (error) {
         console.error(error)
-        return res.status(500).json({ error: "An error occurred meanwhile authenticating your token" })
+
+        return res.status(500).json({
+            error: "An error occurred meanwhile authenticating your token"
+        })
     }
 }
diff --git a/packages/server/services/auth/routes/auth/post.js b/packages/server/services/auth/routes/auth/post.js
index d10f4ed2..e5db3f69 100644
--- a/packages/server/services/auth/routes/auth/post.js
+++ b/packages/server/services/auth/routes/auth/post.js
@@ -1,5 +1,5 @@
 import AuthToken from "@shared-classes/AuthToken"
-import { UserConfig, MFASession } from "@db_models"
+import { UserConfig, MFASession, TosViolations } from "@db_models"
 import requiredFields from "@shared-utils/requiredFields"
 import obscureEmail from "@shared-utils/obscureEmail"
 
@@ -13,6 +13,15 @@ export default async (req, res) => {
         password: req.body.password,
     })
 
+    const violation = await TosViolations.findOne({ user_id: user._id.toString() })
+
+    if (violation) {
+        return res.status(403).json({
+            error: "Terms of service violated",
+            violation: violation.toObject()
+        })
+    }
+
     const userConfig = await UserConfig.findOne({ user_id: user._id.toString() }).catch(() => {
         return {}
     })
diff --git a/packages/server/services/music/classes/Room/index.js b/packages/server/services/music/classes/Room/index.js
deleted file mode 100755
index f6b18865..00000000
--- a/packages/server/services/music/classes/Room/index.js
+++ /dev/null
@@ -1,345 +0,0 @@
-import generateFnHandler from "@utils/generateFnHandler"
-import composePayloadData from "@utils/composePayloadData"
-
-export default class Room {
-    constructor(io, roomId, roomOptions = { title: "Untitled Room" }) {
-        if (!io) {
-            throw new Error("io is required")
-        }
-
-        this.io = io
-        this.roomId = roomId
-        this.roomOptions = roomOptions
-    }
-
-    // declare the maximum audio offset from owner
-    static maxOffsetFromOwner = 1
-
-    ownerUserId = null
-
-    connections = []
-
-    limitations = {
-        maxConnections: 10,
-    }
-
-    currentState = null
-
-    events = {
-        "music:player:start": (socket, data) => {
-            // dispached when someone start playing a new track
-            // if not owner, do nothing
-            if (socket.userData._id !== this.ownerUserId) {
-                return false
-            }
-
-            if (data.state) {
-                this.currentState = data.state
-            }
-
-            this.io.to(this.roomId).emit("music:player:start", composePayloadData(socket, data))
-        },
-        "music:player:seek": (socket, data) => {
-            // dispached when someone seek the track
-            // if not owner, do nothing
-            if (socket.userData._id !== this.ownerUserId) {
-                return false
-            }
-
-            if (data.state) {
-                this.currentState = data.state
-            }
-
-            this.io.to(this.roomId).emit("music:player:seek", composePayloadData(socket, data))
-        },
-        "music:player:loading": (socket, data) => {
-            // TODO: Softmode and Hardmode
-            // Ignore if is the owner
-            if (socket.userData._id === this.ownerUserId) {
-                return false
-            }
-
-            // if not loading, check if need to sync
-            if (!data.loading) {
-                // try to sync with current state
-                if (data.state.time > this.currentState.time + Room.maxOffsetFromOwner) {
-                    socket.emit("music:player:seek", composePayloadData(socket, {
-                        position: this.currentState.time,
-                        command_issuer: this.ownerUserId,
-                    }))
-                }
-            }
-        },
-        "music:player:status": (socket, data) => {
-            if (socket.userData._id !== this.ownerUserId) {
-                return false
-            }
-
-            if (data.state) {
-                this.currentState = data.state
-            }
-
-            this.io.to(this.roomId).emit("music:player:status", composePayloadData(socket, data))
-        },
-        // UPDATE TICK
-        "music:state:update": (socket, data) => {
-            if (socket.userData._id === this.ownerUserId) {
-                // update current state
-                this.currentState = data
-
-                return true
-            }
-
-            if (!this.currentState) {
-                return false
-            }
-
-            if (data.loading) {
-                return false
-            }
-
-            // check if match with current manifest
-            if (!data.manifest || data.manifest._id !== this.currentState.manifest._id) {
-                socket.emit("music:player:start", composePayloadData(socket, {
-                    manifest: this.currentState.manifest,
-                    time: this.currentState.time,
-                    command_issuer: this.ownerUserId,
-                }))
-            }
-
-            if (data.firstSync) {
-                // if not owner, try to sync with current state
-                if (data.time > this.currentState.time + Room.maxOffsetFromOwner) {
-                    socket.emit("music:player:seek", composePayloadData(socket, {
-                        position: this.currentState.time,
-                        command_issuer: this.ownerUserId,
-                    }))
-                }
-
-                // check if match with current playing status
-                if (data.playbackStatus !== this.currentState.playbackStatus && data.firstSync) {
-                    socket.emit("music:player:status", composePayloadData(socket, {
-                        status: this.currentState.playbackStatus,
-                        command_issuer: this.ownerUserId,
-                    }))
-                }
-            }
-        },
-        // ROOM MODERATION CONTROL
-        "room:moderation:kick": (socket, data) => {
-            if (socket.userData._id !== this.ownerUserId) {
-                return socket.emit("error", {
-                    message: "You are not the owner of this room, cannot kick this user",
-                })
-            }
-
-            const { room_id, user_id } = data
-
-            if (this.roomId !== room_id) {
-                console.warn(`[${socket.id}][@${socket.userData.username}] not connected to room ${room_id}, cannot kick`)
-
-                return socket.emit("error", {
-                    message: "You are not connected to requested room, cannot kick this user",
-                })
-            }
-
-            const socket_conn = this.connections.find((socket_conn) => {
-                return socket_conn.userData._id === user_id
-            })
-
-            if (!socket_conn) {
-                console.warn(`[${socket.id}][@${socket.userData.username}] not found user ${user_id} in room ${room_id}, cannot kick`)
-
-                return socket.emit("error", {
-                    message: "User not found in room, cannot kick",
-                })
-            }
-
-            socket_conn.emit("room:moderation:kicked", {
-                room_id,
-            })
-
-            this.leave(socket_conn)
-        },
-        "room:moderation:transfer_ownership": (socket, data) => {
-            if (socket.userData._id !== this.ownerUserId) {
-                return socket.emit("error", {
-                    message: "You are not the owner of this room, cannot transfer ownership",
-                })
-            }
-
-            const { room_id, user_id } = data
-
-            if (this.roomId !== room_id) {
-                console.warn(`[${socket.id}][@${socket.userData.username}] not connected to room ${room_id}, cannot transfer ownership`)
-
-                return socket.emit("error", {
-                    message: "You are not connected to requested room, cannot transfer ownership",
-                })
-            }
-
-            const socket_conn = this.connections.find((socket_conn) => {
-                return socket_conn.userData._id === user_id
-            })
-
-            if (!socket_conn) {
-                console.warn(`[${socket.id}][@${socket.userData.username}] not found user ${user_id} in room ${room_id}, cannot transfer ownership`)
-
-                return socket.emit("error", {
-                    message: "User not found in room, cannot transfer ownership",
-                })
-            }
-
-            this.transferOwner(socket_conn)
-        }
-    }
-
-    join = (socket) => {
-        // set connected room name
-        socket.connectedRoomId = this.roomId
-
-        // join room
-        socket.join(this.roomId)
-
-        // add to connections
-        this.connections.push(socket)
-
-        // emit to self
-        socket.emit("room:joined", this.composeRoomData())
-
-        // emit to others
-        this.io.to(this.roomId).emit("room:user:joined", {
-            user: {
-                user_id: socket.userData._id,
-                username: socket.userData.username,
-                fullName: socket.userData.fullName,
-                avatar: socket.userData.avatar,
-            }
-        })
-
-        // register events
-        for (const [event, fn] of Object.entries(this.events)) {
-            const handler = generateFnHandler(fn, socket)
-
-            if (!Array.isArray(socket.handlers)) {
-                socket.handlers = []
-            }
-
-            socket.handlers.push([event, handler])
-
-            socket.on(event, handler)
-        }
-
-        // send current state
-        this.sendRoomData()
-
-        console.log(`[${socket.id}][@${socket.userData.username}] joined room ${this.roomId}`)
-    }
-
-    leave = (socket) => {
-        // if not connected to any room, do nothing
-        if (!socket.connectedRoomId) {
-            console.warn(`[${socket.id}][@${socket.userData.username}] not connected to any room`)
-            return
-        }
-
-        // if not connected to this room, do nothing
-        if (socket.connectedRoomId !== this.roomId) {
-            console.warn(`[${socket.id}][@${socket.userData.username}] not connected to room ${this.roomId}, cannot leave`)
-            return false
-        }
-
-        // leave room
-        socket.leave(this.roomId)
-
-        // remove from connections
-        const connIndex = this.connections.findIndex((socket_conn) => socket_conn.id === socket.id)
-
-        if (connIndex !== -1) {
-            this.connections.splice(connIndex, 1)
-        }
-
-        // remove connected room name
-        socket.connectedRoomId = null
-
-        // emit to self
-        socket.emit("room:left", this.composeRoomData())
-
-        // emit to others
-        this.io.to(this.roomId).emit("room:user:left", {
-            user: {
-                user_id: socket.userData._id,
-                username: socket.userData.username,
-                fullName: socket.userData.fullName,
-                avatar: socket.userData.avatar,
-            },
-        })
-
-        // unregister events
-        for (const [event, handler] of socket.handlers) {
-            socket.off(event, handler)
-        }
-
-        // send current state
-        this.sendRoomData()
-
-        console.log(`[${socket.id}][@${socket.userData.username}] left room ${this.roomId}`)
-    }
-
-    composeRoomData = () => {
-        return {
-            roomId: this.roomId,
-            limitations: this.limitations,
-            ownerUserId: this.ownerUserId,
-            options: this.roomOptions,
-            connectedUsers: this.connections.map((socket_conn) => {
-                return {
-                    user_id: socket_conn.userData._id,
-                    username: socket_conn.userData.username,
-                    fullName: socket_conn.userData.fullName,
-                    avatar: socket_conn.userData.avatar,
-                }
-            }),
-            currentState: this.currentState,
-        }
-    }
-
-    sendRoomData = () => {
-        this.io.to(this.roomId).emit("room:current-data", this.composeRoomData())
-    }
-
-    transferOwner = (socket) => {
-        if (!socket || !socket.userData) {
-            console.warn(`[${socket.id}] cannot transfer owner for room [${this.roomId}], no user data`)
-            return false
-        }
-
-        this.ownerUserId = socket.userData._id
-
-        console.log(`[${socket.id}][@${socket.userData.username}] is now the owner of the room [${this.roomId}]`)
-
-        this.io.to(this.roomId).emit("room:owner:changed", {
-            ownerUserId: this.ownerUserId,
-        })
-
-        this.sendRoomData()
-    }
-
-    destroy = () => {
-        for (const socket of this.connections) {
-            this.leave(socket)
-        }
-
-        this.connections = []
-
-        this.io.to(this.roomId).emit("room:destroyed", {
-            room: this.roomId,
-        })
-
-        console.log(`Room ${this.roomId} destroyed`)
-    }
-
-    makeOwner = (socket) => {
-        this.ownerUserId = socket.userData._id
-    }
-}
\ No newline at end of file
diff --git a/packages/server/services/music/classes/RoomsController/index.js b/packages/server/services/music/classes/RoomsController/index.js
deleted file mode 100755
index 42885da6..00000000
--- a/packages/server/services/music/classes/RoomsController/index.js
+++ /dev/null
@@ -1,99 +0,0 @@
-import Room from "@classes/Room"
-
-export default class RoomsController {
-    constructor(io) {
-        if (!io) {
-            throw new Error("io is required")
-        }
-
-        this.io = io
-    }
-
-    rooms = []
-
-    checkRoomExists = (roomId) => {
-        return this.rooms.some((room) => room.roomId === roomId)
-    }
-
-    createRoom = async (roomId, roomOptions) => {
-        if (this.checkRoomExists(roomId)) {
-            throw new Error(`Room ${roomId} already exists`)
-        }
-
-        const room = new Room(this.io, roomId, roomOptions)
-
-        this.rooms.push(room)
-
-        return room
-    }
-
-    connectSocketToRoom = async (socket, roomId, roomOptions) => {
-        let room = null
-
-        if (!this.checkRoomExists(roomId)) {
-            room = await this.createRoom(roomId, roomOptions)
-
-            // make owner
-            room.makeOwner(socket)
-        }
-
-        // check if user is already connected to a room
-        if (socket.connectedRoomId) {
-            console.warn(`[${socket.id}][@${socket.userData.username}] already connected to room ${socket.connectedRoomId}`)
-
-            this.disconnectSocketFromRoom(socket)
-        }
-
-        if (!room) {
-            room = this.rooms.find((room) => room.roomId === roomId)
-        }
-
-        return room.join(socket)
-    }
-
-    disconnectSocketFromRoom = async (socket, roomId) => {
-        if (!roomId) {
-            roomId = socket.connectedRoomId
-        }
-
-        if (!this.checkRoomExists(roomId)) {
-            console.warn(`Cannot disconnect socket [${socket.id}][@${socket.userData.username}] from room ${roomId}, room does not exists`)
-            return false
-        }
-
-        const room = this.rooms.find((room) => room.roomId === roomId)
-
-        // if owners leaves, rotate owner to the next user
-        if (socket.userData._id === room.ownerUserId) {
-            if (room.connections.length > 0 && room.connections[1]) {
-                room.transferOwner(room.connections[1])
-            }
-        }
-
-        // leave
-        room.leave(socket)
-
-        // if room is empty, destroy it
-        if (room.connections.length === 0) {
-            await this.destroyRoom(roomId)
-
-            return true
-        }
-
-        return true
-    }
-
-    destroyRoom = async (roomId) => {
-        if (!this.checkRoomExists(roomId)) {
-            throw new Error(`Room ${roomId} does not exists`)
-        }
-
-        const room = this.rooms.find((room) => room.roomId === roomId)
-
-        room.destroy()
-
-        this.rooms.splice(this.rooms.indexOf(room), 1)
-
-        return true
-    }
-}
diff --git a/packages/server/services/music/classes/track/index.js b/packages/server/services/music/classes/track/index.js
new file mode 100644
index 00000000..280d10ff
--- /dev/null
+++ b/packages/server/services/music/classes/track/index.js
@@ -0,0 +1,5 @@
+export default class Track {
+    static create = require("./methods/create").default
+    static delete = require("./methods/delete").default
+    static get = require("./methods/get").default
+}
\ No newline at end of file
diff --git a/packages/server/services/music/classes/track/methods/create.js b/packages/server/services/music/classes/track/methods/create.js
new file mode 100644
index 00000000..021c17c9
--- /dev/null
+++ b/packages/server/services/music/classes/track/methods/create.js
@@ -0,0 +1,84 @@
+import { Track } from "@db_models"
+import requiredFields from "@shared-utils/requiredFields"
+import MusicMetadata from "music-metadata"
+import axios from "axios"
+
+export default async (payload = {}) => {
+    requiredFields(["title", "source", "user_id"], payload)
+
+    const { data: stream, headers } = await axios({
+        url: payload.source,
+        method: "GET",
+        responseType: "stream",
+    })
+
+    const fileMetadata = await MusicMetadata.parseStream(stream, {
+        mimeType: headers["content-type"],
+    })
+
+    const metadata = {
+        format: fileMetadata.format.codec,
+        channels: fileMetadata.format.numberOfChannels,
+        sampleRate: fileMetadata.format.sampleRate,
+        bits: fileMetadata.format.bitsPerSample,
+        lossless: fileMetadata.format.lossless,
+        duration: fileMetadata.format.duration,
+
+        title: fileMetadata.common.title,
+        artists: fileMetadata.common.artists,
+        album: fileMetadata.common.album,
+    }
+
+    if (typeof payload.metadata === "object") {
+        metadata = {
+            ...metadata,
+            ...payload.metadata,
+        }
+    }
+
+    const obj = {
+        title: payload.title,
+        album: payload.album,
+        cover: payload.cover,
+        artists: [],
+        source: payload.source,
+        metadata: metadata,
+    }
+
+    if (Array.isArray(payload.artists)) {
+        obj.artists = payload.artists
+    }
+
+    if (typeof payload.artists === "string") {
+        obj.artists.push(payload.artists)
+    }
+
+    if (obj.artists.length === 0 || !obj.artists) {
+        obj.artists = metadata.artists
+    }
+
+    let track = null
+
+    if (payload._id) {
+        track = await Track.findById(payload._id)
+
+        if (!track) {
+            throw new OperationError(404, "Track not found, cannot update")
+        }
+
+        throw new OperationError(501, "Not implemented")
+    } else {
+        track = new Track({
+            ...obj,
+            publisher: {
+                user_id: payload.user_id,
+            }
+        })
+
+        await track.save()
+    }
+
+    track = track.toObject()
+
+    return track
+}
\ No newline at end of file
diff --git a/packages/server/services/music/classes/track/methods/delete.js b/packages/server/services/music/classes/track/methods/delete.js
new file mode 100644
index 00000000..24478cda
--- /dev/null
+++ b/packages/server/services/music/classes/track/methods/delete.js
@@ -0,0 +1,9 @@
+import { Track } from "@db_models"
+
+export default async (track_id) => {
+    if (!track_id) {
+        throw new OperationError(400, "Missing track_id")
+    }
+
+    return await Track.findOneAndDelete({ _id: track_id })
+}
\ No newline at end of file
diff --git a/packages/server/services/music/classes/track/methods/get.js b/packages/server/services/music/classes/track/methods/get.js
new file mode 100644
index 00000000..b9fd1a82
--- /dev/null
+++ b/packages/server/services/music/classes/track/methods/get.js
@@ -0,0 +1,30 @@
+import { Track } from "@db_models"
+
+export default async (track_id, { limit = 50, offset = 0 } = {}) => {
+    if (!track_id) {
+        throw new OperationError(400, "Missing track_id")
+    }
+
+    const isMultiple = track_id.includes(",")
+
+    if (isMultiple) {
+        const track_ids = track_id.split(",")
+
+        const tracks = await Track.find({ _id: { $in: track_ids } })
+            .limit(limit)
+            .skip(offset)
+
+        return {
+            total_count: await Track.countDocuments({ _id: { $in: track_ids } }),
+            list: tracks.map(track => track.toObject()),
+        }
+    }
+
+    const track = await Track.findById(track_id).catch(() => null)
+
+    if (!track) {
+        throw new OperationError(404, "Track not found")
+    }
+
+    return track
+}
\ No newline at end of file
diff --git a/packages/server/services/music/package.json b/packages/server/services/music/package.json
index 7adb9b27..b414fb8b 100755
--- a/packages/server/services/music/package.json
+++ b/packages/server/services/music/package.json
@@ -24,7 +24,8 @@
         "moment-timezone": "0.5.37",
         "mongoose": "^6.9.0",
         "morgan": "^1.10.0",
+        "music-metadata": "^7.14.0",
         "redis": "^4.6.6",
         "socket.io": "^4.5.4"
     }
-}
\ No newline at end of file
+}
diff --git a/packages/server/services/music/routes/music/tracks/[track_id]/data/get.js b/packages/server/services/music/routes/music/tracks/[track_id]/data/get.js
new file mode 100644
index 00000000..e410e18c
--- /dev/null
+++ b/packages/server/services/music/routes/music/tracks/[track_id]/data/get.js
@@ -0,0 +1,11 @@
+import TrackClass from "@classes/track"
+
+export default {
+    fn: async (req) => {
+        const { track_id } = req.params
+
+        const track = await TrackClass.get(track_id)
+
+        return track
+    }
+}
\ No newline at end of file
diff --git a/packages/server/services/music/routes/music/tracks/[track_id]/delete.js b/packages/server/services/music/routes/music/tracks/[track_id]/delete.js
new file mode 100644
index 00000000..5c14506f
--- /dev/null
+++ b/packages/server/services/music/routes/music/tracks/[track_id]/delete.js
@@ -0,0 +1,21 @@
+import TrackClass from "@classes/track"
+
+export default {
+    middlewares: ["withAuthentication"],
+    fn: async (req) => {
+        const { track_id } = req.params
+
+        const track = await TrackClass.get(track_id)
+
+        if (track.publisher.user_id !== req.auth.session.user_id) {
+            throw new Error("Forbidden, you don't own this track")
+        }
+
+        await TrackClass.delete(track_id)
+
+        return {
+            success: true,
+            track: track,
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/server/services/music/routes/music/tracks/put.js b/packages/server/services/music/routes/music/tracks/put.js
new file mode 100644
index 00000000..c83a489d
--- /dev/null
+++ b/packages/server/services/music/routes/music/tracks/put.js
@@ -0,0 +1,16 @@
+import requiredFields from "@shared-utils/requiredFields"
+import TrackClass from "@classes/track"
+
+export default {
+    middlewares: ["withAuthentication"],
+    fn: async (req) => {
+        requiredFields(["title", "source"], req.body)
+
+        const track = await TrackClass.create({
+            ...req.body,
+            user_id: req.auth.session.user_id,
+        })
+
+        return track
+    }
+}
\ No newline at end of file
diff --git a/packages/server/services/tv/StreamingController/endpoints/deleteStreamingProfile.js b/packages/server/services/tv/StreamingController/endpoints/deleteStreamingProfile.js
deleted file mode 100755
index 19deaa4c..00000000
--- a/packages/server/services/tv/StreamingController/endpoints/deleteStreamingProfile.js
+++ /dev/null
@@ -1,42 +0,0 @@
-import { StreamingProfile } from "@db_models"
-
-export default {
-    method: "DELETE",
-    route: "/streaming/profile",
-    middlewares: ["withAuthentication"],
-    fn: async (req, res) => {
-        const user_id = req.user._id.toString()
-        const { profile_id } = req.body
-
-        if (!profile_id) {
-            return res.status(400).json({
-                error: "Invalid request, missing profile_id"
-            })
-        }
-
-        // search for existing profile
-        let currentProfile = await StreamingProfile.findOne({
-            _id: profile_id,
-        })
-
-        if (!currentProfile) {
-            return res.status(400).json({
-                error: "Invalid request, profile not found"
-            })
-        }
-
-        // check if the profile belongs to the user
-        if (currentProfile.user_id !== user_id) {
-            return res.status(400).json({
-                error: "Invalid request, profile does not belong to the user"
-            })
-        }
-
-        // delete the profile
-        await currentProfile.delete()
-
-        return res.json({
-            success: true
-        })
-    }
-}
\ No newline at end of file
diff --git a/packages/server/services/tv/StreamingController/endpoints/getProfileFromStreamKey.js b/packages/server/services/tv/StreamingController/endpoints/getProfileFromStreamKey.js
deleted file mode 100755
index 8de0f6f2..00000000
--- a/packages/server/services/tv/StreamingController/endpoints/getProfileFromStreamKey.js
+++ /dev/null
@@ -1,19 +0,0 @@
-import { StreamingProfile } from "@db_models"
-
-export default {
-    method: "GET",
-    route: "/profile/streamkey/:streamkey",
-    fn: async (req, res) => {
-        const profile = await StreamingProfile.findOne({
-            stream_key: req.params.streamkey
-        })
-
-        if (!profile) {
-            return res.status(404).json({
-                error: "Profile not found"
-            })
-        }
-
-        return res.json(profile)
-    }
-}
\ No newline at end of file
diff --git a/packages/server/services/tv/StreamingController/endpoints/getProfilesVisibility.js b/packages/server/services/tv/StreamingController/endpoints/getProfilesVisibility.js
deleted file mode 100755
index d83ad2b7..00000000
--- a/packages/server/services/tv/StreamingController/endpoints/getProfilesVisibility.js
+++ /dev/null
@@ -1,24 +0,0 @@
-import { StreamingProfile } from "@db_models"
-
-export default {
-    method: "GET",
-    route: "/profile/visibility",
-    middlewares: ["withAuthentication"],
-    fn: async (req, res) => {
-        let { ids } = req.query
-
-        if (typeof ids === "string") {
-            ids = [ids]
-        }
-
-        let visibilities = await StreamingProfile.find({
-            _id: { $in: ids }
-        })
-
-        visibilities = visibilities.map((visibility) => {
-            return [visibility._id.toString(), visibility.options.private]
-        })
-
-        return res.json(visibilities)
-    }
-}
\ No newline at end of file
diff --git a/packages/server/services/tv/StreamingController/endpoints/getStreamingCategories.js b/packages/server/services/tv/StreamingController/endpoints/getStreamingCategories.js
deleted file mode 100755
index 53db7e0e..00000000
--- a/packages/server/services/tv/StreamingController/endpoints/getStreamingCategories.js
+++ /dev/null
@@ -1,17 +0,0 @@
-import { StreamingCategory } from "@db_models"
-
-export default {
-    method: "GET",
-    route: "/streaming/categories",
-    fn: async (req, res) => {
-        const categories = await StreamingCategory.find()
-
-        if (req.query.key) {
-            const category = categories.find((category) => category.key === req.query.key)
-
-            return res.json(category)
-        }
-
-        return res.json(categories)
-    }
-}
\ No newline at end of file
diff --git a/packages/server/services/tv/StreamingController/endpoints/getStreamingProfiles.js b/packages/server/services/tv/StreamingController/endpoints/getStreamingProfiles.js
deleted file mode 100755
index 1507dd2d..00000000
--- a/packages/server/services/tv/StreamingController/endpoints/getStreamingProfiles.js
+++ /dev/null
@@ -1,50 +0,0 @@
-import { StreamingProfile } from "@db_models"
-import NewStreamingProfile from "@services/newStreamingProfile"
-import composeStreamingSources from "@utils/compose-streaming-sources"
-
-export default {
-    method: "GET",
-    route: "/streaming/profiles",
-    middlewares: ["withAuthentication"],
-    fn: async (req, res) => {
-        const user_id = req.user._id.toString()
-
-        if (!user_id) {
-            return res.status(400).json({
-                error: "Invalid request, missing user_id"
-            })
-        }
-
-        let profiles = await StreamingProfile.find({
-            user_id,
-        }).select("+stream_key")
-
-        if (profiles.length === 0) {
-            // create a new profile
-            const profile = await NewStreamingProfile({
-                user_id,
-                profile_name: "default",
-            })
-
-            profiles = [profile]
-        }
-
-        profiles = profiles.map((profile) => {
-            profile = profile.toObject()
-
-            profile._id = profile._id.toString()
-
-            profile.stream_key = `${req.user.username}__${profile._id}?secret=${profile.stream_key}`
-
-            return profile
-        })
-
-        profiles = profiles.map((profile) => {
-            profile.addresses = composeStreamingSources(req.user.username, profile._id)
-
-            return profile
-        })
-
-        return res.json(profiles)
-    }
-}
\ No newline at end of file
diff --git a/packages/server/services/tv/StreamingController/endpoints/getStreams.js b/packages/server/services/tv/StreamingController/endpoints/getStreams.js
deleted file mode 100755
index 20546e2f..00000000
--- a/packages/server/services/tv/StreamingController/endpoints/getStreams.js
+++ /dev/null
@@ -1,23 +0,0 @@
-import fetchRemoteStreams from "@services/fetchRemoteStreams"
-
-export default {
-    method: "GET",
-    route: "/streams",
-    fn: async (req, res) => {
-        if (req.query.username) {
-            const stream = await fetchRemoteStreams(`${req.query.username}${req.query.profile_id ? `__${req.query.profile_id}` : ""}`)
-
-            if (!stream) {
-                return res.status(404).json({
-                    error: "Stream not found"
-                })
-            }
-
-            return res.json(stream)
-        } else {
-            const streams = await fetchRemoteStreams()
-
-            return res.json(streams)
-        }
-    }
-}
\ No newline at end of file
diff --git a/packages/server/services/tv/StreamingController/endpoints/handleStreamPublish.js b/packages/server/services/tv/StreamingController/endpoints/handleStreamPublish.js
deleted file mode 100755
index 39c0086c..00000000
--- a/packages/server/services/tv/StreamingController/endpoints/handleStreamPublish.js
+++ /dev/null
@@ -1,58 +0,0 @@
-import { StreamingProfile, User } from "@db_models"
-
-export default {
-    method: "POST",
-    route: "/stream/publish",
-    fn: async (req, res) => {
-        const { stream, app } = req.body
-
-        if (process.env.STREAMING__OUTPUT_PUBLISH_REQUESTS === "true") {
-            console.log("Publish request:", req.body)
-        }
-
-        const streamingProfile = await StreamingProfile.findOne({
-            stream_key: stream
-        })
-
-        if (!streamingProfile) {
-            return res.status(404).json({
-                code: 1,
-                error: "Streaming profile not found",
-            })
-        }
-
-        const user = await User.findById(streamingProfile.user_id)
-
-        if (!user) {
-            return res.status(404).json({
-                code: 1,
-                error: "User not found",
-            })
-        }
-
-        const [username, profile_id] = app.split("/")[1].split("__")
-
-        if (user.username !== username) {
-            return res.status(403).json({
-                code: 1,
-                error: "Invalid mount point, username does not match with the stream key",
-            })
-        }
-
-        if (streamingProfile._id.toString() !== profile_id) {
-            return res.status(403).json({
-                code: 1,
-                error: "Invalid mount point, profile id does not match with the stream key",
-            })
-        }
-
-        global.engine.ws.io.of("/").emit(`streaming.new`, streamingProfile)
-
-        global.engine.ws.io.of("/").emit(`streaming.new.${streamingProfile.user_id}`, streamingProfile)
-
-        return res.json({
-            code: 0,
-            status: "ok"
-        })
-    }
-}
\ No newline at end of file
diff --git a/packages/server/services/tv/StreamingController/endpoints/handleStreamUnpublish.js b/packages/server/services/tv/StreamingController/endpoints/handleStreamUnpublish.js
deleted file mode 100755
index a5545532..00000000
--- a/packages/server/services/tv/StreamingController/endpoints/handleStreamUnpublish.js
+++ /dev/null
@@ -1,29 +0,0 @@
-import { StreamingProfile } from "@db_models"
-
-export default {
-    method: "POST",
-    route: "/stream/unpublish",
-    fn: async (req, res) => {
-        const { stream } = req.body
-
-        const streamingProfile = await StreamingProfile.findOne({
-            stream_key: stream
-        })
-
-        if (streamingProfile) {
-            global.engine.ws.io.of("/").emit(`streaming.end`, streamingProfile)
-
-            global.engine.ws.io.of("/").emit(`streaming.end.${streamingProfile.user_id}`, streamingProfile)
-
-            return res.json({
-                code: 0,
-                status: "ok"
-            })
-        }
-
-        return res.json({
-            code: 0,
-            status: "ok, but no streaming profile found"
-        })
-    }
-}
\ No newline at end of file
diff --git a/packages/server/services/tv/StreamingController/endpoints/postStreamingProfile.js b/packages/server/services/tv/StreamingController/endpoints/postStreamingProfile.js
deleted file mode 100755
index d76948f8..00000000
--- a/packages/server/services/tv/StreamingController/endpoints/postStreamingProfile.js
+++ /dev/null
@@ -1,64 +0,0 @@
-import { StreamingProfile } from "@db_models"
-import NewStreamingProfile from "@services/newStreamingProfile"
-
-const AllowedChangesFields = ["profile_name", "info", "options"]
-
-export default {
-    method: "POST",
-    route: "/streaming/profile",
-    middlewares: ["withAuthentication"],
-    fn: async (req, res) => {
-        const user_id = req.user._id.toString()
-
-        if (!user_id) {
-            return res.status(400).json({
-                error: "Invalid request, missing user_id"
-            })
-        }
-
-        const {
-            profile_id,
-            profile_name,
-            info,
-            options,
-        } = req.body
-
-        if (!profile_id && !profile_name) {
-            return res.status(400).json({
-                error: "Invalid request, missing profile_id and profile_name"
-            })
-        }
-
-        // search for existing profile
-        let currentProfile = await StreamingProfile.findOne({
-            _id: profile_id,
-        })
-
-        if (currentProfile && profile_id) {
-            // update the profile
-            AllowedChangesFields.forEach((field) => {
-                if (req.body[field]) {
-                    currentProfile[field] = req.body[field]
-                }
-            })
-
-            await currentProfile.save()
-        } else {
-            if (!profile_name) {
-                return res.status(400).json({
-                    error: "Invalid request, missing profile_name"
-                })
-            }
-
-            // create a new profile
-            currentProfile = await NewStreamingProfile({
-                user_id,
-                profile_name,
-                info,
-                options,
-            })
-        }
-
-        return res.json(currentProfile)
-    }
-}
\ No newline at end of file
diff --git a/packages/server/services/tv/StreamingController/endpoints/regenerateStreamingKey.js b/packages/server/services/tv/StreamingController/endpoints/regenerateStreamingKey.js
deleted file mode 100755
index 33de6b5c..00000000
--- a/packages/server/services/tv/StreamingController/endpoints/regenerateStreamingKey.js
+++ /dev/null
@@ -1,37 +0,0 @@
-import { StreamingProfile } from "@db_models"
-
-export default {
-    method: "POST",
-    route: "/streaming/regenerate_key",
-    middlewares: ["withAuthentication"],
-    fn: async (req, res) => {
-        const { profile_id } = req.body
-
-        if (!profile_id) {
-            return res.status(400).json({
-                message: "Missing profile_id"
-            })
-        }
-
-        const profile = await StreamingProfile.findById(profile_id)
-
-        if (!profile) {
-            return res.status(404).json({
-                message: "Profile not found"
-            })
-        }
-
-        // check if profile user is the same as the user in the request
-        if (profile.user_id !== req.user._id.toString()) {
-            return res.status(403).json({
-                message: "You are not allowed to regenerate this key"
-            })
-        }
-
-        profile.stream_key = global.nanoid()
-
-        await profile.save()
-
-        return res.json(profile.toObject())
-    }
-}
\ No newline at end of file
diff --git a/packages/server/services/tv/StreamingController/index.js b/packages/server/services/tv/StreamingController/index.js
deleted file mode 100755
index 68ccc0fe..00000000
--- a/packages/server/services/tv/StreamingController/index.js
+++ /dev/null
@@ -1,39 +0,0 @@
-import { Controller } from "linebridge/dist/server"
-import generateEndpointsFromDir from "linebridge/dist/server/lib/generateEndpointsFromDir"
-
-export default class StreamingController extends Controller {
-    static refName = "StreamingController"
-    static useRoute = "/tv"
-
-    httpEndpoints = generateEndpointsFromDir(__dirname + "/endpoints")
-
-    // 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)
-    //         })
-    //     }
-    // }
-}
\ No newline at end of file