diff --git a/comty.js b/comty.js new file mode 160000 index 00000000..8acb3f00 --- /dev/null +++ b/comty.js @@ -0,0 +1 @@ +Subproject commit 8acb3f008477bbb782eca1c7f747b494a293e57b diff --git a/linebridge b/linebridge index c011f235..6d553830 160000 --- a/linebridge +++ b/linebridge @@ -1 +1 @@ -Subproject commit c011f2353f8db14a2ed287015d108c2620098a84 +Subproject commit 6d553830ab4661ffab952253d77ccb0bfc1363d8 diff --git a/packages/app/.config.js b/packages/app/.config.js index 9b7eaa89..42e16caf 100755 --- a/packages/app/.config.js +++ b/packages/app/.config.js @@ -18,7 +18,7 @@ const aliases = { layouts: path.join(__dirname, "src/layouts"), hooks: path.join(__dirname, "src/hooks"), classes: path.join(__dirname, "src/classes"), - "comty.js": path.join(__dirname, "../", "comty.js", "src"), + "comty.js": path.join(__dirname, "../../", "comty.js", "src"), models: path.join(__dirname, "../comty.js/src/models"), } diff --git a/packages/app/constants/routes.js b/packages/app/constants/routes.js index 1ddcc27d..615fc273 100755 --- a/packages/app/constants/routes.js +++ b/packages/app/constants/routes.js @@ -77,6 +77,11 @@ export default [ useLayout: "default", public: true }, + { + path: "/apr/*", + useLayout: "minimal", + public: true + }, // THIS MUST BE THE LAST ROUTE { path: "/", diff --git a/packages/app/src/App.jsx b/packages/app/src/App.jsx index 8ac2ab0c..473b8fc8 100755 --- a/packages/app/src/App.jsx +++ b/packages/app/src/App.jsx @@ -143,9 +143,7 @@ class ComtyApp extends React.Component { document.getElementById("root").classList.add("electron") } - console.log(import.meta.env) - - if (import.meta.env.VITE_SENTRY_DSN) { + if (import.meta.env.VITE_SENTRY_DSN && import.meta.env.PROD) { console.log(`Initializing Sentry...`) Sentry.init({ diff --git a/packages/app/src/components/FollowersList/index.jsx b/packages/app/src/components/FollowersList/index.jsx index 0694be93..7cbccf5e 100755 --- a/packages/app/src/components/FollowersList/index.jsx +++ b/packages/app/src/components/FollowersList/index.jsx @@ -47,7 +47,7 @@ export default (props) => { console.log(`Loading Followers for [${props.user_id}]...`) - const followers = await FollowsModel.getFollowers(props.user_id).catch((err) => { + const followers = await FollowsModel.getFollowers(props.user_id, true).catch((err) => { console.error(err) app.message.error("Failed to fetch followers") diff --git a/packages/app/src/components/Login/index.jsx b/packages/app/src/components/Login/index.jsx index 79e4803a..31c26856 100755 --- a/packages/app/src/components/Login/index.jsx +++ b/packages/app/src/components/Login/index.jsx @@ -40,30 +40,45 @@ export default class Login extends React.Component { loginInputs: {}, error: null, phase: 0, + mfa_required: null } formRef = React.createRef() handleFinish = async () => { + this.setState({ + mfa_required: false, + }) + const payload = { username: this.state.loginInputs.username, password: this.state.loginInputs.password, + mfa_code: this.state.loginInputs.mfa_code, } this.clearError() this.toggleLoading(true) - await AuthModel.login(payload, () => this.onDone()).catch((error) => { + await AuthModel.login(payload, this.onDone).catch((error) => { console.error(error, error.response) this.toggleLoading(false) - this.onError(error.response.data.message) + this.onError(error.response.data.error) return false }) } - onDone = async () => { + onDone = async ({ mfa_required } = {}) => { + if (mfa_required) { + this.setState({ + loading: false, + mfa_required: mfa_required, + }) + + return false + } + if (typeof this.props.close === "function") { await this.props.close({ unlock: true @@ -77,6 +92,18 @@ export default class Login extends React.Component { return true } + onClickForgotPassword = () => { + if (this.props.locked) { + this.props.unlock() + } + + if (typeof this.props.close === "function") { + this.props.close() + } + + app.location.push("/apr") + } + onClickRegister = () => { if (this.props.locked) { this.props.unlock() @@ -238,6 +265,34 @@ export default class Login extends React.Component { onPressEnter={this.nextStep} /> + + + Verification Code + + { + this.state.mfa_required && <> +

We send a verification code to [{this.state.mfa_required.sended_to}]

+ +

+ Didn't receive the code? Resend +

+ + } + + this.onUpdateInput("mfa_code", e.target.value)} + onPressEnter={this.nextStep} + /> +
@@ -262,6 +317,10 @@ export default class Login extends React.Component { {this.state.error}
+
+ Forgot your password? +
+
You need a account?
diff --git a/packages/app/src/cores/api/api.core.js b/packages/app/src/cores/api/api.core.js index f81f2c4b..9e003fe3 100755 --- a/packages/app/src/cores/api/api.core.js +++ b/packages/app/src/cores/api/api.core.js @@ -29,48 +29,60 @@ export default class APICore extends Core { } listenEvent(key, handler, instance) { - this.instance.wsInstances[instance ?? "default"].on(key, handler) + if (!this.instance.wsInstances[instance ?? "default"]) { + console.error(`[API] Websocket instance ${instance} not found`) + + return false + } + + return this.instance.wsInstances[instance ?? "default"].on(key, handler) } unlistenEvent(key, handler, instance) { - this.instance.wsInstances[instance ?? "default"].off(key, handler) + if (!this.instance.wsInstances[instance ?? "default"]) { + console.error(`[API] Websocket instance ${instance} not found`) + + return false + } + + return this.instance.wsInstances[instance ?? "default"].off(key, handler) } pendingPingsFromInstance = {} createPingIntervals() { - Object.keys(this.instance.wsInstances).forEach((instance) => { - this.console.debug(`[API] Creating ping interval for ${instance}`) + // Object.keys(this.instance.wsInstances).forEach((instance) => { + // this.console.debug(`[API] Creating ping interval for ${instance}`) - if (this.instance.wsInstances[instance].pingInterval) { - clearInterval(this.instance.wsInstances[instance].pingInterval) - } + // if (this.instance.wsInstances[instance].pingInterval) { + // clearInterval(this.instance.wsInstances[instance].pingInterval) + // } - this.instance.wsInstances[instance].pingInterval = setInterval(() => { - if (this.instance.wsInstances[instance].pendingPingTry && this.instance.wsInstances[instance].pendingPingTry > 3) { - this.console.debug(`[API] Ping timeout for ${instance}`) + // this.instance.wsInstances[instance].pingInterval = setInterval(() => { + // if (this.instance.wsInstances[instance].pendingPingTry && this.instance.wsInstances[instance].pendingPingTry > 3) { + // this.console.debug(`[API] Ping timeout for ${instance}`) - return clearInterval(this.instance.wsInstances[instance].pingInterval) - } + // return clearInterval(this.instance.wsInstances[instance].pingInterval) + // } - const timeStart = Date.now() + // const timeStart = Date.now() - //this.console.debug(`[API] Ping ${instance}`, this.instance.wsInstances[instance].pendingPingTry) + // //this.console.debug(`[API] Ping ${instance}`, this.instance.wsInstances[instance].pendingPingTry) - this.instance.wsInstances[instance].emit("ping", () => { - this.instance.wsInstances[instance].latency = Date.now() - timeStart + // this.instance.wsInstances[instance].emit("ping", () => { + // this.instance.wsInstances[instance].latency = Date.now() - timeStart - this.instance.wsInstances[instance].pendingPingTry = 0 - }) + // this.instance.wsInstances[instance].pendingPingTry = 0 + // }) - this.instance.wsInstances[instance].pendingPingTry = this.instance.wsInstances[instance].pendingPingTry ? this.instance.wsInstances[instance].pendingPingTry + 1 : 1 - }, 5000) + // this.instance.wsInstances[instance].pendingPingTry = this.instance.wsInstances[instance].pendingPingTry ? this.instance.wsInstances[instance].pendingPingTry + 1 : 1 + // }, 5000) - // clear interval on close - this.instance.wsInstances[instance].on("close", () => { - clearInterval(this.instance.wsInstances[instance].pingInterval) - }) - }) + // // clear interval on close + // this.instance.wsInstances[instance].on("close", () => { + // clearInterval(this.instance.wsInstances[instance].pingInterval) + // }) + // }) } async onInitialize() { @@ -92,8 +104,8 @@ export default class APICore extends Core { // make a basic request to check if the API is available await this.instance.instances["default"]({ - method: "GET", - url: "/ping", + method: "head", + url: "/", }).catch((error) => { this.console.error("[API] Ping error", error) @@ -105,7 +117,7 @@ export default class APICore extends Core { this.console.debug("[API] Attached to", this.instance) - this.createPingIntervals() + //this.createPingIntervals() return this.instance } diff --git a/packages/app/src/pages/account/index.jsx b/packages/app/src/pages/account/index.jsx index d9195337..b7646c67 100755 --- a/packages/app/src/pages/account/index.jsx +++ b/packages/app/src/pages/account/index.jsx @@ -56,10 +56,10 @@ export default class Account extends React.Component { requestedUser: null, user: null, - followers: [], - isSelf: false, - isFollowed: false, + + followersCount: 0, + following: false, tabActiveKey: "posts", @@ -87,8 +87,7 @@ export default class Account extends React.Component { let isSelf = false let user = null - let isFollowed = false - let followers = [] + let followersCount = 0 if (requestedUser != null) { if (token.username === requestedUser) { @@ -113,33 +112,24 @@ export default class Account extends React.Component { console.log(`Loaded User [${user.username}] >`, user) - if (!isSelf) { - const followedResult = await FollowsModel.imFollowing(user._id).catch(() => false) - - if (followedResult) { - isFollowed = followedResult.isFollowed - } - } - const followersResult = await FollowsModel.getFollowers(user._id).catch(() => false) if (followersResult) { - followers = followersResult + followersCount = followersResult.count } } await this.setState({ isSelf, - user, requestedUser, - isFollowed, - followers, + user, + + following: user.following, + followersCount: followersCount, }) } onPostListTopVisibility = (to) => { - console.log("onPostListTopVisibility", to) - if (to) { this.profileRef.current.classList.remove("topHidden") } else { @@ -149,7 +139,7 @@ export default class Account extends React.Component { onClickFollow = async () => { const result = await FollowsModel.toggleFollow({ - username: this.state.requestedUser, + user_id: this.state.user._id, }).catch((error) => { console.error(error) antd.message.error(error.message) @@ -158,8 +148,8 @@ export default class Account extends React.Component { }) await this.setState({ - isFollowed: result.following, - followers: result.followers, + following: result.following, + followersCount: result.count, }) } @@ -240,9 +230,9 @@ export default class Account extends React.Component { ref={this.actionsRef} > diff --git a/packages/app/src/pages/apr/index.jsx b/packages/app/src/pages/apr/index.jsx new file mode 100644 index 00000000..a4b2e967 --- /dev/null +++ b/packages/app/src/pages/apr/index.jsx @@ -0,0 +1,11 @@ +import React from "react" + +import "./index.less" + +const AccountPasswordRecovery = () => { + return
+

Account Password Recovery

+
+} + +export default AccountPasswordRecovery \ No newline at end of file diff --git a/packages/app/src/pages/apr/index.less b/packages/app/src/pages/apr/index.less new file mode 100644 index 00000000..d850d946 --- /dev/null +++ b/packages/app/src/pages/apr/index.less @@ -0,0 +1,3 @@ +.password_recover { + padding: 20px; +} \ No newline at end of file diff --git a/packages/app/src/pages/auth/index.less b/packages/app/src/pages/auth/index.less index ff0ddcfa..326303dc 100755 --- a/packages/app/src/pages/auth/index.less +++ b/packages/app/src/pages/auth/index.less @@ -46,6 +46,7 @@ flex-direction: row; width: 55vw; + min-width: 700px; max-width: 800px; overflow: hidden; @@ -83,6 +84,8 @@ width: 100%; + min-width: 420px; + padding: 40px; .content_header { diff --git a/packages/comty.js/package.json b/packages/comty.js/package.json deleted file mode 100755 index 35093759..00000000 --- a/packages/comty.js/package.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "name": "comty.js", - "version": "0.60.3", - "main": "./dist/index.js", - "author": "RageStudio ", - "scripts": { - "build": "hermes build" - }, - "files": [ - "dist" - ], - "license": "MIT", - "dependencies": { - "@foxify/events": "^2.1.0", - "axios": "^1.4.0", - "js-cookie": "^3.0.5", - "jsonwebtoken": "^9.0.0", - "jwt-decode": "^3.1.2", - "luxon": "^3.3.0", - "socket.io-client": "^4.6.1" - }, - "devDependencies": { - "@ragestudio/hermes": "^0.1.0", - "corenode": "^0.28.26" - } -} \ No newline at end of file diff --git a/packages/comty.js/src/handlers/measurePing.js b/packages/comty.js/src/handlers/measurePing.js deleted file mode 100755 index 20a3774c..00000000 --- a/packages/comty.js/src/handlers/measurePing.js +++ /dev/null @@ -1,58 +0,0 @@ -import request from "./request" - -export default async () => { - const timings = {} - - const promises = [ - new Promise(async (resolve) => { - const start = Date.now() - - const failTimeout = setTimeout(() => { - timings.http = "failed" - - resolve() - }, 10000) - - request({ - method: "GET", - url: "/ping", - }) - .then(() => { - // set http timing in ms - timings.http = Date.now() - start - - failTimeout && clearTimeout(failTimeout) - - resolve() - }) - .catch(() => { - timings.http = "failed" - - resolve() - }) - }), - new Promise((resolve) => { - const start = Date.now() - - const failTimeout = setTimeout(() => { - timings.ws = "failed" - - resolve() - }, 10000) - - __comty_shared_state.wsInstances["default"].on("pong", () => { - timings.ws = Date.now() - start - - failTimeout && clearTimeout(failTimeout) - - resolve() - }) - - __comty_shared_state.wsInstances["default"].emit("ping") - }) - ] - - await Promise.all(promises) - - return timings -} \ No newline at end of file diff --git a/packages/comty.js/src/handlers/request.js b/packages/comty.js/src/handlers/request.js deleted file mode 100755 index 852e0539..00000000 --- a/packages/comty.js/src/handlers/request.js +++ /dev/null @@ -1,51 +0,0 @@ -import handleBeforeRequest from "../helpers/handleBeforeRequest" -import handleAfterRequest from "../helpers/handleAfterRequest" - -export default async ( - request = { - method: "GET", - }, - ...args -) => { - const instance = request.instance ?? __comty_shared_state.instances.default - - if (!instance) { - throw new Error("No instance provided") - } - - // handle before request - await handleBeforeRequest(request) - - if (typeof request === "string") { - request = { - url: request, - } - } - - if (typeof request.headers !== "object") { - request.headers = {} - } - - let result = null - - const makeRequest = async () => { - const _result = await instance(request, ...args) - .catch((error) => { - return error - }) - - result = _result - } - - await makeRequest() - - // handle after request - await handleAfterRequest(result, makeRequest) - - // if error, throw it - if (result instanceof Error) { - throw result - } - - return result -} \ No newline at end of file diff --git a/packages/comty.js/src/helpers/handleAfterRequest.js b/packages/comty.js/src/helpers/handleAfterRequest.js deleted file mode 100755 index 3840d158..00000000 --- a/packages/comty.js/src/helpers/handleAfterRequest.js +++ /dev/null @@ -1,34 +0,0 @@ -import handleRegenerationEvent from "./handleRegenerationEvent" - -export default async (data, callback) => { - // handle 401, 403 responses - if (data instanceof Error) { - if (data.code && (data.code === "ECONNABORTED" || data.code === "ERR_NETWORK")) { - console.error(`Request aborted or network error, ignoring`) - return false - } - - if (data.response.status === 401) { - // check if the server issue a refresh token on data - if (data.response.data.refreshToken) { - console.log(`Session expired, but the server issued a refresh token, handling regeneration event`) - - // handle regeneration event - await handleRegenerationEvent(data.response.data.refreshToken) - - return await callback() - } - - // check if route is from "session" namespace - if (data.config.url.includes("/session")) { - return __comty_shared_state.eventBus.emit("session.invalid", "Session expired, but the server did not issue a refresh token") - } - } - - if (data.response.status === 403) { - if (data.config.url.includes("/session")) { - return __comty_shared_state.eventBus.emit("session.invalid", "Session not valid or not existent") - } - } - } -} \ No newline at end of file diff --git a/packages/comty.js/src/helpers/handleBeforeRequest.js b/packages/comty.js/src/helpers/handleBeforeRequest.js deleted file mode 100755 index be7cbee3..00000000 --- a/packages/comty.js/src/helpers/handleBeforeRequest.js +++ /dev/null @@ -1,13 +0,0 @@ -export default async (request) => { - if (__comty_shared_state.onExpiredExceptionEvent) { - if (__comty_shared_state.excludedExpiredExceptionURL.includes(request.url)) return - - await new Promise((resolve) => { - __comty_shared_state.eventBus.once("session.regenerated", () => { - console.log(`Session has been regenerated, retrying request`) - - resolve() - }) - }) - } -} \ No newline at end of file diff --git a/packages/comty.js/src/helpers/handleRegenerationEvent.js b/packages/comty.js/src/helpers/handleRegenerationEvent.js deleted file mode 100755 index 9f67575c..00000000 --- a/packages/comty.js/src/helpers/handleRegenerationEvent.js +++ /dev/null @@ -1,43 +0,0 @@ -import SessionModel from "../models/session" -import request from "../handlers/request" -import { reconnectWebsockets } from "../" - -export default async (refreshToken) => { - __comty_shared_state.eventBus.emit("session.expiredExceptionEvent", refreshToken) - - __comty_shared_state.onExpiredExceptionEvent = true - - const expiredToken = await SessionModel.token - - // send request to regenerate token - const response = await request({ - method: "POST", - url: "/session/regenerate", - data: { - expiredToken: expiredToken, - refreshToken, - } - }).catch((error) => { - console.error(`Failed to regenerate token: ${error.message}`) - return false - }) - - if (!response) { - return __comty_shared_state.eventBus.emit("session.invalid", "Failed to regenerate token") - } - - if (!response.data?.token) { - return __comty_shared_state.eventBus.emit("session.invalid", "Failed to regenerate token, invalid server response.") - } - - // set new token - SessionModel.token = response.data.token - - __comty_shared_state.onExpiredExceptionEvent = false - - // emit event - __comty_shared_state.eventBus.emit("session.regenerated") - - // reconnect websockets - reconnectWebsockets() -} \ No newline at end of file diff --git a/packages/comty.js/src/helpers/withSettings.js b/packages/comty.js/src/helpers/withSettings.js deleted file mode 100755 index d48e243b..00000000 --- a/packages/comty.js/src/helpers/withSettings.js +++ /dev/null @@ -1,25 +0,0 @@ -export default class Settings { - static get = (key) => { - if (typeof window === "undefined") { - return null - } - - return window?.app?.cores?.settings.get(key) - } - - static set = (key, value) => { - if (typeof window === "undefined") { - return null - } - - return window?.app?.cores?.settings.set(key, value) - } - - static is = (key) => { - if (typeof window === "undefined") { - return null - } - - return window?.app?.cores?.settings.is(key) - } -} \ No newline at end of file diff --git a/packages/comty.js/src/helpers/withStorage.js b/packages/comty.js/src/helpers/withStorage.js deleted file mode 100755 index bbea9678..00000000 --- a/packages/comty.js/src/helpers/withStorage.js +++ /dev/null @@ -1,31 +0,0 @@ -import jscookies from "js-cookie" - -class InternalStorage { - #storage = {} - - get(key) { - // get value from storage - return this.#storage[key] - } - - set(key, value) { - // storage securely in memory - return this.#storage[key] = value - } -} - -export default class Storage { - static get engine() { - // check if is running in browser, if is import js-cookie - // else use in-memory safe storage - if (typeof window !== "undefined") { - return jscookies - } - - if (!globalThis.__comty_shared_state["_internal_storage"]) { - globalThis.__comty_shared_state["_internal_storage"] = new InternalStorage() - } - - return globalThis.__comty_shared_state["_internal_storage"] - } -} \ No newline at end of file diff --git a/packages/comty.js/src/hooks/useRequest/index.js b/packages/comty.js/src/hooks/useRequest/index.js deleted file mode 100755 index 0dd2b7a6..00000000 --- a/packages/comty.js/src/hooks/useRequest/index.js +++ /dev/null @@ -1,32 +0,0 @@ -import React from "react" - -export default (method, ...args) => { - if (typeof method !== "function") { - throw new Error("useRequest: method must be a function") - } - - const [loading, setLoading] = React.useState(true) - const [result, setResult] = React.useState(null) - const [error, setError] = React.useState(null) - - const makeRequest = (...newArgs) => { - method(...newArgs) - .then((data) => { - setResult(data) - setLoading(false) - }) - .catch((err) => { - setError(err) - setLoading(false) - }) - } - - React.useEffect(() => { - makeRequest(...args) - }, []) - - return [loading, result, error, (...newArgs) => { - setLoading(true) - makeRequest(...newArgs) - }] -} \ No newline at end of file diff --git a/packages/comty.js/src/index.js b/packages/comty.js/src/index.js deleted file mode 100755 index 9b64c944..00000000 --- a/packages/comty.js/src/index.js +++ /dev/null @@ -1,201 +0,0 @@ -import pkg from "../package.json" -import EventEmitter from "@foxify/events" - -import axios from "axios" -import { io } from "socket.io-client" - -import remotes from "./remotes" - -//import request from "./handlers/request" -import Storage from "./helpers/withStorage" - -import SessionModel from "./models/session" -import { createHandlers } from "./models" - -globalThis.isServerMode = typeof window === "undefined" && typeof global !== "undefined" - -if (globalThis.isServerMode) { - const { Buffer } = require("buffer") - - globalThis.b64Decode = (data) => { - return Buffer.from(data, "base64").toString("utf-8") - } - globalThis.b64Encode = (data) => { - return Buffer.from(data, "utf-8").toString("base64") - } -} - -export async function createWebsockets() { - const instances = globalThis.__comty_shared_state.wsInstances - - for (let [key, instance] of Object.entries(instances)) { - if (instance.connected) { - // disconnect first - instance.disconnect() - } - - // remove current listeners - instance.removeAllListeners() - - delete globalThis.__comty_shared_state.wsInstances[key] - } - - for (let [key, remote] of Object.entries(remotes)) { - if (!remote.hasWebsocket) { - continue - } - - let opts = { - transports: ["websocket"], - autoConnect: remote.autoConnect ?? true, - ...remote.wsParams ?? {}, - } - - if (remote.noAuth !== true) { - opts.auth = { - token: SessionModel.token, - } - } - - globalThis.__comty_shared_state.wsInstances[key] = io(remote.wsOrigin ?? remote.origin, opts) - } - - // regsister events - for (let [key, instance] of Object.entries(instances)) { - instance.on("connect", () => { - console.debug(`[WS-API][${key}] Connected`) - - if (remotes[key].useClassicAuth && remotes[key].noAuth !== true) { - // try to auth - instance.emit("auth", { - token: SessionModel.token, - }) - } - - globalThis.__comty_shared_state.eventBus.emit(`${key}:connected`) - }) - - instance.on("disconnect", () => { - console.debug(`[WS-API][${key}] Disconnected`) - - globalThis.__comty_shared_state.eventBus.emit(`${key}:disconnected`) - }) - - instance.on("error", (error) => { - console.error(`[WS-API][${key}] Error`, error) - - globalThis.__comty_shared_state.eventBus.emit(`${key}:error`, error) - }) - - instance.onAny((event, ...args) => { - console.debug(`[WS-API][${key}] Event (${event})`, ...args) - - globalThis.__comty_shared_state.eventBus.emit(`${key}:${event}`, ...args) - }) - } -} - -export async function disconnectWebsockets() { - const instances = globalThis.__comty_shared_state.wsInstances - - for (let [key, instance] of Object.entries(instances)) { - if (instance.connected) { - instance.disconnect() - } - } -} - -export async function reconnectWebsockets({ force = false } = {}) { - const instances = globalThis.__comty_shared_state.wsInstances - - for (let [key, instance] of Object.entries(instances)) { - if (instance.connected) { - if (!instance.auth) { - instance.disconnect() - - instance.auth = { - token: SessionModel.token, - } - - instance.connect() - continue - } - - if (!force) { - instance.emit("reauthenticate", { - token: SessionModel.token, - }) - - continue - } - - // disconnect first - instance.disconnect() - } - - if (remotes[key].noAuth !== true) { - instance.auth = { - token: SessionModel.token, - } - } - - instance.connect() - } -} - -export default function createClient({ - accessKey = null, - privateKey = null, - enableWs = false, -} = {}) { - const sharedState = globalThis.__comty_shared_state = { - onExpiredExceptionEvent: false, - excludedExpiredExceptionURL: ["/session/regenerate"], - eventBus: new EventEmitter(), - mainOrigin: remotes.default.origin, - instances: Object(), - wsInstances: Object(), - rest: null, - version: pkg.version, - } - - if (globalThis.isServerMode) { - sharedState.rest = createHandlers() - } - - if (privateKey && accessKey && globalThis.isServerMode) { - Storage.engine.set("token", `${accessKey}:${privateKey}`) - } - - // create instances for every remote - for (const [key, remote] of Object.entries(remotes)) { - sharedState.instances[key] = axios.create({ - baseURL: remote.origin, - headers: { - "Content-Type": "application/json", - } - }) - - // create a interceptor to attach the token every request - sharedState.instances[key].interceptors.request.use((config) => { - // check if current request has no Authorization header, if so, attach the token - if (!config.headers["Authorization"]) { - const sessionToken = SessionModel.token - - if (sessionToken) { - config.headers["Authorization"] = `${globalThis.isServerMode ? "Server" : "Bearer"} ${sessionToken}` - } else { - console.warn("Making a request with no session token") - } - } - - return config - }) - } - - if (enableWs) { - createWebsockets() - } - - return sharedState -} \ No newline at end of file diff --git a/packages/comty.js/src/models/auth/index.js b/packages/comty.js/src/models/auth/index.js deleted file mode 100755 index 20490419..00000000 --- a/packages/comty.js/src/models/auth/index.js +++ /dev/null @@ -1,86 +0,0 @@ -import request from "../../handlers/request" -import SessionModel from "../session" - -const emailRegex = new RegExp(/^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/) - -export default class AuthModel { - static login = async (payload, callback) => { - const response = await request({ - method: "post", - url: "/auth/login", - data: { - username: payload.username, //window.btoa(payload.username), - password: payload.password, //window.btoa(payload.password), - }, - }) - - SessionModel.token = response.data.token - - if (typeof callback === "function") { - await callback() - } - - __comty_shared_state.eventBus.emit("auth:login_success") - - return response.data - } - - static logout = async () => { - await SessionModel.destroyCurrentSession() - - SessionModel.removeToken() - - __comty_shared_state.eventBus.emit("auth:logout_success") - } - - static register = async (payload) => { - const { username, password, email } = payload - - const response = await request({ - method: "post", - url: "/auth/register", - data: { - username, - password, - email, - } - }).catch((error) => { - console.error(error) - - return false - }) - - if (!response) { - throw new Error("Unable to register user") - } - - return response - } - - static usernameValidation = async (username) => { - let payload = {} - - // check if usename arguemnt is an email - if (emailRegex.test(username)) { - payload.email = username - } else { - payload.username = username - } - - const response = await request({ - method: "get", - url: "/auth/login/validation", - params: payload, - }).catch((error) => { - console.error(error) - - return false - }) - - if (!response) { - throw new Error("Unable to validate user") - } - - return response.data - } -} \ No newline at end of file diff --git a/packages/comty.js/src/models/feed/index.js b/packages/comty.js/src/models/feed/index.js deleted file mode 100755 index fef3ba08..00000000 --- a/packages/comty.js/src/models/feed/index.js +++ /dev/null @@ -1,56 +0,0 @@ -import request from "../../handlers/request" -import Settings from "../../helpers/withSettings" - -export default class FeedModel { - static getMusicFeed = async ({ trim, limit } = {}) => { - const { data } = await request({ - method: "GET", - url: `/feed/music`, - params: { - trim: trim ?? 0, - limit: limit ?? Settings.get("feed_max_fetch"), - } - }) - - return data - } - - static getGlobalMusicFeed = async ({ trim, limit } = {}) => { - const { data } = await request({ - method: "GET", - url: `/feed/music/global`, - params: { - trim: trim ?? 0, - limit: limit ?? Settings.get("feed_max_fetch"), - } - }) - - return data - } - - static getTimelineFeed = async ({ trim, limit = 10 } = {}) => { - const { data } = await request({ - method: "GET", - url: `/feed/timeline`, - params: { - trim: trim ?? 0, - limit: limit ?? Settings.get("feed_max_fetch"), - } - }) - - return data - } - - static getPostsFeed = async ({ trim, limit } = {}) => { - const { data } = await request({ - method: "GET", - url: `/feed/posts`, - params: { - trim: trim ?? 0, - limit: limit ?? Settings.get("feed_max_fetch"), - } - }) - - return data - } -} \ No newline at end of file diff --git a/packages/comty.js/src/models/follows/index.js b/packages/comty.js/src/models/follows/index.js deleted file mode 100755 index 56986a79..00000000 --- a/packages/comty.js/src/models/follows/index.js +++ /dev/null @@ -1,48 +0,0 @@ -import { SessionModel } from "../../models" -import request from "../../handlers/request" - -export default class FollowsModel { - static imFollowing = async (user_id) => { - if (!user_id) { - throw new Error("user_id is required") - } - - const response = await request({ - method: "GET", - url: `/follow/user/${user_id}`, - }) - - return response.data - } - - static getFollowers = async (user_id) => { - if (!user_id) { - // set current user_id - user_id = SessionModel.user_id - } - - const response = await request({ - method: "GET", - url: `/follow/user/${user_id}/followers`, - }) - - return response.data - } - - static toggleFollow = async ({ user_id, username }) => { - if (!user_id && !username) { - throw new Error("user_id or username is required") - } - - const response = await request({ - method: "POST", - url: "/follow/user/toggle", - data: { - user_id: user_id, - username: username - }, - }) - - return response.data - } -} \ No newline at end of file diff --git a/packages/comty.js/src/models/index.js b/packages/comty.js/src/models/index.js deleted file mode 100755 index 11d514c3..00000000 --- a/packages/comty.js/src/models/index.js +++ /dev/null @@ -1,41 +0,0 @@ -import AuthModel from "./auth" -import FeedModel from "./feed" -import FollowsModel from "./follows" -import LivestreamModel from "./livestream" -import PostModel from "./post" -import SessionModel from "./session" -import SyncModel from "./sync" -import UserModel from "./user" - -function getEndpointsFromModel(model) { - return Object.entries(model).reduce((acc, [key, value]) => { - acc[key] = value - - return acc - }, {}) -} - -function createHandlers() { - return { - auth: getEndpointsFromModel(AuthModel), - feed: getEndpointsFromModel(FeedModel), - follows: getEndpointsFromModel(FollowsModel), - livestream: getEndpointsFromModel(LivestreamModel), - post: getEndpointsFromModel(PostModel), - session: getEndpointsFromModel(SessionModel), - sync: getEndpointsFromModel(SyncModel), - user: getEndpointsFromModel(UserModel), - } -} - -export { - AuthModel, - FeedModel, - FollowsModel, - LivestreamModel, - PostModel, - SessionModel, - SyncModel, - UserModel, - createHandlers, -} diff --git a/packages/comty.js/src/models/livestream/index.js b/packages/comty.js/src/models/livestream/index.js deleted file mode 100755 index 5393b23e..00000000 --- a/packages/comty.js/src/models/livestream/index.js +++ /dev/null @@ -1,84 +0,0 @@ -import request from "../../handlers/request" - -export default class Livestream { - static deleteProfile = async (profile_id) => { - const response = await request({ - method: "DELETE", - url: `/tv/streaming/profile`, - data: { - profile_id - } - }) - - return response.data - } - - static postProfile = async (payload) => { - const response = await request({ - method: "POST", - url: `/tv/streaming/profile`, - data: payload, - }) - - return response.data - } - - static getProfiles = async () => { - const response = await request({ - method: "GET", - url: `/tv/streaming/profiles`, - }) - - return response.data - } - - static regenerateStreamingKey = async (profile_id) => { - const response = await request({ - method: "POST", - url: `/tv/streaming/regenerate_key`, - data: { - profile_id - } - }) - - return response.data - } - - static getCategories = async (key) => { - const response = await request({ - method: "GET", - url: `/tv/streaming/categories`, - params: { - key, - } - }) - - return response.data - } - - static getLivestream = async (payload = {}) => { - if (!payload.username) { - throw new Error("Username is required") - } - - let response = await request({ - method: "GET", - url: `/tv/streams`, - params: { - username: payload.username, - profile_id: payload.profile_id, - } - }) - - return response.data - } - - static getLivestreams = async () => { - const response = await request({ - method: "GET", - url: `/tv/streams`, - }) - - return response.data - } -} \ No newline at end of file diff --git a/packages/comty.js/src/models/music/index.js b/packages/comty.js/src/models/music/index.js deleted file mode 100755 index bff3c5cb..00000000 --- a/packages/comty.js/src/models/music/index.js +++ /dev/null @@ -1,561 +0,0 @@ -import request from "../../handlers/request" -import pmap from "p-map" -import SyncModel from "../sync" - -export default class MusicModel { - static get api_instance() { - return globalThis.__comty_shared_state.instances["music"] - } - - /** - * Retrieves the official featured playlists. - * - * @return {Promise} The data containing the featured playlists. - */ - static async getFeaturedPlaylists() { - const response = await request({ - instance: MusicModel.api_instance, - method: "GET", - url: "/featured/playlists", - }) - - return response.data - } - - /** - * Retrieves track data for a given ID. - * - * @param {string} id - The ID of the track. - * @return {Promise} The track data. - */ - static async getTrackData(id) { - const response = await request({ - instance: MusicModel.api_instance, - method: "GET", - url: `/tracks/${id}/data`, - }) - - return response.data - } - - /** - * Retrieves tracks data for the given track IDs. - * - * @param {Array} ids - An array of track IDs. - * @return {Promise} A promise that resolves to the tracks data. - */ - static async getTracksData(ids) { - const response = await request({ - instance: MusicModel.api_instance, - method: "GET", - url: `/tracks/many`, - params: { - ids, - } - }) - - return response.data - } - - /** - * Retrieves favorite tracks based on specified parameters. - * - * @param {Object} options - The options for retrieving favorite tracks. - * @param {boolean} options.useTidal - Whether to use Tidal for retrieving tracks. Defaults to false. - * @param {number} options.limit - The maximum number of tracks to retrieve. - * @param {number} options.offset - The offset from which to start retrieving tracks. - * @return {Promise} - An object containing the total length of the tracks and the retrieved tracks. - */ - static async getFavoriteTracks({ useTidal = false, limit, offset }) { - let result = [] - - let limitPerRequesters = limit - - if (useTidal) { - limitPerRequesters = limitPerRequesters / 2 - } - - const requesters = [ - async () => { - let { data } = await request({ - instance: MusicModel.api_instance, - method: "GET", - url: `/tracks/liked`, - params: { - limit: limitPerRequesters, - offset, - }, - }) - - return data - }, - async () => { - if (!useTidal) { - return { - total_length: 0, - tracks: [], - } - } - - const tidalResult = await SyncModel.tidalCore.getMyFavoriteTracks({ - limit: limitPerRequesters, - offset, - }) - - return tidalResult - }, - ] - - result = await pmap( - requesters, - async requester => { - const data = await requester() - - return data - }, - { - concurrency: 3, - }, - ) - - let total_length = 0 - - result.forEach(result => { - total_length += result.total_length - }) - - let tracks = result.reduce((acc, cur) => { - return [...acc, ...cur.tracks] - }, []) - - tracks = tracks.sort((a, b) => { - return b.liked_at - a.liked_at - }) - - return { - total_length, - tracks, - } - } - - /** - * Retrieves favorite playlists based on the specified parameters. - * - * @param {Object} options - The options for retrieving favorite playlists. - * @param {number} options.limit - The maximum number of playlists to retrieve. Default is 50. - * @param {number} options.offset - The offset of playlists to retrieve. Default is 0. - * @param {Object} options.services - The services to include for retrieving playlists. Default is an empty object. - * @param {string} options.keywords - The keywords to filter playlists by. - * @return {Promise} - An object containing the total length of the playlists and the playlist items. - */ - static async getFavoritePlaylists({ limit = 50, offset = 0, services = {}, keywords } = {}) { - let result = [] - - let limitPerRequesters = limit - - const requesters = [ - async () => { - const { data } = await request({ - instance: MusicModel.api_instance, - method: "GET", - url: `/playlists/self`, - params: { - keywords, - }, - }) - - return data - }, - ] - - if (services["tidal"] === true) { - limitPerRequesters = limitPerRequesters / (requesters.length + 1) - - requesters.push(async () => { - const _result = await SyncModel.tidalCore.getMyFavoritePlaylists({ - limit: limitPerRequesters, - offset, - }) - - return _result - }) - } - - result = await pmap( - requesters, - async requester => { - const data = await requester() - - return data - }, - { - concurrency: 3, - }, - ) - - // calculate total length - let total_length = 0 - - result.forEach(result => { - total_length += result.total_length - }) - - // reduce items - let items = result.reduce((acc, cur) => { - return [...acc, ...cur.items] - }, []) - - - // sort by created_at - items = items.sort((a, b) => { - return new Date(b.created_at) - new Date(a.created_at) - }) - - return { - total_length: total_length, - items, - } - } - - /** - * Retrieves playlist items based on the provided parameters. - * - * @param {Object} options - The options object. - * @param {string} options.playlist_id - The ID of the playlist. - * @param {string} options.service - The service from which to retrieve the playlist items. - * @param {number} options.limit - The maximum number of items to retrieve. - * @param {number} options.offset - The number of items to skip before retrieving. - * @return {Promise} Playlist items data. - */ - static async getPlaylistItems({ - playlist_id, - service, - - limit, - offset, - }) { - if (service === "tidal") { - const result = await SyncModel.tidalCore.getPlaylistItems({ - playlist_id, - - limit, - offset, - - resolve_items: true, - }) - - return result - } - - const { data } = await request({ - instance: MusicModel.api_instance, - method: "GET", - url: `/playlists/${playlist_id}/items`, - params: { - limit, - offset, - } - }) - - return data - } - - /** - * Retrieves playlist data based on the provided parameters. - * - * @param {Object} options - The options object. - * @param {string} options.playlist_id - The ID of the playlist. - * @param {string} options.service - The service to use. - * @param {number} options.limit - The maximum number of items to retrieve. - * @param {number} options.offset - The offset for pagination. - * @return {Promise} Playlist data. - */ - static async getPlaylistData({ - playlist_id, - service, - - limit, - offset, - }) { - if (service === "tidal") { - const result = await SyncModel.tidalCore.getPlaylistData({ - playlist_id, - - limit, - offset, - - resolve_items: true, - }) - - return result - } - - const { data } = await request({ - instance: MusicModel.api_instance, - method: "GET", - url: `/playlists/${playlist_id}/data`, - params: { - limit, - offset, - } - }) - - return data - } - - /** - * Performs a search based on the provided keywords, with optional parameters for limiting the number of results and pagination. - * - * @param {string} keywords - The keywords to search for. - * @param {object} options - An optional object containing additional parameters. - * @param {number} options.limit - The maximum number of results to return. Defaults to 5. - * @param {number} options.offset - The offset to start returning results from. Defaults to 0. - * @param {boolean} options.useTidal - Whether to use Tidal for the search. Defaults to false. - * @return {Promise} The search results. - */ - static async search(keywords, { limit = 5, offset = 0, useTidal = false }) { - const { data } = await request({ - instance: MusicModel.api_instance, - method: "GET", - url: `/search`, - params: { - keywords, - limit, - offset, - useTidal, - }, - }) - - return data - } - - /** - * Creates a new playlist. - * - * @param {object} payload - The payload containing the data for the new playlist. - * @return {Promise} The new playlist data. - */ - static async newPlaylist(payload) { - const { data } = await request({ - instance: MusicModel.api_instance, - method: "POST", - url: `/playlists/new`, - data: payload, - }) - - return data - } - - /** - * Updates a playlist item in the specified playlist. - * - * @param {string} playlist_id - The ID of the playlist to update. - * @param {object} item - The updated playlist item to be added. - * @return {Promise} - The updated playlist item. - */ - static async putPlaylistItem(playlist_id, item) { - const response = await request({ - instance: MusicModel.api_instance, - method: "PUT", - url: `/playlists/${playlist_id}/items`, - data: item, - }) - - return response.data - } - - /** - * Delete a playlist item. - * - * @param {string} playlist_id - The ID of the playlist. - * @param {string} item_id - The ID of the item to delete. - * @return {Promise} The data returned by the server after the item is deleted. - */ - static async deletePlaylistItem(playlist_id, item_id) { - const response = await request({ - instance: MusicModel.api_instance, - method: "DELETE", - url: `/playlists/${playlist_id}/items/${item_id}`, - }) - - return response.data - } - - /** - * Deletes a playlist. - * - * @param {number} playlist_id - The ID of the playlist to be deleted. - * @return {Promise} The response data from the server. - */ - static async deletePlaylist(playlist_id) { - const response = await request({ - instance: MusicModel.api_instance, - method: "DELETE", - url: `/playlists/${playlist_id}`, - }) - - return response.data - } - - /** - * Execute a PUT request to update or create a release. - * - * @param {object} payload - The payload data. - * @return {Promise} The response data from the server. - */ - static async putRelease(payload) { - const response = await request({ - instance: MusicModel.api_instance, - method: "PUT", - url: `/releases/release`, - data: payload - }) - - return response.data - } - - - /** - * Retrieves the releases associated with the authenticated user. - * - * @param {string} keywords - The keywords to filter the releases by. - * @return {Promise} A promise that resolves to the data of the releases. - */ - static async getMyReleases(keywords) { - const response = await request({ - instance: MusicModel.api_instance, - method: "GET", - url: `/releases/self`, - params: { - keywords, - } - }) - - return response.data - } - - - /** - * Retrieves releases based on the provided parameters. - * - * @param {object} options - The options for retrieving releases. - * @param {string} options.user_id - The ID of the user. - * @param {string[]} options.keywords - The keywords to filter releases by. - * @param {number} options.limit - The maximum number of releases to retrieve. - * @param {number} options.offset - The offset for paginated results. - * @return {Promise} - A promise that resolves to the retrieved releases. - */ - static async getReleases({ - user_id, - keywords, - limit = 50, - offset = 0, - }) { - const response = await request({ - instance: MusicModel.api_instance, - method: "GET", - url: `/releases/user/${user_id}`, - params: { - keywords, - limit, - offset, - } - }) - - return response.data - } - - /** - * Retrieves release data by ID. - * - * @param {number} id - The ID of the release. - * @return {Promise} The release data. - */ - static async getReleaseData(id) { - const response = await request({ - instance: MusicModel.api_instance, - method: "GET", - url: `/releases/${id}/data` - }) - - return response.data - } - - /** - * Deletes a release by its ID. - * - * @param {string} id - The ID of the release to delete. - * @return {Promise} - A Promise that resolves to the data returned by the API. - */ - static async deleteRelease(id) { - const response = await request({ - instance: MusicModel.api_instance, - method: "DELETE", - url: `/releases/${id}` - }) - - return response.data - } - - /** - * Refreshes the track cache for a given track ID. - * - * @param {string} track_id - The ID of the track to refresh the cache for. - * @throws {Error} If track_id is not provided. - * @return {Promise} The response data from the API call. - */ - static async refreshTrackCache(track_id) { - if (!track_id) { - throw new Error("Track ID is required") - } - - const response = await request({ - instance: MusicModel.api_instance, - method: "POST", - url: `/tracks/${track_id}/refresh-cache`, - }) - - return response.data - } - - /** - * Toggles the like status of a track. - * - * @param {Object} manifest - The manifest object containing track information. - * @param {boolean} to - The like status to toggle (true for like, false for unlike). - * @throws {Error} Throws an error if the manifest is missing. - * @return {Object} The response data from the API. - */ - static async toggleTrackLike(manifest, to) { - if (!manifest) { - throw new Error("Manifest is required") - } - - console.log(`Toggling track ${manifest._id} like status to ${to}`) - - const track_id = manifest._id - - switch (manifest.service) { - case "tidal": { - const response = await SyncModel.tidalCore.toggleTrackLike({ - track_id, - to, - }) - - return response - } - default: { - const response = await request({ - instance: MusicModel.api_instance, - method: to ? "POST" : "DELETE", - url: `/tracks/${track_id}/like`, - params: { - service: manifest.service - } - }) - - return response.data - } - } - } -} diff --git a/packages/comty.js/src/models/nfc/index.js b/packages/comty.js/src/models/nfc/index.js deleted file mode 100755 index 2305c13a..00000000 --- a/packages/comty.js/src/models/nfc/index.js +++ /dev/null @@ -1,56 +0,0 @@ -import request from "../../handlers/request" - -export default class NFCModel { - static async getOwnTags() { - const { data } = await request({ - method: "GET", - url: `/nfc/tags` - }) - - return data - } - - static async getTagById(id) { - if (!id) { - throw new Error("ID is required") - } - - const { data } = await request({ - method: "GET", - url: `/nfc/tags/${id}` - }) - - return data - } - - static async getTagBySerial(serial) { - if (!serial) { - throw new Error("Serial is required") - } - - const { data } = await request({ - method: "GET", - url: `/nfc/tag/serial/${serial}` - }) - - return data - } - - static async registerTag(serial, payload) { - if (!serial) { - throw new Error("Serial is required") - } - - if (!payload) { - throw new Error("Payload is required") - } - - const { data } = await request({ - method: "POST", - url: `/nfc/tag/${serial}`, - data: payload - }) - - return data - } -} \ No newline at end of file diff --git a/packages/comty.js/src/models/post/index.js b/packages/comty.js/src/models/post/index.js deleted file mode 100755 index c1116993..00000000 --- a/packages/comty.js/src/models/post/index.js +++ /dev/null @@ -1,169 +0,0 @@ -import request from "../../handlers/request" -import Settings from "../../helpers/withSettings" - -export default class Post { - static get maxPostTextLength() { - return 3200 - } - - static get maxCommentLength() { - return 1200 - } - - static getPostingPolicy = async () => { - const { data } = await request({ - method: "GET", - url: "/posting_policy", - }) - - return data - } - - static getPost = async ({ post_id }) => { - if (!post_id) { - throw new Error("Post ID is required") - } - - const { data } = await request({ - method: "GET", - url: `/posts/post/${post_id}`, - }) - - return data - } - - static getPostComments = async ({ post_id }) => { - if (!post_id) { - throw new Error("Post ID is required") - } - - const { data } = await request({ - method: "GET", - url: `/comments/post/${post_id}`, - }) - - return data - } - - static sendComment = async ({ post_id, comment }) => { - if (!post_id || !comment) { - throw new Error("Post ID and/or comment are required") - } - - const { data } = await request({ - method: "POST", - url: `/comments/post/${post_id}`, - data: { - message: comment, - }, - }) - - return data - } - - static deleteComment = async ({ post_id, comment_id }) => { - if (!post_id || !comment_id) { - throw new Error("Post ID and/or comment ID are required") - } - - const { data } = await request({ - method: "DELETE", - url: `/comments/post/${post_id}/${comment_id}`, - }) - - return data - } - - static getExplorePosts = async ({ trim, limit }) => { - const { data } = await request({ - method: "GET", - url: `/posts/explore`, - params: { - trim: trim ?? 0, - limit: limit ?? Settings.get("feed_max_fetch"), - } - }) - - return data - } - - static getSavedPosts = async ({ trim, limit }) => { - const { data } = await request({ - method: "GET", - url: `/posts/saved`, - params: { - trim: trim ?? 0, - limit: limit ?? Settings.get("feed_max_fetch"), - } - }) - - return data - } - - static getUserPosts = async ({ user_id, trim, limit }) => { - if (!user_id) { - // use current user_id - user_id = app.userData?._id - } - - const { data } = await request({ - method: "GET", - url: `/posts/user/${user_id}`, - params: { - trim: trim ?? 0, - limit: limit ?? Settings.get("feed_max_fetch"), - } - }) - - return data - } - - static toggleLike = async ({ post_id }) => { - if (!post_id) { - throw new Error("Post ID is required") - } - - const { data } = await request({ - method: "POST", - url: `/posts/${post_id}/toggle_like`, - }) - - return data - } - - static toggleSave = async ({ post_id }) => { - if (!post_id) { - throw new Error("Post ID is required") - } - - const { data } = await request({ - method: "POST", - url: `/posts/${post_id}/toggle_save`, - }) - - return data - } - - static create = async (payload) => { - const { data } = await request({ - method: "POST", - url: `/posts/new`, - data: payload, - }) - - return data - } - - static deletePost = async ({ post_id }) => { - if (!post_id) { - throw new Error("Post ID is required") - } - - const { data } = await request({ - method: "DELETE", - url: `/posts/${post_id}`, - }) - - return data - } -} \ No newline at end of file diff --git a/packages/comty.js/src/models/search/index.js b/packages/comty.js/src/models/search/index.js deleted file mode 100755 index 1aa86173..00000000 --- a/packages/comty.js/src/models/search/index.js +++ /dev/null @@ -1,26 +0,0 @@ -import request from "../../handlers/request" - -export default class Search { - static search = async (keywords, params = {}) => { - const { data } = await request({ - method: "GET", - url: `/search`, - params: { - keywords: keywords, - params: params - } - }) - - return data - } - - static async quickSearch(params) { - const response = await request({ - method: "GET", - url: "/search/quick", - params: params - }) - - return response.data - } -} \ No newline at end of file diff --git a/packages/comty.js/src/models/session/index.js b/packages/comty.js/src/models/session/index.js deleted file mode 100755 index 8e088e59..00000000 --- a/packages/comty.js/src/models/session/index.js +++ /dev/null @@ -1,135 +0,0 @@ -import jwt_decode from "jwt-decode" -import request from "../../handlers/request" -import Storage from "../../helpers/withStorage" - -export default class Session { - static storageTokenKey = "token" - - static get token() { - return Storage.engine.get(this.storageTokenKey) - } - - static set token(token) { - return Storage.engine.set(this.storageTokenKey, token) - } - - static get roles() { - return this.getDecodedToken()?.roles - } - - static get user_id() { - return this.getDecodedToken()?.user_id - } - - static get session_uuid() { - return this.getDecodedToken()?.session_uuid - } - - static getDecodedToken = () => { - const token = this.token - - return token && jwt_decode(token) - } - - static getAllSessions = async () => { - const response = await request({ - method: "get", - url: "/session/all" - }) - - return response.data - } - - static getCurrentSession = async () => { - const response = await request({ - method: "get", - url: "/session/current" - }) - - return response.data - } - - static getTokenValidation = async () => { - const session = await Session.token - - const response = await request({ - method: "get", - url: "/session/validate", - data: { - session: session - } - }) - - return response.data - } - - static removeToken() { - return Storage.engine.remove(Session.storageTokenKey) - } - - static destroyCurrentSession = async () => { - const token = await Session.token - const session = await Session.getDecodedToken() - - if (!session || !token) { - return false - } - - const response = await request({ - method: "delete", - url: "/session/current" - }).catch((error) => { - console.error(error) - - return false - }) - - Session.removeToken() - - __comty_shared_state.eventBus.emit("session.destroyed") - - return response.data - } - - static destroyAllSessions = async () => { - const session = await Session.getDecodedToken() - - if (!session) { - return false - } - - const response = await request({ - method: "delete", - url: "/session/all" - }) - - Session.removeToken() - - __comty_shared_state.eventBus.emit("session.destroyed") - - return response.data - } - - // alias for validateToken method - static validSession = async (token) => { - return await Session.validateToken(token) - } - - static validateToken = async (token) => { - const response = await request({ - method: "post", - url: "/session/validate", - data: { - token: token - } - }) - - return response.data - } - - static isCurrentTokenValid = async () => { - const health = await Session.getTokenValidation() - - return health.valid - } -} \ No newline at end of file diff --git a/packages/comty.js/src/models/sync/index.js b/packages/comty.js/src/models/sync/index.js deleted file mode 100755 index 0d335763..00000000 --- a/packages/comty.js/src/models/sync/index.js +++ /dev/null @@ -1,59 +0,0 @@ -import spotifyService from "./services/spotify" -import tidalService from "./services/tidal" - -import request from "../../handlers/request" - -const namespacesServices = { - spotify: spotifyService, - tidal: tidalService -} - -export default class SyncModel { - static get spotifyCore() { - return namespacesServices.spotify - } - - static get tidalCore() { - return namespacesServices.tidal - } - - static async linkService(namespace) { - const service = namespacesServices[namespace] - - if (!service || typeof service.linkAccount !== "function") { - throw new Error(`Service ${namespace} not found or not accepting linking.`) - } - - return await service.linkAccount() - } - - static async unlinkService(namespace) { - const service = namespacesServices[namespace] - - if (!service || typeof service.unlinkAccount !== "function") { - throw new Error(`Service ${namespace} not found or not accepting unlinking.`) - } - - return await service.unlinkAccount() - } - - static async hasServiceLinked(namespace) { - const service = namespacesServices[namespace] - - if (!service || typeof service.isActive !== "function") { - throw new Error(`Service ${namespace} not found or not accepting linking.`) - } - - return await service.isActive() - } - - static async getLinkedServices() { - const response = await request({ - instance: globalThis.__comty_shared_state.instances["sync"], - method: "GET", - url: "/active_services", - }) - - return response.data - } -} \ No newline at end of file diff --git a/packages/comty.js/src/models/sync/services/spotify.js b/packages/comty.js/src/models/sync/services/spotify.js deleted file mode 100755 index 5860e212..00000000 --- a/packages/comty.js/src/models/sync/services/spotify.js +++ /dev/null @@ -1,87 +0,0 @@ -export default class SpotifySyncModel { - static get spotify_redirect_uri() { - return window.location.origin + "/callbacks/sync/spotify" - } - - static get spotify_authorize_endpoint() { - return "https://accounts.spotify.com/authorize?response_type=code&client_id={{client_id}}&scope={{scope}}&redirect_uri={{redirect_uri}}&response_type=code" - } - - static async authorizeAccount() { - const scopes = [ - "user-read-private", - "user-modify-playback-state", - "user-read-currently-playing", - "user-read-playback-state", - "streaming", - ] - - const { client_id } = await SpotifySyncModel.get_client_id() - - const parsedUrl = SpotifySyncModel.spotify_authorize_endpoint - .replace("{{client_id}}", client_id) - .replace("{{scope}}", scopes.join(" ")) - .replace("{{redirect_uri}}", SpotifySyncModel.spotify_redirect_uri) - - // open on a new tab - window.open(parsedUrl, "_blank") - } - - static async get_client_id() { - const { data } = await app.cores.api.customRequest({ - method: "GET", - url: `/sync/spotify/client_id`, - }) - - return data - } - - static async syncAuthCode(code) { - const { data } = await app.cores.api.customRequest({ - method: "POST", - url: `/sync/spotify/auth`, - data: { - redirect_uri: SpotifySyncModel.spotify_redirect_uri, - code, - }, - }) - - return data - } - - static async unlinkAccount() { - const { data } = await app.cores.api.customRequest({ - method: "POST", - url: `/sync/spotify/unlink`, - }) - - return data - } - - static async isAuthorized() { - const { data } = await app.cores.api.customRequest({ - method: "GET", - url: `/sync/spotify/is_authorized`, - }) - - return data - } - - static async getData() { - const { data } = await app.cores.api.customRequest({ - method: "GET", - url: `/sync/spotify/data`, - }) - - return data - } - - static async getCurrentPlaying() { - const { data } = await app.cores.api.customRequest({ - method: "GET", - url: `/sync/spotify/currently_playing`, - }) - - return data - } -} \ No newline at end of file diff --git a/packages/comty.js/src/models/sync/services/tidal.js b/packages/comty.js/src/models/sync/services/tidal.js deleted file mode 100755 index ba52ad2e..00000000 --- a/packages/comty.js/src/models/sync/services/tidal.js +++ /dev/null @@ -1,172 +0,0 @@ -import request from "../../../handlers/request" - -export default class TidalService { - static get api_instance() { - return globalThis.__comty_shared_state.instances["sync"] - } - - static async linkAccount() { - if (!window) { - throw new Error("This method is only available in the browser.") - } - - const { data } = await request({ - instance: TidalService.api_instance, - method: "GET", - url: `/services/tidal/create_link`, - }) - - if (data.auth_url) { - window.open(data.auth_url, "_blank") - } - - return data - } - - static async unlinkAccount() { - if (!window) { - throw new Error("This method is only available in the browser.") - } - - const { data } = await request({ - instance: TidalService.api_instance, - method: "POST", - url: `/services/tidal/delete_link`, - }) - - return data - } - - static async isActive() { - if (!window) { - throw new Error("This method is only available in the browser.") - } - - const { data } = await request({ - instance: TidalService.api_instance, - method: "GET", - url: `/services/tidal/is_active`, - }) - - return data - } - - static async getCurrentUser() { - const { data } = await request({ - instance: TidalService.api_instance, - method: "GET", - url: `/services/tidal/current`, - }) - - return data - } - - static async getPlaybackUrl(track_id) { - const { data } = await request({ - instance: TidalService.api_instance, - method: "GET", - url: `/services/tidal/playback/${track_id}`, - }) - - return data - } - - static async getTrackManifest(track_id) { - const { data } = await request({ - instance: TidalService.api_instance, - method: "GET", - url: `/services/tidal/manifest/${track_id}`, - }) - - return data - } - - static async getMyFavoriteTracks({ - limit = 50, - offset = 0, - } = {}) { - const { data } = await request({ - instance: TidalService.api_instance, - method: "GET", - url: `/services/tidal/favorites/tracks`, - params: { - limit, - offset, - }, - }) - - return data - } - - static async getMyFavoritePlaylists({ - limit = 50, - offset = 0, - } = {}) { - const { data } = await request({ - instance: TidalService.api_instance, - method: "GET", - url: `/services/tidal/favorites/playlists`, - params: { - limit, - offset, - }, - }) - - return data - } - - static async getPlaylistData({ - playlist_id, - - resolve_items = false, - limit = 50, - offset = 0, - }) { - const { data } = await request({ - instance: TidalService.api_instance, - method: "GET", - url: `/services/tidal/playlist/${playlist_id}/data`, - params: { - limit, - offset, - resolve_items, - }, - }) - - return data - } - - static async getPlaylistItems({ - playlist_id, - - resolve_items = false, - limit = 50, - offset = 0, - }) { - const { data } = await request({ - instance: TidalService.api_instance, - method: "GET", - url: `/services/tidal/playlist/${playlist_id}/items`, - params: { - limit, - offset, - resolve_items, - }, - }) - - return data - } - - static async toggleTrackLike({ - track_id, - to, - }) { - const { data } = await request({ - instance: TidalService.api_instance, - method: to ? "POST" : "DELETE", - url: `/services/tidal/track/${track_id}/like`, - }) - - return data - } -} \ No newline at end of file diff --git a/packages/comty.js/src/models/user/index.js b/packages/comty.js/src/models/user/index.js deleted file mode 100755 index c56a1993..00000000 --- a/packages/comty.js/src/models/user/index.js +++ /dev/null @@ -1,159 +0,0 @@ -import SessionModel from "../session" -import request from "../../handlers/request" - -export default class User { - static data = async (payload = {}) => { - let { - username, - user_id, - } = payload - - if (!username && !user_id) { - user_id = SessionModel.user_id - } - - if (username && !user_id) { - // resolve user_id from username - const resolveResponse = await request({ - method: "GET", - url: `/user/user_id/${username}`, - }) - - user_id = resolveResponse.data.user_id - } - - const response = await request({ - method: "GET", - url: `/user/${user_id}/data`, - }) - - return response.data - } - - static updateData = async (payload) => { - const response = await request({ - method: "POST", - url: "/user/self/update_data", - data: { - update: payload, - }, - }) - - return response.data - } - - static unsetFullName = async () => { - const response = await request({ - method: "DELETE", - url: "/user/self/public_name", - }) - - return response.data - } - - static selfRoles = async () => { - const response = await request({ - method: "GET", - url: "/roles/self", - }) - - return response.data - } - - static haveRole = async (role) => { - const roles = await User.selfRoles() - - if (!roles) { - return false - } - - return Array.isArray(roles) && roles.includes(role) - } - - static haveAdmin = async () => { - return User.haveRole("admin") - } - - static getUserBadges = async (user_id) => { - if (!user_id) { - user_id = SessionModel.user_id - } - - const { data } = await request({ - method: "GET", - url: `/badge/user/${user_id}`, - }) - - return data - } - - static changePassword = async (payload) => { - const { currentPassword, newPassword } = payload - - const { data } = await request({ - method: "POST", - url: "/user/self/update_password", - data: { - currentPassword, - newPassword, - } - }) - - return data - } - - static getUserFollowers = async ({ - user_id, - limit = 20, - offset = 0, - }) => { - // if user_id or username is not provided, set with current user - if (!user_id && !username) { - user_id = SessionModel.user_id - } - - const { data } = await request({ - method: "GET", - url: `/user/${user_id}/followers`, - params: { - limit, - offset, - } - }) - - return data - } - - static getConnectedUsersFollowing = async () => { - const { data } = await request({ - method: "GET", - url: "/status/connected/following", - }) - - return data - } - - static checkUsernameAvailability = async (username) => { - const { data } = await request({ - method: "GET", - url: `/user/username_available`, - params: { - username, - } - }) - - return data - } - - static checkEmailAvailability = async (email) => { - const { data } = await request({ - method: "GET", - url: `/user/email_available`, - params: { - email, - } - }) - - return data - } -} \ No newline at end of file diff --git a/packages/comty.js/src/models/widget/index.js b/packages/comty.js/src/models/widget/index.js deleted file mode 100755 index 4ba349e8..00000000 --- a/packages/comty.js/src/models/widget/index.js +++ /dev/null @@ -1,18 +0,0 @@ -import request from "../../handlers/request" - -export default class WidgetModel { - static browse = async ({ limit, offset, keywords } = {}) => { - const response = await request({ - instance: globalThis.__comty_shared_state.instances["marketplace"], - method: "GET", - url: "/widgets", - params: { - limit, - offset, - keywords: JSON.stringify(keywords), - }, - }) - - return response.data - } -} \ No newline at end of file diff --git a/packages/comty.js/src/remotes.js b/packages/comty.js/src/remotes.js deleted file mode 100755 index c508ca2d..00000000 --- a/packages/comty.js/src/remotes.js +++ /dev/null @@ -1,78 +0,0 @@ -function composeRemote(path) { - if (typeof window !== "undefined") { - if (window.localStorage.getItem("comty:use_indev") || window.location.hostname === "indev.comty.app") { - return envOrigins["indev"][path] - } - } - - return envOrigins[process.env.NODE_ENV ?? "production"][path] -} - -function getCurrentHostname() { - if (typeof window === "undefined") { - return "localhost" - } - - return window?.location?.hostname ?? "localhost" -} - -const envOrigins = { - "development": { - default: `http://${getCurrentHostname()}:3010`, - chat: `http://${getCurrentHostname()}:3020`, - livestreaming: `http://${getCurrentHostname()}:3030`, - marketplace: `http://${getCurrentHostname()}:3040`, - music: `http://${getCurrentHostname()}:3050`, - files: `http://${getCurrentHostname()}:3060`, - sync: `http://${getCurrentHostname()}:3070`, - }, - "indev": { - default: `https://indev_api.comty.app/main`, - chat: `https://indev_api.comty.app/chat`, - livestreaming: `https://indev_api.comty.app/livestreaming`, - marketplace: `https://indev_api.comty.app/marketplace`, - music: `https://indev_api.comty.app/music`, - files: `https://indev_api.comty.app/files`, - sync: `https://indev_api.comty.app/sync`, - }, - "production": { - default: "https://api.comty.app", - chat: `https://chat_api.comty.app`, - livestreaming: `https://livestreaming_api.comty.app`, - marketplace: `https://marketplace_api.comty.app`, - music: `https://music_api.comty.app`, - files: `https://files_api.comty.app`, - sync: `https://sync_api.comty.app`, - } -} - -export default { - default: { - origin: composeRemote("default"), - hasWebsocket: true, - }, - chat: { - origin: composeRemote("chat"), - hasWebsocket: true, - }, - music: { - origin: composeRemote("music"), - hasWebsocket: true, - }, - livestreaming: { - origin: composeRemote("livestreaming"), - hasWebsocket: false, - }, - marketplace: { - origin: composeRemote("marketplace"), - hasWebsocket: false, - }, - files: { - origin: composeRemote("files"), - hasWebsocket: false, - }, - sync: { - origin: composeRemote("sync"), - hasWebsocket: false, - } -} \ No newline at end of file diff --git a/packages/server/db_models/user/index.js b/packages/server/db_models/user/index.js index b0d2dbaf..1bcf247c 100755 --- a/packages/server/db_models/user/index.js +++ b/packages/server/db_models/user/index.js @@ -14,6 +14,7 @@ export default { verified: { type: Boolean, default: false }, badges: { type: Array, default: [] }, links: { type: Array, default: [] }, + location: { type: String, default: null }, birthday: { type: Date, default: null }, } } \ No newline at end of file diff --git a/packages/server/index.js b/packages/server/index.js index e224c655..bf3c8844 100755 --- a/packages/server/index.js +++ b/packages/server/index.js @@ -9,14 +9,20 @@ import { Observable } from "@gullerya/object-observer" import chalk from "chalk" import Spinnies from "spinnies" import chokidar from "chokidar" +import IPCRouter from "linebridge/src/server/classes/IPCRouter" +import fastify from "fastify" +import { createProxyMiddleware } from "http-proxy-middleware" import { dots as DefaultSpinner } from "spinnies/spinners.json" - -import IPCRouter from "linebridge/src/server/classes/IPCRouter" import getInternalIp from "./lib/getInternalIp" import comtyAscii from "./ascii" import pkg from "./package.json" +import cors from "linebridge/src/server/middlewares/cors" + +import { onExit } from "signal-exit" + + const bootloaderBin = path.resolve(__dirname, "boot") const servicesPath = path.resolve(__dirname, "services") @@ -45,6 +51,7 @@ async function scanServices() { return finalServices } +let internal_proxy = null let allReady = false let selectedProcessInstance = null let internalIp = null @@ -158,6 +165,10 @@ async function getIgnoredFiles(cwd) { } async function handleAllReady() { + if (allReady) { + return false + } + console.clear() allReady = true @@ -199,29 +210,53 @@ async function handleServiceExit(id, code, err) { serviceRegistry[id].ready = false } -// PROCESS HANDLERS -async function handleProcessExit(error, code) { - if (error) { - console.error(error) +async function registerProxy(_path, target, pathRewrite) { + if (internal_proxy.proxys.has(_path)) { + console.warn(`Proxy already registered [${_path}], skipping...`) + return false } - console.log(`\nPreparing to exit...`) + console.log(`🔗 Registering path proxy [${_path}] -> [${target}]`) - for await (let instance of instancePool) { - console.log(`🛑 Killing ${instance.id} [${instance.instance.pid}]`) - await instance.instance.kill() - } + internal_proxy.proxys.add(_path) - return 0 + internal_proxy.use(_path, createProxyMiddleware({ + target: target, + changeOrigin: true, + pathRewrite: pathRewrite, + ws: true, + logLevel: "silent", + })) + + return true } -async function handleIPCData(id, data) { - if (data.type === "log") { - console.log(`[${id}] ${data.message}`) +async function handleIPCData(service_id, msg) { + if (msg.type === "log") { + console.log(`[${service_id}] ${msg.message}`) } - if (data.status === "ready") { - await handleServiceStarted(id) + if (msg.status === "ready") { + await handleServiceStarted(service_id) + } + + if (msg.type === "router:register") { + if (msg.data.path_overrides) { + for await (let pathOverride of msg.data.path_overrides) { + await registerProxy( + `/${pathOverride}`, + `http://${internalIp}:${msg.data.listen.port}/${pathOverride}`, + { + [`^/${pathOverride}`]: "", + } + ) + } + } else { + await registerProxy( + `/${service_id}`, + `http://${msg.data.listen.ip}:${msg.data.listen.port}` + ) + } } } @@ -305,6 +340,31 @@ function createServiceLogTransformer({ id, color = "bgCyan" }) { async function main() { internalIp = await getInternalIp() + internal_proxy = fastify() + + internal_proxy.proxys = new Set() + + await internal_proxy.register(require("@fastify/middie")) + + await internal_proxy.use(cors) + + internal_proxy.get("/ping", (request, reply) => { + return reply.send({ + status: "ok" + }) + }) + + internal_proxy.get("/", (request, reply) => { + return reply.send({ + services: instancePool.map((instance) => { + return { + id: instance.id, + version: instance.version, + } + }), + }) + }) + console.clear() console.log(comtyAscii) console.log(`\nRunning ${chalk.bgBlue(`${pkg.name}`)} | ${chalk.bgMagenta(`[v${pkg.version}]`)} | ${internalIp} \n\n\n`) @@ -323,7 +383,7 @@ async function main() { const instanceFile = path.basename(service) const instanceBasePath = path.dirname(service) - const { name: id, version, proxy } = require(path.resolve(instanceBasePath, "package.json")) + const { name: id, version } = require(path.resolve(instanceBasePath, "package.json")) serviceFileReference[instanceFile] = id @@ -333,7 +393,6 @@ async function main() { version: version, file: instanceFile, cwd: instanceBasePath, - proxy: proxy, buffer: [], ready: false, } @@ -413,14 +472,29 @@ async function main() { return command_fn.fn(callback, ...args) } - }).on("exit", () => { - process.exit(0) + }) + + await internal_proxy.listen({ + host: "0.0.0.0", + port: 9000 + }) + + onExit((code, signal) => { + console.clear() + console.log(`\n🛑 Preparing to exit...`) + + console.log(`Stoping proxy...`) + + internal_proxy.close() + + console.log(`Kill all ${instancePool.length} instances...`) + + for (let instance of instancePool) { + console.log(`Killing ${instance.id} [${instance.instance.pid}]`) + + instance.instance.kill() + } }) } -process.on("exit", handleProcessExit) -process.on("SIGINT", handleProcessExit) -process.on("uncaughtException", handleProcessExit) -process.on("unhandledRejection", handleProcessExit) - main() \ No newline at end of file diff --git a/packages/server/lib/api_wrapper/index.js b/packages/server/lib/api_wrapper/index.js deleted file mode 100755 index 3fdfc6d8..00000000 --- a/packages/server/lib/api_wrapper/index.js +++ /dev/null @@ -1,120 +0,0 @@ -require("dotenv").config() - -const path = require("path") -const { registerBaseAliases } = require("linebridge/dist/server") -const { webcrypto: crypto } = require("crypto") -const infisical = require("infisical-node") - -global.isProduction = process.env.NODE_ENV === "production" - -globalThis["__root"] = path.resolve(process.cwd()) -globalThis["__src"] = path.resolve(globalThis["__root"], global.isProduction ? "dist" : "src") - -const customAliases = { - "root": globalThis["__root"], - "src": globalThis["__src"], - "@shared-classes": path.resolve(globalThis["__src"], "_shared/classes"), - "@services": path.resolve(globalThis["__src"], "services"), -} - -if (!global.isProduction) { - customAliases["comty.js"] = path.resolve(globalThis["__src"], "../../comty.js/src") - customAliases["@shared-classes"] = path.resolve(globalThis["__src"], "shared-classes") -} - -if (process.env.USE_LINKED_SHARED) { - customAliases["@shared-classes"] = path.resolve(globalThis["__src"], "shared-classes") -} - -registerBaseAliases(globalThis["__src"], customAliases) - -// patches -const { Buffer } = require("buffer") - -global.b64Decode = (data) => { - return Buffer.from(data, "base64").toString("utf-8") -} -global.b64Encode = (data) => { - return Buffer.from(data, "utf-8").toString("base64") -} - -global.nanoid = (t = 21) => crypto.getRandomValues(new Uint8Array(t)).reduce(((t, e) => t += (e &= 63) < 36 ? e.toString(36) : e < 62 ? (e - 26).toString(36).toUpperCase() : e > 62 ? "-" : "_"), ""); - -Array.prototype.updateFromObjectKeys = function (obj) { - this.forEach((value, index) => { - if (obj[value] !== undefined) { - this[index] = obj[value] - } - }) - - return this -} - -global.toBoolean = (value) => { - if (typeof value === "boolean") { - return value - } - - if (typeof value === "string") { - return value.toLowerCase() === "true" - } - - return false -} - -async function injectEnvFromInfisical() { - const envMode = global.FORCE_ENV ?? global.isProduction ? "prod" : "dev" - - console.log(`🔑 Injecting env variables from INFISICAL in [${envMode}] mode...`) - - const client = new infisical({ - token: process.env.INFISICAL_TOKEN, - }) - - const secrets = await client.getAllSecrets({ - environment: envMode, - attachToProcessEnv: false, - }) - - // inject to process.env - secrets.forEach((secret) => { - if (!(process.env[secret.secretName])) { - process.env[secret.secretName] = secret.secretValue - } - }) -} - -function handleExit(instance, code) { - if (instance.server) { - if (typeof instance.server.close === "function") { - instance.server.close() - } - } - - return process.exit(code) -} - -async function main({ - force_infisical, -} = {}) { - const API = require(path.resolve(globalThis["__src"], "api.js")).default - - if (force_infisical || process.env.INFISICAL_TOKEN) { - await injectEnvFromInfisical() - } - - const instance = new API() - - process.on("exit", () => handleExit(instance, 0)) - process.on("SIGINT", () => handleExit(instance, 0)) - process.on("uncaughtException", () => handleExit(instance, 1)) - process.on("unhandledRejection", () => handleExit(instance, 1)) - - await instance.initialize() - - return instance -} - -main().catch((error) => { - console.error(`🆘 [FATAL ERROR] >`, error) -}) \ No newline at end of file diff --git a/packages/server/lib/handleWsAuth/index.js b/packages/server/lib/handleWsAuth/index.js new file mode 100644 index 00000000..8482ab74 --- /dev/null +++ b/packages/server/lib/handleWsAuth/index.js @@ -0,0 +1,38 @@ +import AuthToken from "../../classes/AuthToken" +import { User } from "../../db_models" + +export default async (socket, token, err) => { + try { + const validation = await AuthToken.validate(token) + + if (!validation.valid) { + if (validation.error) { + return err(`auth:server_error`) + } + + return err(`auth:token_invalid`) + } + + const userData = await User.findById(validation.data.user_id).catch((err) => { + console.error(`[${socket.id}] failed to get user data caused by server error`, err) + + return null + }) + + if (!userData) { + return err(`auth:user_failed`) + } + + socket.userData = userData + socket.token = token + socket.session = validation.data + + return { + token: token, + username: userData.username, + user_id: userData._id, + } + } catch (error) { + return err(`auth:authentification_failed`, error) + } +} \ No newline at end of file diff --git a/packages/server/package.json b/packages/server/package.json index 5cffaebf..7d25d1be 100755 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -12,6 +12,8 @@ "run:prod": "cross-env NODE_ENV=production node ./dist/index.js" }, "dependencies": { + "@fastify/cors": "^9.0.1", + "@fastify/middie": "^8.3.0", "@gullerya/object-observer": "^6.1.3", "@infisical/sdk": "^2.1.8", "@ragestudio/hermes": "^0.1.1", @@ -21,12 +23,14 @@ "cli-color": "^2.0.3", "clui": "^0.3.6", "dotenv": "^16.4.4", + "fastify": "^4.26.2", "http-proxy-middleware": "^2.0.6", "jsonwebtoken": "^9.0.2", - "linebridge": "^0.16.0", + "linebridge": "^0.18.1", "module-alias": "^2.2.3", "p-map": "^4.0.0", "p-queue": "^7.3.4", + "signal-exit": "^4.1.0", "spinnies": "^0.5.1" }, "devDependencies": { diff --git a/packages/server/services/auth/classes/account/index.js b/packages/server/services/auth/classes/account/index.js index be8c1a17..171dd72f 100644 --- a/packages/server/services/auth/classes/account/index.js +++ b/packages/server/services/auth/classes/account/index.js @@ -4,4 +4,6 @@ export default class Account { static loginStrategy = require("./methods/loginStrategy").default static changePassword = require("./methods/changePassword").default static create = require("./methods/create").default + static sessions = require("./methods/sessions").default + static deleteSession = require("./methods/deleteSession").default } \ No newline at end of file diff --git a/packages/server/services/auth/classes/account/methods/deleteSession.js b/packages/server/services/auth/classes/account/methods/deleteSession.js new file mode 100644 index 00000000..c887c063 --- /dev/null +++ b/packages/server/services/auth/classes/account/methods/deleteSession.js @@ -0,0 +1,28 @@ +import { Session } from "@db_models" + +export default async (payload = {}) => { + const { user_id, token } = payload + + if (!user_id) { + throw new OperationError(400, "user_id not provided") + } + + if (!token) { + throw new OperationError(400, "token not provided") + } + + const session = await Session.findOne({ + user_id, + token + }) + + if (!session) { + throw new OperationError(400, "Session not found") + } + + await session.delete() + + return { + success: true + } +} \ No newline at end of file diff --git a/packages/server/services/auth/classes/account/methods/sessions.js b/packages/server/services/auth/classes/account/methods/sessions.js new file mode 100644 index 00000000..336aacea --- /dev/null +++ b/packages/server/services/auth/classes/account/methods/sessions.js @@ -0,0 +1,13 @@ +import { Session } from "@db_models" + +export default async (payload = {}) => { + const { user_id } = payload + + if (!user_id) { + throw new OperationError(400, "user_id not provided") + } + + const sessions = await Session.find({ user_id }) + + return sessions +} \ No newline at end of file diff --git a/packages/server/services/auth/package.json b/packages/server/services/auth/package.json index 34a2bca5..64192631 100644 --- a/packages/server/services/auth/package.json +++ b/packages/server/services/auth/package.json @@ -2,5 +2,9 @@ "name": "auth", "version": "1.0.0", "main": "index.js", - "license": "MIT" + "license": "MIT", + "proxy":{ + "namespace": "/auth", + "port": 3020 + } } diff --git a/packages/server/services/auth/routes/auth/[username]/exists/get.js b/packages/server/services/auth/routes/auth/[username]/exists/get.js new file mode 100644 index 00000000..bce99e16 --- /dev/null +++ b/packages/server/services/auth/routes/auth/[username]/exists/get.js @@ -0,0 +1,20 @@ +import { User } from "@db_models" + +const emailRegex = new RegExp(/^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/) + +export default async (req) => { + const { username } = req.params + + const query = { + username + } + + if (emailRegex.test(username)) { + delete query.username + query.email = username + } + + return { + exists: !!await User.exists(query) + } +} \ No newline at end of file diff --git a/packages/server/services/auth/routes/auth/post.js b/packages/server/services/auth/routes/auth/post.js index 8eb9b558..b9dd04de 100644 --- a/packages/server/services/auth/routes/auth/post.js +++ b/packages/server/services/auth/routes/auth/post.js @@ -1,6 +1,7 @@ import AuthToken from "@shared-classes/AuthToken" import { UserConfig, MFASession } from "@db_models" import requiredFields from "@shared-utils/requiredFields" +import obscureEmail from "@shared-utils/obscureEmail" import Account from "@classes/account" @@ -66,6 +67,8 @@ export default async (req, res) => { return { message: `MFA required, using [${mfa.type}] method.`, + method: mfa.type, + sended_to: obscureEmail(user.email), mfa_required: true, } } diff --git a/packages/server/services/auth/routes/sessions/all/get.js b/packages/server/services/auth/routes/sessions/all/get.js new file mode 100644 index 00000000..f3064b9c --- /dev/null +++ b/packages/server/services/auth/routes/sessions/all/get.js @@ -0,0 +1,10 @@ +import AccountClass from "@classes/account" + +export default { + middlewares: ["withAuthentication"], + fn: async (req, res) => { + return await AccountClass.sessions({ + user_id: req.auth.session.user_id + }) + } +} \ No newline at end of file diff --git a/packages/server/services/auth/routes/sessions/current/delete.js b/packages/server/services/auth/routes/sessions/current/delete.js new file mode 100644 index 00000000..e22d8bf6 --- /dev/null +++ b/packages/server/services/auth/routes/sessions/current/delete.js @@ -0,0 +1,11 @@ +import AccountClass from "@classes/account" + +export default { + middlewares: ["withAuthentication"], + fn: async (req, res) => { + return await AccountClass.deleteSession({ + user_id: req.auth.session.user_id, + token: req.auth.token, + }) + } +} \ No newline at end of file diff --git a/packages/server/services/main/controllers/SessionController/endpoints/getCurrentSession.js b/packages/server/services/auth/routes/sessions/current/get.js old mode 100755 new mode 100644 similarity index 52% rename from packages/server/services/main/controllers/SessionController/endpoints/getCurrentSession.js rename to packages/server/services/auth/routes/sessions/current/get.js index 7a49da98..45a487c7 --- a/packages/server/services/main/controllers/SessionController/endpoints/getCurrentSession.js +++ b/packages/server/services/auth/routes/sessions/current/get.js @@ -1,8 +1,6 @@ export default { - method: "GET", - route: "/current", middlewares: ["withAuthentication"], fn: async (req, res) => { - return res.json(req.currentSession) + return req.auth.session } } \ No newline at end of file diff --git a/packages/server/services/ems/ipcEvents/mfaSend.js b/packages/server/services/ems/ipcEvents/mfaSend.js index 86b1cc88..0912a935 100644 --- a/packages/server/services/ems/ipcEvents/mfaSend.js +++ b/packages/server/services/ems/ipcEvents/mfaSend.js @@ -3,12 +3,14 @@ import { User } from "@db_models" import templates from "../templates" export default async (ctx, data) => { - const user = await User.findById(data.user_id) + const user = await User.findById(data.user_id).select("+email") if (!user) { throw new OperationError(404, "User not found") } + console.log(`Sending MFA code to ${user.email}...`) + const result = await ctx.mailTransporter.sendMail({ from: process.env.SMTP_USERNAME, to: user.email, diff --git a/packages/server/services/ems/ipcEvents/newLogin.js b/packages/server/services/ems/ipcEvents/newLogin.js index 6fe05d9f..39816220 100644 --- a/packages/server/services/ems/ipcEvents/newLogin.js +++ b/packages/server/services/ems/ipcEvents/newLogin.js @@ -2,7 +2,7 @@ import { User } from "@db_models" import templates from "../templates" export default async (ctx, data) => { - const user = await User.findById(data.user_id) + const user = await User.findById(data.user_id).select("+email") if (!user) { throw new OperationError(404, "User not found") diff --git a/packages/server/services/ems/ipcEvents/passwordChanged.js b/packages/server/services/ems/ipcEvents/passwordChanged.js index 2bf1b8d5..788c2a37 100644 --- a/packages/server/services/ems/ipcEvents/passwordChanged.js +++ b/packages/server/services/ems/ipcEvents/passwordChanged.js @@ -2,7 +2,7 @@ import { User } from "@db_models" import templates from "../templates" export default async (ctx, data) => { - const user = await User.findById(data.user_id) + const user = await User.findById(data.user_id).select("+email") if (!user) { throw new OperationError(404, "User not found") diff --git a/packages/server/services/files/file.service.js b/packages/server/services/files/file.service.js index 55eabd2e..57dbeec8 100755 --- a/packages/server/services/files/file.service.js +++ b/packages/server/services/files/file.service.js @@ -12,7 +12,7 @@ class API extends Server { static refName = "files" static useEngine = "hyper-express" static routesPath = `${__dirname}/routes` - static listen_port = process.env.HTTP_LISTEN_PORT ?? 3008 + static listen_port = process.env.HTTP_LISTEN_PORT ?? 3002 static maxBodyLength = 1000 * 1000 * 1000 diff --git a/packages/server/services/main/controllers/CommentsController/endpoints/createPostComment.js b/packages/server/services/main/controllers/CommentsController/endpoints/createPostComment.js deleted file mode 100755 index 51d7b2c2..00000000 --- a/packages/server/services/main/controllers/CommentsController/endpoints/createPostComment.js +++ /dev/null @@ -1,29 +0,0 @@ -import { Schematized } from "@lib" -import newComment from "../services/newComment" - -export default { - method: "POST", - route: "/post/:post_id", - middlewares: ["withAuthentication"], - fn: Schematized({ - required: ["message"], - select: ["message"], - }, async (req, res) => { - const { post_id } = req.params - const { message } = req.selection - - try { - const comment = await newComment({ - user_id: req.user._id.toString(), - parent_id: post_id, - message: message, - }) - - return res.json(comment) - } catch (error) { - return res.status(400).json({ - error: error.message, - }) - } - }) -} \ No newline at end of file diff --git a/packages/server/services/main/controllers/CommentsController/endpoints/deletePostComment.js b/packages/server/services/main/controllers/CommentsController/endpoints/deletePostComment.js deleted file mode 100755 index a69e31e4..00000000 --- a/packages/server/services/main/controllers/CommentsController/endpoints/deletePostComment.js +++ /dev/null @@ -1,21 +0,0 @@ -import deleteComment from "../services/deleteComment" - -export default { - method: "DELETE", - route: "/post/:post_id/:comment_id", - middlewares: ["withAuthentication"], - fn: async (req, res) => { - const result = await deleteComment({ - comment_id: req.params.comment_id, - issuer_id: req.user._id.toString(), - }).catch((err) => { - res.status(500).json({ message: err.message }) - - return false - }) - - if (result) { - return res.json(result) - } - } -} \ No newline at end of file diff --git a/packages/server/services/main/controllers/CommentsController/endpoints/getPostComments.js b/packages/server/services/main/controllers/CommentsController/endpoints/getPostComments.js deleted file mode 100755 index c675877c..00000000 --- a/packages/server/services/main/controllers/CommentsController/endpoints/getPostComments.js +++ /dev/null @@ -1,21 +0,0 @@ -import getComments from "../services/getComments" - -export default { - method: "GET", - route: "/post/:post_id", - fn: async (req, res) => { - const { post_id } = req.params - - const comments = await getComments({ parent_id: post_id }).catch((err) => { - res.status(400).json({ - error: err.message, - }) - - return false - }) - - if (!comments) return - - return res.json(comments) - } -} \ No newline at end of file diff --git a/packages/server/services/main/controllers/CommentsController/index.js b/packages/server/services/main/controllers/CommentsController/index.js deleted file mode 100755 index 644a9704..00000000 --- a/packages/server/services/main/controllers/CommentsController/index.js +++ /dev/null @@ -1,9 +0,0 @@ -import { Controller } from "linebridge/dist/server" -import generateEndpointsFromDir from "linebridge/dist/server/lib/generateEndpointsFromDir" - -export default class CommentsController extends Controller { - static refName = "CommentsController" - static useRoute = "/comments" - - httpEndpoints = generateEndpointsFromDir(__dirname + "/endpoints") -} \ No newline at end of file diff --git a/packages/server/services/main/controllers/CommentsController/services/deleteComment.js b/packages/server/services/main/controllers/CommentsController/services/deleteComment.js deleted file mode 100755 index 63ef98b2..00000000 --- a/packages/server/services/main/controllers/CommentsController/services/deleteComment.js +++ /dev/null @@ -1,32 +0,0 @@ -import { Comment } from "@db_models" -import CheckUserAdmin from "../../../lib/checkUserAdmin" - -export default async (payload) => { - const { issuer_id, comment_id } = payload - - if (!issuer_id) { - throw new Error("Missing issuer_id") - } - - if (!comment_id) { - throw new Error("Missing comment_id") - } - - const isAdmin = await CheckUserAdmin(issuer_id) - - const comment = await Comment.findById(comment_id) - - if (!comment) { - throw new Error("Comment not found") - } - - if (comment.user_id !== issuer_id && !isAdmin) { - throw new Error("You can't delete this comment, cause you are not the owner.") - } - - await comment.delete() - - global.engine.ws.io.of("/").emit(`post.delete.comment.${comment.parent_id.toString()}`, comment_id) - - return comment -} diff --git a/packages/server/services/main/controllers/CommentsController/services/getComments.js b/packages/server/services/main/controllers/CommentsController/services/getComments.js deleted file mode 100755 index b27bac87..00000000 --- a/packages/server/services/main/controllers/CommentsController/services/getComments.js +++ /dev/null @@ -1,25 +0,0 @@ -import { User, Comment } from "@db_models" - -export default async (payload = {}) => { - const { parent_id } = payload - - if (!parent_id) { - throw new Error("Missing parent_id") - } - - // get comments by descending order - let comments = await Comment.find({ parent_id: parent_id }) - .sort({ created_at: -1 }) - - // fullfill comments with user data - comments = await Promise.all(comments.map(async comment => { - const user = await User.findById(comment.user_id) - - return { - ...comment.toObject(), - user: user.toObject(), - } - })) - - return comments -} \ No newline at end of file diff --git a/packages/server/services/main/controllers/CommentsController/services/newComment.js b/packages/server/services/main/controllers/CommentsController/services/newComment.js deleted file mode 100755 index f6d9cac6..00000000 --- a/packages/server/services/main/controllers/CommentsController/services/newComment.js +++ /dev/null @@ -1,34 +0,0 @@ -import { User, Comment } from "@db_models" - -export default async (payload) => { - const { parent_id, message, user_id } = payload - - if (!parent_id) { - throw new Error("Missing parent_id") - } - - if (!message) { - throw new Error("Missing message") - } - - if (!user_id) { - throw new Error("Missing user_id") - } - - const comment = new Comment({ - user_id: user_id, - parent_id: parent_id, - message: message, - }) - - await comment.save() - - const userData = await User.findById(user_id) - - global.engine.ws.io.of("/").emit(`post.new.comment.${parent_id}`, { - ...comment.toObject(), - user: userData.toObject(), - }) - - return comment -} \ No newline at end of file diff --git a/packages/server/services/main/controllers/FeedController/index.js b/packages/server/services/main/controllers/FeedController/index.js deleted file mode 100755 index 6260e6e4..00000000 --- a/packages/server/services/main/controllers/FeedController/index.js +++ /dev/null @@ -1,140 +0,0 @@ -import { Controller } from "linebridge/dist/server" - -import pmap from "p-map" - -import getPosts from "./services/getPosts" - -import getGlobalReleases from "./services/getGlobalReleases" -import getReleasesFromFollowing from "./services/getReleasesFromFollowing" -import getPlaylistsFromFollowing from "./services/getPlaylistsFromFollowing" - -export default class FeedController extends Controller { - static refName = "FeedController" - static useRoute = "/feed" - - httpEndpoints = { - get: { - "/timeline": { - middlewares: ["withAuthentication"], - fn: async (req, res) => { - const for_user_id = req.user?._id.toString() - - if (!for_user_id) { - return res.status(400).json({ - error: "Invalid user id" - }) - } - - // fetch posts - let posts = await getPosts({ - for_user_id, - limit: req.query?.limit, - skip: req.query?.trim, - }) - - // add type to posts and playlists - posts = posts.map((data) => { - data.type = "post" - - return data - }) - - let feed = [ - ...posts, - ] - - // sort feed - feed.sort((a, b) => { - return new Date(b.created_at) - new Date(a.created_at) - }) - - return res.json(feed) - } - }, - "/music/global": { - middlewares: ["withAuthentication"], - fn: async (req, res) => { - const for_user_id = req.user?._id.toString() - - if (!for_user_id) { - return res.status(400).json({ - error: "Invalid user id" - }) - } - - // fetch playlists from global - const result = await getGlobalReleases({ - for_user_id, - limit: req.query?.limit, - skip: req.query?.trim, - }) - - return res.json(result) - } - }, - "/music": { - middlewares: ["withAuthentication"], - fn: async (req, res) => { - const for_user_id = req.user?._id.toString() - - if (!for_user_id) { - return res.status(400).json({ - error: "Invalid user id" - }) - } - - const searchers = [ - getGlobalReleases, - //getReleasesFromFollowing, - //getPlaylistsFromFollowing, - ] - - let result = await pmap( - searchers, - async (fn, index) => { - const data = await fn({ - for_user_id, - limit: req.query?.limit, - skip: req.query?.trim, - }) - - return data - }, { - concurrency: 3, - },) - - result = result.reduce((acc, cur) => { - return [...acc, ...cur] - }, []) - - return res.json(result) - } - }, - "/posts": { - middlewares: ["withAuthentication"], - fn: async (req, res) => { - const for_user_id = req.user?._id.toString() - - if (!for_user_id) { - return res.status(400).json({ - error: "Invalid user id" - }) - } - - let feed = [] - - // fetch posts - const posts = await getPosts({ - for_user_id, - limit: req.query?.limit, - skip: req.query?.trim, - }) - - feed = feed.concat(posts) - - return res.json(feed) - } - }, - } - } -} \ No newline at end of file diff --git a/packages/server/services/main/controllers/FeedController/services/getGlobalReleases.js b/packages/server/services/main/controllers/FeedController/services/getGlobalReleases.js deleted file mode 100755 index d0c88316..00000000 --- a/packages/server/services/main/controllers/FeedController/services/getGlobalReleases.js +++ /dev/null @@ -1,25 +0,0 @@ -import { Release } from "@db_models" - -export default async (payload) => { - const { - limit = 20, - skip = 0, - } = payload - - let releases = await Release.find({ - $or: [ - { public: true }, - ] - }) - .sort({ created_at: -1 }) - .limit(limit) - .skip(skip) - - releases = Promise.all(releases.map(async (release) => { - release = release.toObject() - - return release - })) - - return releases -} \ No newline at end of file diff --git a/packages/server/services/main/controllers/FeedController/services/getPosts.js b/packages/server/services/main/controllers/FeedController/services/getPosts.js deleted file mode 100755 index 4d68c706..00000000 --- a/packages/server/services/main/controllers/FeedController/services/getPosts.js +++ /dev/null @@ -1,39 +0,0 @@ -import { Post, UserFollow } from "@db_models" - -import fullfillPostsData from "@utils/fullfillPostsData" - -export default async (payload) => { - const { - for_user_id, - limit = 20, - skip = 0, - } = payload - - // get post from users that the user follows - const followingUsers = await UserFollow.find({ - user_id: for_user_id - }) - - const followingUserIds = followingUsers.map((followingUser) => followingUser.to) - - const fetchPostsFromIds = [ - for_user_id, - ...followingUserIds, - ] - - let posts = await Post.find({ - user_id: { $in: fetchPostsFromIds } - }) - .sort({ created_at: -1 }) - .limit(limit) - .skip(skip) - - // fullfill data - posts = await fullfillPostsData({ - posts, - for_user_id, - skip, - }) - - return posts -} \ No newline at end of file diff --git a/packages/server/services/main/controllers/FollowController/endpoints/getFollowStatus.js b/packages/server/services/main/controllers/FollowController/endpoints/getFollowStatus.js deleted file mode 100755 index 1622636d..00000000 --- a/packages/server/services/main/controllers/FollowController/endpoints/getFollowStatus.js +++ /dev/null @@ -1,17 +0,0 @@ -import { UserFollow } from "@db_models" - -export default { - method: "GET", - route: "/user/:user_id", - middlewares: ["withAuthentication"], - fn: async (req, res) => { - const isFollowed = await UserFollow.findOne({ - user_id: req.user._id.toString(), - to: req.params.user_id, - }).catch(() => false) - - return res.json({ - isFollowed: Boolean(isFollowed), - }) - } -} \ No newline at end of file diff --git a/packages/server/services/main/controllers/FollowController/endpoints/getUserFollowers.js b/packages/server/services/main/controllers/FollowController/endpoints/getUserFollowers.js deleted file mode 100755 index b4a144e9..00000000 --- a/packages/server/services/main/controllers/FollowController/endpoints/getUserFollowers.js +++ /dev/null @@ -1,29 +0,0 @@ -import { User, UserFollow } from "@db_models" - -export default { - method: "GET", - route: "/user/:user_id/followers", - fn: async (req, res) => { - const { limit = 30, offset } = req.query - - let followers = [] - - const follows = await UserFollow.find({ - to: req.params.user_id, - }) - .limit(limit) - .skip(offset) - - for await (const follow of follows) { - const user = await User.findById(follow.user_id) - - if (!user) { - continue - } - - followers.push(user.toObject()) - } - - return res.json(followers) - } -} \ No newline at end of file diff --git a/packages/server/services/main/controllers/FollowController/endpoints/toggleFollow.js b/packages/server/services/main/controllers/FollowController/endpoints/toggleFollow.js deleted file mode 100755 index 3043512c..00000000 --- a/packages/server/services/main/controllers/FollowController/endpoints/toggleFollow.js +++ /dev/null @@ -1,60 +0,0 @@ -import { Schematized } from "@lib" -import { User, UserFollow } from "@db_models" - -import followUser from "../services/followUser" -import unfollowUser from "../services/unfollowUser" - -export default { - method: "POST", - route: "/user/toggle", - middlewares: ["withAuthentication"], - fn: Schematized({ - select: ["user_id", "username"], - }, async (req, res) => { - const selfUserId = req.user._id.toString() - - let targetUserId = null - let result = null - - if (typeof req.selection.user_id === "undefined" && typeof req.selection.username === "undefined") { - return res.status(400).json({ message: "No user_id or username provided" }) - } - - if (typeof req.selection.user_id !== "undefined") { - targetUserId = req.selection.user_id - } else { - const user = await User.findOne({ username: req.selection.username }) - - if (!user) { - return res.status(404).json({ message: "User not found" }) - } - - targetUserId = user._id.toString() - } - - // check if already following - const isFollowed = await UserFollow.findOne({ - user_id: selfUserId, - to: targetUserId, - }) - - // if already following, delete - if (isFollowed) { - result = await unfollowUser({ - user_id: selfUserId, - to: targetUserId, - }).catch((error) => { - return res.status(500).json({ message: error.message }) - }) - } else { - result = await followUser({ - user_id: selfUserId, - to: targetUserId, - }).catch((error) => { - return res.status(500).json({ message: error.message }) - }) - } - - return res.json(result) - }) -} \ No newline at end of file diff --git a/packages/server/services/main/controllers/FollowController/index.js b/packages/server/services/main/controllers/FollowController/index.js deleted file mode 100755 index abca8d13..00000000 --- a/packages/server/services/main/controllers/FollowController/index.js +++ /dev/null @@ -1,9 +0,0 @@ -import { Controller } from "linebridge/dist/server" -import generateEndpointsFromDir from "linebridge/dist/server/lib/generateEndpointsFromDir" - -export default class FollowController extends Controller { - static refName = "FollowController" - static useRoute = "/follow" - - httpEndpoints = generateEndpointsFromDir(__dirname + "/endpoints") -} \ No newline at end of file diff --git a/packages/server/services/main/controllers/FollowController/services/followUser.js b/packages/server/services/main/controllers/FollowController/services/followUser.js deleted file mode 100755 index 9232b576..00000000 --- a/packages/server/services/main/controllers/FollowController/services/followUser.js +++ /dev/null @@ -1,48 +0,0 @@ -import { User, UserFollow } from "@db_models" - -export default async (payload) => { - if (typeof payload.user_id === "undefined") { - throw new Error("No user_id provided") - } - if (typeof payload.to === "undefined") { - throw new Error("No to provided") - } - - const user = await User.findById(payload.user_id) - - if (!user) { - throw new Error("User not found") - } - - const follow = await UserFollow.findOne({ - user_id: payload.user_id, - to: payload.to, - }) - - if (follow) { - throw new Error("Already following") - } - - const newFollow = await UserFollow.create({ - user_id: payload.user_id, - to: payload.to, - }) - - await newFollow.save() - - global.engine.ws.io.of("/").emit(`user.follow`, { - ...user.toObject(), - }) - global.engine.ws.io.of("/").emit(`user.follow.${payload.user_id}`, { - ...user.toObject(), - }) - - const followers = await UserFollow.find({ - to: payload.to, - }) - - return { - following: true, - followers: followers, - } -} \ No newline at end of file diff --git a/packages/server/services/main/controllers/FollowController/services/unfollowUser.js b/packages/server/services/main/controllers/FollowController/services/unfollowUser.js deleted file mode 100755 index dfd37328..00000000 --- a/packages/server/services/main/controllers/FollowController/services/unfollowUser.js +++ /dev/null @@ -1,43 +0,0 @@ -import { User, UserFollow } from "@db_models" - -export default async (payload) => { - if (typeof payload.user_id === "undefined") { - throw new Error("No user_id provided") - } - if (typeof payload.to === "undefined") { - throw new Error("No to provided") - } - - const user = await User.findById(payload.user_id) - - if (!user) { - throw new Error("User not found") - } - - const follow = await UserFollow.findOne({ - user_id: payload.user_id, - to: payload.to, - }) - - if (!follow) { - throw new Error("Not following") - } - - await follow.remove() - - global.engine.ws.io.of("/").emit(`user.unfollow`, { - ...user.toObject(), - }) - global.engine.ws.io.of("/").emit(`user.unfollow.${payload.user_id}`, { - ...user.toObject(), - }) - - const followers = await UserFollow.find({ - to: payload.to, - }) - - return { - following: false, - followers: followers, - } -} \ No newline at end of file diff --git a/packages/server/services/main/controllers/ModerationController/endpoints/modifyPostLikes.js b/packages/server/services/main/controllers/ModerationController/endpoints/modifyPostLikes.js deleted file mode 100755 index 36bc80bc..00000000 --- a/packages/server/services/main/controllers/ModerationController/endpoints/modifyPostLikes.js +++ /dev/null @@ -1,47 +0,0 @@ -import { Post, } from "@db_models" -import toggleLike from "../../PostsController/services/toggleLike" - -export default { - method: "POST", - route: "/:post_id/mok_likes", - middlewares: ["withAuthentication", "onlyAdmin"], - fn: async (req, res) => { - const { - count, - interval = 100, - } = req.body - - if (count < 1) { - return res.status(400).json({ - error: "Invalid count, must be greater than 0", - }) - } - - let postData = await Post.findById(req.params.post_id) - - if (!postData) { - return res.status(404).json({ - error: "Post not found", - }) - } - - for (let i = 0; i < count; i++) { - const mokUserId = `mok_${i}_${count}` - - toggleLike({ - post_id: postData._id.toString(), - user_id: mokUserId, - to: true - }) - - await new Promise((resolve) => setTimeout(resolve, interval ?? 100)) - - continue - } - - return res.status(200).json({ - message: "Success", - data: postData - }) - } -} \ No newline at end of file diff --git a/packages/server/services/main/controllers/ModerationController/index.js b/packages/server/services/main/controllers/ModerationController/index.js deleted file mode 100755 index 1b987346..00000000 --- a/packages/server/services/main/controllers/ModerationController/index.js +++ /dev/null @@ -1,10 +0,0 @@ -import { Controller } from "linebridge/dist/server" -import generateEndpointsFromDir from "linebridge/dist/server/lib/generateEndpointsFromDir" - -export default class ModerationController extends Controller { - static refName = "ModerationController" - static useRoute = "/mod" - static reachable = false - - httpEndpoints = generateEndpointsFromDir(__dirname + "/endpoints") -} \ No newline at end of file diff --git a/packages/server/services/main/controllers/PostsController/endpoints/createPost.js b/packages/server/services/main/controllers/PostsController/endpoints/createPost.js deleted file mode 100755 index 4370f580..00000000 --- a/packages/server/services/main/controllers/PostsController/endpoints/createPost.js +++ /dev/null @@ -1,22 +0,0 @@ -import { Schematized } from "@lib" -import { CreatePost } from "../services" - -export default { - method: "POST", - route: "/new", - middlewares: ["withAuthentication"], - fn: Schematized({ - required: ["timestamp"], - select: ["message", "attachments", "timestamp", "reply_to"], - }, async (req, res) => { - const post = await CreatePost({ - user_id: req.user._id.toString(), - message: req.selection.message, - timestamp: req.selection.timestamp, - attachments: req.selection.attachments, - reply_to: req.selection.reply_to, - }) - - return res.json(post) - }) -} \ No newline at end of file diff --git a/packages/server/services/main/controllers/PostsController/endpoints/deletePost.js b/packages/server/services/main/controllers/PostsController/endpoints/deletePost.js deleted file mode 100755 index 228b2486..00000000 --- a/packages/server/services/main/controllers/PostsController/endpoints/deletePost.js +++ /dev/null @@ -1,26 +0,0 @@ -import { DeletePost } from "../services" - -export default { - method: "DELETE", - route: "/:post_id", - middlewares: ["withAuthentication"], - fn: async (req, res) => { - const post = await DeletePost({ - post_id: req.params.post_id, - by_user_id: req.user._id.toString(), - }).catch((err) => { - res.status(400).json({ - error: err.message - }) - - return false - }) - - if (!post) return - - return res.json({ - success: true, - post - }) - } -} \ No newline at end of file diff --git a/packages/server/services/main/controllers/PostsController/endpoints/explorePosts.js b/packages/server/services/main/controllers/PostsController/endpoints/explorePosts.js deleted file mode 100755 index b5db54c2..00000000 --- a/packages/server/services/main/controllers/PostsController/endpoints/explorePosts.js +++ /dev/null @@ -1,20 +0,0 @@ -import { Schematized } from "@lib" -import { GetPostData } from "../services" - -export default { - method: "GET", - route: "/explore", - middlewares: ["withOptionalAuthentication"], - fn: Schematized({ - select: ["user_id"] - }, async (req, res) => { - let posts = await GetPostData({ - limit: req.query?.limit, - skip: req.query?.trim, - from_user_id: req.query?.user_id, - for_user_id: req.user?._id.toString(), - }) - - return res.json(posts) - }) -} \ No newline at end of file diff --git a/packages/server/services/main/controllers/PostsController/endpoints/getPostData.js b/packages/server/services/main/controllers/PostsController/endpoints/getPostData.js deleted file mode 100755 index 649fa54a..00000000 --- a/packages/server/services/main/controllers/PostsController/endpoints/getPostData.js +++ /dev/null @@ -1,21 +0,0 @@ -import { GetPostData } from "../services" - -export default { - method: "GET", - route: "/post/:post_id", - middlewares: ["withOptionalAuthentication"], - fn: async (req, res) => { - let post = await GetPostData({ - post_id: req.params.post_id, - for_user_id: req.user?._id.toString(), - }).catch((error) => { - res.status(404).json({ error: error.message }) - - return null - }) - - if (!post) return - - return res.json(post) - } -} \ No newline at end of file diff --git a/packages/server/services/main/controllers/PostsController/endpoints/getPostFromUser.js b/packages/server/services/main/controllers/PostsController/endpoints/getPostFromUser.js deleted file mode 100755 index bce72f75..00000000 --- a/packages/server/services/main/controllers/PostsController/endpoints/getPostFromUser.js +++ /dev/null @@ -1,17 +0,0 @@ -import { GetPostData } from "../services" - -export default { - method: "GET", - route: "/user/:user_id", - middlewares: ["withOptionalAuthentication"], - fn: async (req, res) => { - let posts = await GetPostData({ - limit: req.query?.limit, - skip: req.query?.trim, - for_user_id: req.user?._id.toString(), - from_user_id: req.params.user_id, - }) - - return res.json(posts) - } -} \ No newline at end of file diff --git a/packages/server/services/main/controllers/PostsController/endpoints/getPostReplies.js b/packages/server/services/main/controllers/PostsController/endpoints/getPostReplies.js deleted file mode 100755 index bd3571ea..00000000 --- a/packages/server/services/main/controllers/PostsController/endpoints/getPostReplies.js +++ /dev/null @@ -1,29 +0,0 @@ -import { Post } from "@db_models" -import fullfillPostsData from "@utils/fullfillPostsData" - -export default { - method: "GET", - route: "/post/:post_id/replies", - middlewares: ["withOptionalAuthentication"], - fn: async (req, res) => { - const { - limit = 50, - offset = 0, - } = req.query - - let replies = await Post.find({ - reply_to: req.params.post_id, - }) - .skip(offset) - .limit(limit) - .sort({ created_at: -1 }) - - replies = await fullfillPostsData({ - posts: replies, - for_user_id: req.user?._id.toString(), - skip: offset, - }) - - return res.json(replies) - } -} \ No newline at end of file diff --git a/packages/server/services/main/controllers/PostsController/endpoints/savedPosts.js b/packages/server/services/main/controllers/PostsController/endpoints/savedPosts.js deleted file mode 100755 index d4bd4f3a..00000000 --- a/packages/server/services/main/controllers/PostsController/endpoints/savedPosts.js +++ /dev/null @@ -1,20 +0,0 @@ -import { Schematized } from "@lib" -import { GetPostData } from "../services" - -export default { - method: "GET", - route: "/saved", - middlewares: ["withAuthentication"], - fn: Schematized({ - select: ["user_id"] - }, async (req, res) => { - let posts = await GetPostData({ - limit: req.query?.limit, - skip: req.query?.trim, - for_user_id: req.user?._id.toString(), - savedOnly: true, - }) - - return res.json(posts) - }) -} \ No newline at end of file diff --git a/packages/server/services/main/controllers/PostsController/endpoints/toogleLike.js b/packages/server/services/main/controllers/PostsController/endpoints/toogleLike.js deleted file mode 100755 index bece5d19..00000000 --- a/packages/server/services/main/controllers/PostsController/endpoints/toogleLike.js +++ /dev/null @@ -1,29 +0,0 @@ -import { Schematized } from "@lib" -import { ToogleLike } from "../services" - -export default { - method: "POST", - route: "/:post_id/toggle_like", - middlewares: ["withAuthentication"], - fn: Schematized({ - select: ["to"], - }, async (req, res) => { - const post = await ToogleLike({ - user_id: req.user._id.toString(), - post_id: req.params.post_id, - to: req.selection.to, - }).catch((err) => { - res.status(400).json({ - error: err.message - }) - return false - }) - - if (!post) return - - return res.json({ - success: true, - post - }) - }) -} \ No newline at end of file diff --git a/packages/server/services/main/controllers/PostsController/endpoints/toogleSave.js b/packages/server/services/main/controllers/PostsController/endpoints/toogleSave.js deleted file mode 100755 index cbe54e6d..00000000 --- a/packages/server/services/main/controllers/PostsController/endpoints/toogleSave.js +++ /dev/null @@ -1,25 +0,0 @@ -import { ToogleSavePost } from "../services" - -export default { - method: "POST", - route: "/:post_id/toggle_save", - middlewares: ["withAuthentication"], - fn: async (req, res) => { - const post = await ToogleSavePost({ - user_id: req.user._id.toString(), - post_id: req.params.post_id, - }).catch((err) => { - res.status(400).json({ - error: err.message - }) - return false - }) - - if (!post) return - - return res.json({ - success: true, - post - }) - } -} \ No newline at end of file diff --git a/packages/server/services/main/controllers/PostsController/index.js b/packages/server/services/main/controllers/PostsController/index.js deleted file mode 100755 index 6bed9f4a..00000000 --- a/packages/server/services/main/controllers/PostsController/index.js +++ /dev/null @@ -1,19 +0,0 @@ -import { Controller } from "linebridge/dist/server" -import generateEndpointsFromDir from "linebridge/dist/server/lib/generateEndpointsFromDir" - -export default class PostsController extends Controller { - static refName = "PostsController" - static useRoute = "/posts" - - httpEndpoints = generateEndpointsFromDir(__dirname + "/endpoints") - - // put = { - // "/:post_id": { - // middlewares: ["withAuthentication"], - // fn: (req, res) => { - // // TODO: Implement Post update - // return res.status(501).json({ error: "Not implemented" }) - // } - // } - // } -} \ No newline at end of file diff --git a/packages/server/services/main/controllers/PostsController/services/createPost.js b/packages/server/services/main/controllers/PostsController/services/createPost.js deleted file mode 100755 index 058b11db..00000000 --- a/packages/server/services/main/controllers/PostsController/services/createPost.js +++ /dev/null @@ -1,45 +0,0 @@ -import momentTimezone from "moment-timezone" -import { Post } from "@db_models" - -import getPostData from "./getPostData" -import flagNsfwByAttachments from "./flagNsfwByAttachments" - -export default async (payload) => { - let { user_id, message, attachments, timestamp, reply_to } = payload - - // check if is a Array and have at least one element - const isAttachmentsValid = Array.isArray(attachments) && attachments.length > 0 - - if (!isAttachmentsValid && !message) { - throw new Error("Cannot create a post without message or attachments") - } - - if (message) { - message = String(message).toString().trim() - } - - const current_timezone = momentTimezone.tz.guess() - const created_at = momentTimezone.tz(Date.now(), current_timezone).format() - - const post = new Post({ - created_at: created_at, - user_id: typeof user_id === "object" ? user_id.toString() : user_id, - message: message, - attachments: attachments ?? [], - timestamp: timestamp, - reply_to: reply_to, - flags: [], - }) - - await post.save() - - const resultPost = await getPostData({ post_id: post._id.toString() }) - - global.engine.ws.io.of("/").emit(`post.new`, resultPost) - global.engine.ws.io.of("/").emit(`post.new.${post.user_id}`, resultPost) - - // push to background job to check if is NSFW - flagNsfwByAttachments(post._id.toString()) - - return post -} \ No newline at end of file diff --git a/packages/server/services/main/controllers/PostsController/services/deletePost.js b/packages/server/services/main/controllers/PostsController/services/deletePost.js deleted file mode 100755 index 1d630def..00000000 --- a/packages/server/services/main/controllers/PostsController/services/deletePost.js +++ /dev/null @@ -1,33 +0,0 @@ -import { Post, User } from "@db_models" - -export default async (payload) => { - const { post_id, by_user_id } = payload - - if (!by_user_id) { - throw new Error("by_user_id not provided") - } - - const post = await Post.findById(post_id) - - if (!post) { - throw new Error("Post not found") - } - - const userData = await User.findById(by_user_id) - - if (!userData) { - throw new Error("User not found") - } - - const hasAdmin = userData.roles.includes("admin") - - // check if user is the owner of the post - if (post.user_id !== by_user_id && !hasAdmin) { - throw new Error("You are not allowed to delete this post") - } - - await post.remove() - global.engine.ws.io.of("/").emit(`post.delete`, post_id) - - return post.toObject() -} \ No newline at end of file diff --git a/packages/server/services/main/controllers/PostsController/services/flagNsfwByAttachments.js b/packages/server/services/main/controllers/PostsController/services/flagNsfwByAttachments.js deleted file mode 100755 index 27b3d8ec..00000000 --- a/packages/server/services/main/controllers/PostsController/services/flagNsfwByAttachments.js +++ /dev/null @@ -1,54 +0,0 @@ -import { Post } from "@db_models" -import indecentPrediction from "../../../utils/indecent-prediction" -import isNSFW from "../../../utils/is-nsfw" - -import modifyPostData from "./modifyPostData" - -export default async (post_id) => { - try { - if (!post_id) { - throw new Error("Post ID is required") - } - - let post = await Post.findById(post_id) - - if (!post) { - throw new Error("Post not found") - } - - let flags = [] - - // run indecentPrediction to all attachments - if (Array.isArray(post.attachments) && post.attachments.length > 0) { - for await (const attachment of post.attachments) { - const prediction = await indecentPrediction({ - url: attachment.url, - }).catch((err) => { - console.log("Error while checking", attachment, err) - return null - }) - - if (prediction) { - const isNsfw = isNSFW(prediction) - - if (isNsfw) { - flags.push("nsfw") - } - } - } - } - - // if is there new flags update post - if (post.flags !== flags) { - await modifyPostData(post_id, { - flags: flags, - }) - } - - return flags - } catch (error) { - console.error(error) - - return [] - } -} \ No newline at end of file diff --git a/packages/server/services/main/controllers/PostsController/services/getPostData.js b/packages/server/services/main/controllers/PostsController/services/getPostData.js deleted file mode 100755 index b1cb52cd..00000000 --- a/packages/server/services/main/controllers/PostsController/services/getPostData.js +++ /dev/null @@ -1,71 +0,0 @@ -import { Post, SavedPost } from "@db_models" -import fullfillPostsData from "@utils/fullfillPostsData" - -export default async (payload) => { - let { - from_user_id, - for_user_id, - post_id, - query = {}, - skip = 0, - limit = 20, - sort = { created_at: -1 }, - savedOnly = false, - } = payload - - let posts = [] - let savedPostsIds = [] - - // if for_user_id is provided, get saved posts - if (for_user_id) { - const savedPosts = await SavedPost.find({ user_id: for_user_id }) - .sort({ saved_at: -1 }) - - savedPostsIds = savedPosts.map((savedPost) => savedPost.post_id) - } - - // if from_user_id is provided, get posts from that user - if (from_user_id) { - query.user_id = from_user_id - } - - // if savedOnly is true,set to query to get only saved posts - if (savedOnly) { - query._id = { $in: savedPostsIds } - } - - if (post_id) { - const post = await Post.findById(post_id).catch(() => false) - - posts = [post] - } else { - posts = await Post.find({ ...query }) - .sort(sort) - .skip(skip) - .limit(limit) - } - - // short posts if is savedOnly argument - if (savedOnly) { - posts.sort((a, b) => { - return ( - savedPostsIds.indexOf(a._id.toString()) - - savedPostsIds.indexOf(b._id.toString()) - ) - }) - } - - // fullfill data - posts = await fullfillPostsData({ - posts, - for_user_id, - skip, - }) - - // if post_id is specified, return only one post - if (post_id) { - return posts[0] - } - - return posts -} \ No newline at end of file diff --git a/packages/server/services/main/controllers/PostsController/services/index.js b/packages/server/services/main/controllers/PostsController/services/index.js deleted file mode 100755 index 51f68782..00000000 --- a/packages/server/services/main/controllers/PostsController/services/index.js +++ /dev/null @@ -1,8 +0,0 @@ -export { default as CreatePost } from "./createPost" -export { default as ToogleLike } from "./toggleLike" -export { default as ToogleSavePost } from "./togglePostSave" - -export { default as GetPostData } from "./getPostData" -export { default as DeletePost } from "./deletePost" - -export { default as ModifyPostData } from "./modifyPostData" diff --git a/packages/server/services/main/controllers/PostsController/services/modifyPostData.js b/packages/server/services/main/controllers/PostsController/services/modifyPostData.js deleted file mode 100755 index ac8ee7bb..00000000 --- a/packages/server/services/main/controllers/PostsController/services/modifyPostData.js +++ /dev/null @@ -1,31 +0,0 @@ -import { Post } from "@db_models" -import getPostData from "./getPostData" - -export default async (post_id, modification) => { - if (!post_id) { - throw new Error("Cannot modify post data: post not found") - } - - let post = await getPostData({ post_id: post_id }) - - if (!post) { - throw new Error("Cannot modify post data: post not found") - } - - if (typeof modification === "object") { - const result = await Post.findByIdAndUpdate(post_id, modification) - - await result.save() - - post = { - ...post, - ...result.toObject(), - ...modification, - } - } - - global.engine.ws.io.of("/").emit(`post.dataUpdate`, post) - global.engine.ws.io.of("/").emit(`post.dataUpdate.${post_id}`, post) - - return post -} \ No newline at end of file diff --git a/packages/server/services/main/controllers/PostsController/services/toggleLike.js b/packages/server/services/main/controllers/PostsController/services/toggleLike.js deleted file mode 100755 index 3d43da85..00000000 --- a/packages/server/services/main/controllers/PostsController/services/toggleLike.js +++ /dev/null @@ -1,37 +0,0 @@ -import { PostLike } from "@db_models" - -export default async (payload) => { - let { post_id, user_id, to } = payload - - let likeObj = await PostLike.findOne({ - post_id, - user_id, - }).catch(() => false) - - if (typeof to === "undefined") { - if (likeObj) { - to = false - } else { - to = true - } - } - - if (to) { - likeObj = new PostLike({ - post_id, - user_id, - }) - - await likeObj.save() - } else { - await PostLike.findByIdAndDelete(likeObj._id) - } - - global.engine.ws.io.of("/").emit(`post.${post_id}.likes.update`, { - to, - post_id, - user_id, - }) - - return likeObj -} \ No newline at end of file diff --git a/packages/server/services/main/controllers/PostsController/services/togglePostSave.js b/packages/server/services/main/controllers/PostsController/services/togglePostSave.js deleted file mode 100755 index fafc6aaa..00000000 --- a/packages/server/services/main/controllers/PostsController/services/togglePostSave.js +++ /dev/null @@ -1,30 +0,0 @@ -import { SavedPost } from "@db_models" - -export default async (payload) => { - let { post_id, user_id } = payload - - if (!post_id || !user_id) { - throw new Error("Missing post_id or user_id") - } - - let post = await SavedPost.findOne({ post_id, user_id }).catch((err) => { - return false - }) - - if (post) { - await SavedPost.findByIdAndDelete(post._id).catch((err) => { - throw new Error("Cannot delete saved post") - }) - } else { - post = new SavedPost({ - post_id, - user_id, - }) - - await post.save().catch((err) => { - throw new Error("Cannot save post") - }) - } - - return post -} \ No newline at end of file diff --git a/packages/server/services/main/controllers/RolesController/index.js b/packages/server/services/main/controllers/RolesController/index.js deleted file mode 100755 index ce55cfdf..00000000 --- a/packages/server/services/main/controllers/RolesController/index.js +++ /dev/null @@ -1,115 +0,0 @@ -import { Controller } from "linebridge/dist/server" -import { Role, User } from "@db_models" -import { Schematized } from "@lib" - -export default class RolesController extends Controller { - static refName = "RolesController" - static useMiddlewares = ["roles"] - - httpEndpoints = { - get: { - "/roles": Schematized({ - select: ["user_id", "username"], - }, async (req, res) => { - const roles = await Role.find() - - return res.json(roles) - }), - "/roles/self": { - middlewares: ["withAuthentication"], - fn: async (req, res) => { - const user = await User.findOne({ - _id: req.user._id.toString(), - }) - - if (!user) { - return res.status(404).json({ error: "No user founded" }) - } - - return res.json(user.roles) - } - }, - }, - - post: { - "/role": { - middlewares: ["withAuthentication"], - fn: Schematized({ - required: ["name"], - select: ["name", "description"], - }, async (req, res) => { - await Role.findOne(req.selection).then((data) => { - if (data) { - return res.status(409).json("This role is already created") - } - - let role = new Role({ - name: req.selection.name, - description: req.selection.description, - }) - - role.save() - - return res.json(role) - }) - }) - }, - "/update_user_roles": { - middlewares: ["withAuthentication"], - fn: Schematized({ - required: ["update"], - select: ["update"], - }, async (req, res) => { - // check if issuer user is admin - if (!req.isAdmin()) { - return res.status(403).json("You do not have administrator permission") - } - - if (!Array.isArray(req.selection.update)) { - return res.status(400).json("Invalid update request") - } - - req.selection.update.forEach(async (update) => { - const user = await User.findById(update._id).catch(err => { - return false - }) - - console.log(update.roles) - - if (user) { - user.roles = update.roles - - await user.save() - } - }) - - return res.json("done") - }), - }, - }, - - delete: { - "/role": { - middlewares: ["withAuthentication"], - fn: Schematized({ - required: ["name"], - select: ["name"], - }, async (req, res) => { - if (req.selection.name === "admin") { - return res.status(409).json("You can't delete admin role") - } - - await Role.findOne(req.selection).then((data) => { - if (!data) { - return res.status(404).json("This role is not found") - } - - data.remove() - - return res.json(data) - }) - }) - }, - }, - } -} \ No newline at end of file diff --git a/packages/server/services/main/controllers/SessionController/endpoints/deleteAllSelfSessions.js b/packages/server/services/main/controllers/SessionController/endpoints/deleteAllSelfSessions.js deleted file mode 100755 index 002619e7..00000000 --- a/packages/server/services/main/controllers/SessionController/endpoints/deleteAllSelfSessions.js +++ /dev/null @@ -1,16 +0,0 @@ -export default { - method: "DELETE", - route: "/all", - middlewares: ["withAuthentication"], - fn: async (req, res) => { - const user_id = req.user._id.toString() - - const allSessions = await Session.deleteMany({ user_id }) - - if (allSessions) { - return res.json("done") - } - - return res.status(404).json("not found") - } -} \ No newline at end of file diff --git a/packages/server/services/main/controllers/SessionController/endpoints/deleteCurrentSession.js b/packages/server/services/main/controllers/SessionController/endpoints/deleteCurrentSession.js deleted file mode 100755 index 50539469..00000000 --- a/packages/server/services/main/controllers/SessionController/endpoints/deleteCurrentSession.js +++ /dev/null @@ -1,27 +0,0 @@ -import { Session } from "@db_models" - -export default { - method: "DELETE", - route: "/current", - middlewares: ["withAuthentication"], - fn: async (req, res) => { - const token = req.jwtToken - const user_id = req.user._id.toString() - - if (typeof token === "undefined") { - return res.status(400).json("Cannot access to token") - } - - const session = await Session.findOneAndDelete({ user_id, token }) - - if (session) { - return res.json({ - message: "done", - }) - } - - return res.status(404).json({ - error: "Session not found", - }) - }, -} \ No newline at end of file diff --git a/packages/server/services/main/controllers/SessionController/endpoints/getSessions.js b/packages/server/services/main/controllers/SessionController/endpoints/getSessions.js deleted file mode 100755 index ba452c32..00000000 --- a/packages/server/services/main/controllers/SessionController/endpoints/getSessions.js +++ /dev/null @@ -1,13 +0,0 @@ -import { Session } from "@db_models" - -export default { - method: "GET", - route: "/all", - middlewares: ["withAuthentication"], - fn: async (req, res) => { - const sessions = await Session.find({ user_id: req.user._id.toString() }, { token: 0 }) - .sort({ date: -1 }) - - return res.json(sessions) - }, -} \ No newline at end of file diff --git a/packages/server/services/main/controllers/SessionController/endpoints/regenerateSessionToken.js b/packages/server/services/main/controllers/SessionController/endpoints/regenerateSessionToken.js deleted file mode 100755 index 56a657be..00000000 --- a/packages/server/services/main/controllers/SessionController/endpoints/regenerateSessionToken.js +++ /dev/null @@ -1,19 +0,0 @@ -import Token from "@lib/token" - -export default { - method: "POST", - route: "/regenerate", - fn: async (req, res) => { - const { expiredToken, refreshToken } = req.body - - const token = await Token.regenerate(expiredToken, refreshToken).catch((error) => { - res.status(400).json({ error: error.message }) - - return null - }) - - if (!token) return - - return res.json({ token }) - }, -} \ No newline at end of file diff --git a/packages/server/services/main/controllers/SessionController/endpoints/validateSession.js b/packages/server/services/main/controllers/SessionController/endpoints/validateSession.js deleted file mode 100755 index 795cc116..00000000 --- a/packages/server/services/main/controllers/SessionController/endpoints/validateSession.js +++ /dev/null @@ -1,13 +0,0 @@ -import Token from "@lib/token" - -export default { - method: "POST", - route: "/validate", - fn: async (req, res) => { - const token = req.body.token - - const result = await Token.validate(token) - - return res.json(result) - }, -} \ No newline at end of file diff --git a/packages/server/services/main/controllers/SessionController/index.js b/packages/server/services/main/controllers/SessionController/index.js deleted file mode 100755 index 4455376d..00000000 --- a/packages/server/services/main/controllers/SessionController/index.js +++ /dev/null @@ -1,9 +0,0 @@ -import { Controller } from "linebridge/dist/server" -import generateEndpointsFromDir from "linebridge/dist/server/lib/generateEndpointsFromDir" - -export default class SessionController extends Controller { - static refName = "SessionController" - static useRoute = "/session" - - httpEndpoints = generateEndpointsFromDir(__dirname + "/endpoints") -} \ No newline at end of file diff --git a/packages/server/services/main/controllers/UserController/endpoints/checkEmailAvailable.js b/packages/server/services/main/controllers/UserController/endpoints/checkEmailAvailable.js deleted file mode 100755 index 629153c7..00000000 --- a/packages/server/services/main/controllers/UserController/endpoints/checkEmailAvailable.js +++ /dev/null @@ -1,15 +0,0 @@ -import { User } from "@db_models" - -export default { - method: "GET", - route: "/email_available", - fn: async (req, res) => { - const user = await User.findOne({ - email: req.query.email, - }) - - return res.json({ - available: !user, - }) - } -} \ No newline at end of file diff --git a/packages/server/services/main/controllers/UserController/endpoints/checkUsernameAvailable.js b/packages/server/services/main/controllers/UserController/endpoints/checkUsernameAvailable.js deleted file mode 100755 index 64d2066a..00000000 --- a/packages/server/services/main/controllers/UserController/endpoints/checkUsernameAvailable.js +++ /dev/null @@ -1,15 +0,0 @@ -import { User } from "@db_models" - -export default { - method: "GET", - route: "/username_available", - fn: async (req, res) => { - const user = await User.findOne({ - username: req.query.username, - }) - - return res.json({ - available: !user, - }) - } -} \ No newline at end of file diff --git a/packages/server/services/main/controllers/UserController/endpoints/getConnectedFollowingUsers.js b/packages/server/services/main/controllers/UserController/endpoints/getConnectedFollowingUsers.js deleted file mode 100755 index 4e488d11..00000000 --- a/packages/server/services/main/controllers/UserController/endpoints/getConnectedFollowingUsers.js +++ /dev/null @@ -1,14 +0,0 @@ -import getConnectedUsersFollowing from "../services/getConnectedUsersFollowing" - -export default { - method: "GET", - route: "/connected/followers", - middlewares: ["withAuthentication"], - fn: async (req, res) => { - const users = await getConnectedUsersFollowing({ - from_user_id: req.user._id.toString(), - }) - - return res.json(users) - } -} \ No newline at end of file diff --git a/packages/server/services/main/controllers/UserController/endpoints/getUserData.js b/packages/server/services/main/controllers/UserController/endpoints/getUserData.js deleted file mode 100755 index 328fc93a..00000000 --- a/packages/server/services/main/controllers/UserController/endpoints/getUserData.js +++ /dev/null @@ -1,41 +0,0 @@ -import lodash from "lodash" -import { User } from "@db_models" - -const publicGetters = [ - "_id", - "username", - "fullName", - "avatar", - "roles", - "badges", - "cover", - "verified", - "description", - "links", - "createdAt", -] - -export default { - method: "GET", - route: "/:user_id/data", - middlewares: ["withAuthentication"], - fn: async (req, res) => { - let user = await User.findOne({ - _id: req.params.user_id, - }) - - if (!user) { - return res.status(404).json({ error: "User not exists" }) - } - - if (req.user.roles.includes("admin")) { - return res.json(user) - } - - if (req.user._id.toString() !== user._id.toString()) { - user = lodash.pick(user, publicGetters) - } - - return res.json(user) - } -} \ No newline at end of file diff --git a/packages/server/services/main/controllers/UserController/endpoints/getUsersData.js b/packages/server/services/main/controllers/UserController/endpoints/getUsersData.js deleted file mode 100755 index 047af1a7..00000000 --- a/packages/server/services/main/controllers/UserController/endpoints/getUsersData.js +++ /dev/null @@ -1,69 +0,0 @@ -import { Schematized } from "@lib" -import { User } from "@db_models" - -export default { - method: "GET", - route: "/users/data", - middlewares: ["withAuthentication"], - fn: Schematized({ - select: ["_id", "username"], - }, async (req, res) => { - let result = [] - let selectQueryKeys = [] - - if (Array.isArray(req.selection._id)) { - for await (let _id of req.selection._id) { - const user = await User.findById(_id).catch(err => { - return false - }) - if (user) { - result.push(user) - } - } - } else { - result = await User.find(req.selection, { username: 1, fullName: 1, _id: 1, roles: 1, avatar: 1 }) - } - - if (req.query?.select) { - try { - req.query.select = JSON.parse(req.query.select) - } catch (error) { - req.query.select = {} - } - - selectQueryKeys = Object.keys(req.query.select) - } - - if (selectQueryKeys.length > 0) { - result = result.filter(user => { - let pass = false - const selectFilter = req.query.select - - selectQueryKeys.forEach(key => { - if (Array.isArray(selectFilter[key]) && Array.isArray(user[key])) { - // check if arrays includes any of the values - pass = selectFilter[key].some(val => user[key].includes(val)) - } else if (typeof selectFilter[key] === "object" && typeof user[key] === "object") { - // check if objects includes any of the values - Object.keys(selectFilter[key]).forEach(objKey => { - pass = user[key][objKey] === selectFilter[key][objKey] - }) - } - - // check if strings includes any of the values - if (typeof selectFilter[key] === "string" && typeof user[key] === "string") { - pass = selectFilter[key].split(",").some(val => user[key].includes(val)) - } - }) - - return pass - }) - } - - if (!result) { - return res.status(404).json({ error: "Users not found" }) - } - - return res.json(result) - }) -} \ No newline at end of file diff --git a/packages/server/services/main/controllers/UserController/endpoints/resolveUserIdFromUsername.js b/packages/server/services/main/controllers/UserController/endpoints/resolveUserIdFromUsername.js deleted file mode 100755 index bb97b4ae..00000000 --- a/packages/server/services/main/controllers/UserController/endpoints/resolveUserIdFromUsername.js +++ /dev/null @@ -1,19 +0,0 @@ -import { User } from "@db_models" - -export default { - method: "GET", - route: "/user_id/:username", - middlewares: ["withAuthentication"], - fn: async (req, res) => { - const user = await User.findOne({ username: req.params.username }) - - if (!user) { - return res.status(404).json({ error: "User not exists" }) - } - - return res.json({ - username: user.username, - user_id: user._id, - }) - } -} \ No newline at end of file diff --git a/packages/server/services/main/controllers/UserController/endpoints/selfDeletePublicName.js b/packages/server/services/main/controllers/UserController/endpoints/selfDeletePublicName.js deleted file mode 100755 index 954252e9..00000000 --- a/packages/server/services/main/controllers/UserController/endpoints/selfDeletePublicName.js +++ /dev/null @@ -1,27 +0,0 @@ -import UpdateUserData from "../services/updateUserData" - -export default { - method: "DELETE", - route: "/self/public_name", - middlewares: ["withAuthentication"], - fn: async (req, res) => { - const user_id = req.user._id.toString() - - UpdateUserData({ - user_id: user_id, - update: { - fullName: undefined - } - }) - .then((user) => { - return res.json({ - ...user - }) - }) - .catch((err) => { - return res.json(500).json({ - error: err.message - }) - }) - }, -} \ No newline at end of file diff --git a/packages/server/services/main/controllers/UserController/endpoints/selfUpdateData.js b/packages/server/services/main/controllers/UserController/endpoints/selfUpdateData.js deleted file mode 100755 index 24092de5..00000000 --- a/packages/server/services/main/controllers/UserController/endpoints/selfUpdateData.js +++ /dev/null @@ -1,75 +0,0 @@ -import { Schematized } from "@lib" -import { User } from "@db_models" -import UpdateUserData from "../services/updateUserData" - -const AllowedPublicUpdateFields = [ - "fullName", - "avatar", - "email", - "cover", - "description", - "location", - "links", -] - -const MaxStringsLengths = { - fullName: 120, - email: 320, - description: 320, -} - -export default { - method: "POST", - route: "/self/update_data", - middlewares: ["withAuthentication"], - fn: Schematized({ - required: ["update"], - select: ["update"], - }, async (req, res) => { - const user_id = req.user._id.toString() - - let update = {} - - AllowedPublicUpdateFields.forEach((key) => { - if (typeof req.selection.update[key] !== "undefined") { - // sanitize update - // check maximung strings length - if (typeof req.selection.update[key] === "string" && MaxStringsLengths[key]) { - if (req.selection.update[key].length > MaxStringsLengths[key]) { - // create a substring - req.selection.update[key] = req.selection.update[key].substring(0, MaxStringsLengths[key]) - } - } - - update[key] = req.selection.update[key] - } - }) - - // check if email is already in use - if (typeof update.email !== "undefined") { - const user = await User.findOne({ - email: update.email, - }) - - if (user) { - return res.status(400).json({ - error: "Email is already in use", - }) - } - } - - UpdateUserData({ - user_id: user_id, - update: update, - }).then((user) => { - return res.json({ - ...user - }) - }) - .catch((err) => { - return res.json(500).json({ - error: err.message - }) - }) - }), -} \ No newline at end of file diff --git a/packages/server/services/main/controllers/UserController/endpoints/selfUpdatePassword.js b/packages/server/services/main/controllers/UserController/endpoints/selfUpdatePassword.js deleted file mode 100755 index 81726277..00000000 --- a/packages/server/services/main/controllers/UserController/endpoints/selfUpdatePassword.js +++ /dev/null @@ -1,41 +0,0 @@ -import { Schematized } from "@lib" -import { User } from "@db_models" - -import updateUserPassword from "../services/updateUserPassword" -import bcrypt from "bcrypt" - -export default { - method: "POST", - route: "/self/update_password", - middlewares: ["withAuthentication"], - fn: Schematized({ - required: ["currentPassword", "newPassword"], - select: ["currentPassword", "newPassword",] - }, async (req, res) => { - const user = await User.findById(req.user._id).select("+password") - - if (!user) { - return res.status(404).json({ message: "User not found" }) - } - - const isPasswordValid = await bcrypt.compareSync(req.selection.currentPassword, user.password) - - if (!isPasswordValid) { - return res.status(401).json({ - message: "Current password dont match" - }) - } - - const result = await updateUserPassword({ - user_id: req.user._id, - password: req.selection.newPassword, - }).catch((error) => { - res.status(500).json({ message: error.message }) - return null - }) - - if (result) { - return res.json(result) - } - }) -} \ No newline at end of file diff --git a/packages/server/services/main/controllers/UserController/index.js b/packages/server/services/main/controllers/UserController/index.js deleted file mode 100755 index 9da88c1e..00000000 --- a/packages/server/services/main/controllers/UserController/index.js +++ /dev/null @@ -1,9 +0,0 @@ -import { Controller } from "linebridge/dist/server" -import generateEndpointsFromDir from "linebridge/dist/server/lib/generateEndpointsFromDir" - -export default class UserController extends Controller { - static refName = "UserController" - static useRoute = "/user" - - httpEndpoints = generateEndpointsFromDir(__dirname + "/endpoints") -} \ No newline at end of file diff --git a/packages/server/services/main/controllers/UserController/services/createUser.js b/packages/server/services/main/controllers/UserController/services/createUser.js deleted file mode 100755 index d7683558..00000000 --- a/packages/server/services/main/controllers/UserController/services/createUser.js +++ /dev/null @@ -1,54 +0,0 @@ -import { User } from "@db_models" -import Avatars from "dicebar_lib" -import bcrypt from "bcrypt" - -export default async function (payload) { - let { username, password, email, fullName, roles, avatar } = payload - - // if username has capital letters, throw error - if (username !== username.toLowerCase()) { - throw new Error("Username must be lowercase") - } - - // make sure the username has no spaces - if (username.includes(" ")) { - throw new Error("Username cannot contain spaces") - } - - // make sure the username has no valid characters. Only letters, numbers, and underscores - if (!/^[a-z0-9_]+$/.test(username)) { - throw new Error("Username can only contain letters, numbers, and underscores") - } - - // check if username is already taken - const existentUser = await User.findOne({ username: username }) - - if (existentUser) { - throw new Error("User already exists") - } - - // check if the email is already in use - const existentEmail = await User.findOne({ email: email }) - - if (existentEmail) { - throw new Error("Email already in use") - } - - // hash the password - const hash = bcrypt.hashSync(password, parseInt(process.env.BCRYPT_ROUNDS ?? 3)) - - // create the doc - let user = new User({ - username: username, - password: hash, - email: email, - fullName: fullName, - avatar: avatar ?? Avatars.generate({ seed: username, type: "initials" }).uri, - roles: roles, - createdAt: new Date().getTime(), - }) - - await user.save() - - return user -} \ No newline at end of file diff --git a/packages/server/services/main/controllers/UserController/services/getConnectedUsersFollowing.js b/packages/server/services/main/controllers/UserController/services/getConnectedUsersFollowing.js deleted file mode 100755 index 4cdfa5c1..00000000 --- a/packages/server/services/main/controllers/UserController/services/getConnectedUsersFollowing.js +++ /dev/null @@ -1,25 +0,0 @@ -import { UserFollow } from "@db_models" - -export default async (payload = {}) => { - const { from_user_id } = payload - - // get all the users that are following - const following = await UserFollow.find({ - user_id: from_user_id, - }) - - // check if following users are connected - const connectedUsers = [] - - following.forEach((follow) => { - const connectedClient = global.websocket_instance.clients.find((client) => { - return client.user_id === follow.to - }) - - if (connectedClient) { - connectedUsers.push(connectedClient.user_id) - } - }) - - return connectedUsers -} \ No newline at end of file diff --git a/packages/server/services/main/controllers/UserController/services/updateUserPassword.js b/packages/server/services/main/controllers/UserController/services/updateUserPassword.js deleted file mode 100755 index 9a4f18da..00000000 --- a/packages/server/services/main/controllers/UserController/services/updateUserPassword.js +++ /dev/null @@ -1,32 +0,0 @@ -import { User } from "@db_models" -import bcrypt from "bcrypt" - -export default async function (payload) { - const { user_id, password } = payload - - if (!user_id) { - throw new Error("Missing user_id") - } - - if (!password) { - throw new Error("Missing password") - } - - const user = await User.findById(user_id) - - if (!user) { - throw new Error("User not found") - } - - // hash the password - const hash = bcrypt.hashSync(password, parseInt(process.env.BCRYPT_ROUNDS ?? 3)) - - user.password = hash - - await user.save() - - return { - status: "ok", - message: "Password updated successfully", - } -} \ No newline at end of file diff --git a/packages/server/services/main/main.service.js b/packages/server/services/main/main.service.js index 7d22e91d..b9171f59 100755 --- a/packages/server/services/main/main.service.js +++ b/packages/server/services/main/main.service.js @@ -29,7 +29,6 @@ export default class API extends Server { ...SharedMiddlewares } - controllers = require("@controllers") events = require("./events") storage = global.storage = StorageClient() diff --git a/packages/server/services/main/controllers/FeedController/services/getPlaylistsFromFollowing.js b/packages/server/services/music/getPlaylistsFromFollowing.js similarity index 100% rename from packages/server/services/main/controllers/FeedController/services/getPlaylistsFromFollowing.js rename to packages/server/services/music/getPlaylistsFromFollowing.js diff --git a/packages/server/services/main/controllers/FeedController/services/getReleasesFromFollowing.js b/packages/server/services/music/getReleasesFromFollowing.js similarity index 100% rename from packages/server/services/main/controllers/FeedController/services/getReleasesFromFollowing.js rename to packages/server/services/music/getReleasesFromFollowing.js diff --git a/packages/server/services/posts/classes/posts/methods/toggleLike.js b/packages/server/services/posts/classes/posts/methods/toggleLike.js index f31cee20..7c7c7f54 100644 --- a/packages/server/services/posts/classes/posts/methods/toggleLike.js +++ b/packages/server/services/posts/classes/posts/methods/toggleLike.js @@ -9,7 +9,7 @@ export default async (payload = {}) => { // check if post exist let existPost = await Post.findOne({ - post_id, + _id: post_id, }) if (!existPost) { diff --git a/packages/server/services/posts/classes/posts/methods/toggleSave.js b/packages/server/services/posts/classes/posts/methods/toggleSave.js index 3ae9c83a..df6f686f 100644 --- a/packages/server/services/posts/classes/posts/methods/toggleSave.js +++ b/packages/server/services/posts/classes/posts/methods/toggleSave.js @@ -9,7 +9,7 @@ export default async (payload = {}) => { // check if post exist let existPost = await Post.findOne({ - post_id, + _id: post_id, }) if (!existPost) { diff --git a/packages/server/services/posts/package.json b/packages/server/services/posts/package.json index e52dc066..e76389ea 100644 --- a/packages/server/services/posts/package.json +++ b/packages/server/services/posts/package.json @@ -3,6 +3,10 @@ "version": "1.0.0", "main": "index.js", "license": "MIT", + "proxy": { + "namespace": "/posts", + "port": 3001 + }, "dependencies": { "moment-timezone": "^0.5.45" } diff --git a/packages/server/services/posts/posts.service.js b/packages/server/services/posts/posts.service.js index 6f3bde3b..b1cb218d 100644 --- a/packages/server/services/posts/posts.service.js +++ b/packages/server/services/posts/posts.service.js @@ -20,10 +20,6 @@ export default class API extends Server { redis: RedisClient() } - events = { - - } - async onInitialize() { await this.contexts.db.initialize() await this.contexts.redis.initialize() diff --git a/packages/server/services/main/controllers/StreamingController/endpoints/deleteStreamingProfile.js b/packages/server/services/tv/StreamingController/endpoints/deleteStreamingProfile.js similarity index 100% rename from packages/server/services/main/controllers/StreamingController/endpoints/deleteStreamingProfile.js rename to packages/server/services/tv/StreamingController/endpoints/deleteStreamingProfile.js diff --git a/packages/server/services/main/controllers/StreamingController/endpoints/getProfileFromStreamKey.js b/packages/server/services/tv/StreamingController/endpoints/getProfileFromStreamKey.js similarity index 100% rename from packages/server/services/main/controllers/StreamingController/endpoints/getProfileFromStreamKey.js rename to packages/server/services/tv/StreamingController/endpoints/getProfileFromStreamKey.js diff --git a/packages/server/services/main/controllers/StreamingController/endpoints/getProfilesVisibility.js b/packages/server/services/tv/StreamingController/endpoints/getProfilesVisibility.js similarity index 100% rename from packages/server/services/main/controllers/StreamingController/endpoints/getProfilesVisibility.js rename to packages/server/services/tv/StreamingController/endpoints/getProfilesVisibility.js diff --git a/packages/server/services/main/controllers/StreamingController/endpoints/getStreamingCategories.js b/packages/server/services/tv/StreamingController/endpoints/getStreamingCategories.js similarity index 100% rename from packages/server/services/main/controllers/StreamingController/endpoints/getStreamingCategories.js rename to packages/server/services/tv/StreamingController/endpoints/getStreamingCategories.js diff --git a/packages/server/services/main/controllers/StreamingController/endpoints/getStreamingProfiles.js b/packages/server/services/tv/StreamingController/endpoints/getStreamingProfiles.js similarity index 100% rename from packages/server/services/main/controllers/StreamingController/endpoints/getStreamingProfiles.js rename to packages/server/services/tv/StreamingController/endpoints/getStreamingProfiles.js diff --git a/packages/server/services/main/controllers/StreamingController/endpoints/getStreams.js b/packages/server/services/tv/StreamingController/endpoints/getStreams.js similarity index 100% rename from packages/server/services/main/controllers/StreamingController/endpoints/getStreams.js rename to packages/server/services/tv/StreamingController/endpoints/getStreams.js diff --git a/packages/server/services/main/controllers/StreamingController/endpoints/handleStreamPublish.js b/packages/server/services/tv/StreamingController/endpoints/handleStreamPublish.js similarity index 100% rename from packages/server/services/main/controllers/StreamingController/endpoints/handleStreamPublish.js rename to packages/server/services/tv/StreamingController/endpoints/handleStreamPublish.js diff --git a/packages/server/services/main/controllers/StreamingController/endpoints/handleStreamUnpublish.js b/packages/server/services/tv/StreamingController/endpoints/handleStreamUnpublish.js similarity index 100% rename from packages/server/services/main/controllers/StreamingController/endpoints/handleStreamUnpublish.js rename to packages/server/services/tv/StreamingController/endpoints/handleStreamUnpublish.js diff --git a/packages/server/services/main/controllers/StreamingController/endpoints/postStreamingProfile.js b/packages/server/services/tv/StreamingController/endpoints/postStreamingProfile.js similarity index 100% rename from packages/server/services/main/controllers/StreamingController/endpoints/postStreamingProfile.js rename to packages/server/services/tv/StreamingController/endpoints/postStreamingProfile.js diff --git a/packages/server/services/main/controllers/StreamingController/endpoints/regenerateStreamingKey.js b/packages/server/services/tv/StreamingController/endpoints/regenerateStreamingKey.js similarity index 100% rename from packages/server/services/main/controllers/StreamingController/endpoints/regenerateStreamingKey.js rename to packages/server/services/tv/StreamingController/endpoints/regenerateStreamingKey.js diff --git a/packages/server/services/main/controllers/StreamingController/index.js b/packages/server/services/tv/StreamingController/index.js similarity index 100% rename from packages/server/services/main/controllers/StreamingController/index.js rename to packages/server/services/tv/StreamingController/index.js diff --git a/packages/server/services/users/classes/users/index.js b/packages/server/services/users/classes/users/index.js index 20345c19..22c3c716 100644 --- a/packages/server/services/users/classes/users/index.js +++ b/packages/server/services/users/classes/users/index.js @@ -1,3 +1,7 @@ export default class Users { static data = require("./method/data").default + static toggleFollow = require("./method/toggleFollow").default + static getFollowers = require("./method/getFollowers").default + static resolveUserId = require("./method/resolveUserId").default + static update = require("./method/update").default } \ No newline at end of file diff --git a/packages/server/services/users/classes/users/method/data.js b/packages/server/services/users/classes/users/method/data.js index 56c43ac7..67ecfec9 100644 --- a/packages/server/services/users/classes/users/method/data.js +++ b/packages/server/services/users/classes/users/method/data.js @@ -1,4 +1,4 @@ -import { User } from "@db_models" +import { User, UserFollow } from "@db_models" export default async (payload = {}) => { const { user_id, from_user_id } = payload @@ -7,7 +7,7 @@ export default async (payload = {}) => { throw new OperationError(400, "Missing user_id") } - const user = await User.findOne({ + let user = await User.findOne({ _id: user_id, }).catch((err) => { return false @@ -17,5 +17,16 @@ export default async (payload = {}) => { throw new OperationError(404, "User not found") } + user = user.toObject() + + if (from_user_id) { + const isFollowed = await UserFollow.findOne({ + user_id: from_user_id, + to: user_id, + }).catch(() => false) + + user.following = !!isFollowed + } + return user } \ No newline at end of file diff --git a/packages/server/services/users/classes/users/method/getFollowers.js b/packages/server/services/users/classes/users/method/getFollowers.js new file mode 100644 index 00000000..663284bd --- /dev/null +++ b/packages/server/services/users/classes/users/method/getFollowers.js @@ -0,0 +1,35 @@ +import { User, UserFollow } from "@db_models" + +export default async (payload = {}) => { + const { user_id, data = false, limit = 50, offset = 0 } = payload + + if (!user_id) { + throw new OperationError(400, "Missing user_id") + } + + if (data) { + let followers = await UserFollow.find({ + to: user_id + }) + .limit(limit) + .skip(offset) + + const followersData = await User.find({ + _id: { + $in: followers.map((follow) => { + return follow.user_id + }) + } + }) + + return followersData + } else { + const count = await UserFollow.count({ + to: user_id + }) + + return { + count + } + } +} \ No newline at end of file diff --git a/packages/server/services/users/classes/users/method/resolveUserId.js b/packages/server/services/users/classes/users/method/resolveUserId.js new file mode 100644 index 00000000..ff9f7fa5 --- /dev/null +++ b/packages/server/services/users/classes/users/method/resolveUserId.js @@ -0,0 +1,23 @@ +import { User } from "@db_models" + +export default async (payload = {}) => { + const { username } = payload + + if (!username) { + throw new OperationError(400, "Missing username") + } + + let user = await User.findOne({ + username + }).catch((err) => { + return false + }) + + if (!user) { + throw new OperationError(404, "User not found") + } + + return { + user_id: user._id.toString() + } +} \ No newline at end of file diff --git a/packages/server/services/users/classes/users/method/toggleFollow.js b/packages/server/services/users/classes/users/method/toggleFollow.js new file mode 100644 index 00000000..dbd36af9 --- /dev/null +++ b/packages/server/services/users/classes/users/method/toggleFollow.js @@ -0,0 +1,51 @@ +import { User, UserFollow } from "@db_models" + +export default async (payload = {}) => { + let { user_id, from_user_id, to } = payload + + if (typeof from_user_id === "undefined") { + throw new OperationError(400, "[from_user_id] not provided") + } + if (typeof user_id === "undefined") { + throw new OperationError(400, "[user_id] not provided") + } + + const user = await User.findById(user_id).catch((err) => { + return false + }) + + if (!user) { + throw new OperationError(404, "User not found") + } + + let followObj = await UserFollow.findOne({ + user_id: from_user_id, + to: user_id, + }).catch((err) => { + return false + }) + + if (typeof to === "undefined") { + if (followObj) { + to = false + } else { + to = true + } + } + + if (to === true) { + followObj = new UserFollow({ + user_id: from_user_id, + to: user_id, + }) + + await followObj.save() + } else { + await UserFollow.findByIdAndDelete(followObj._id) + } + + return { + following: to, + count: await UserFollow.count({ to: user_id }), + } +} \ No newline at end of file diff --git a/packages/server/services/main/controllers/UserController/services/updateUserData.js b/packages/server/services/users/classes/users/method/update.js old mode 100755 new mode 100644 similarity index 70% rename from packages/server/services/main/controllers/UserController/services/updateUserData.js rename to packages/server/services/users/classes/users/method/update.js index abc1cdcf..6f7a8c1c --- a/packages/server/services/main/controllers/UserController/services/updateUserData.js +++ b/packages/server/services/users/classes/users/method/update.js @@ -1,9 +1,10 @@ import { User } from "@db_models" -export default async (payload) => { +export default async (payload = {}) => { if (typeof payload.user_id === "undefined") { throw new Error("No user_id provided") } + if (typeof payload.update === "undefined") { throw new Error("No update provided") } @@ -22,12 +23,7 @@ export default async (payload) => { await user.save() - global.engine.ws.io.of("/").emit(`user.update`, { - ...user.toObject(), - }) - global.engine.ws.io.of("/").emit(`user.update.${payload.user_id}`, { - ...user.toObject(), - }) + global.rtengine.io.of("/").emit(`user.update.${payload.user_id}`, user.toObject()) return user.toObject() } \ No newline at end of file diff --git a/packages/server/services/users/routes/users/[user_id]/follow/post.js b/packages/server/services/users/routes/users/[user_id]/follow/post.js new file mode 100644 index 00000000..c1cda11d --- /dev/null +++ b/packages/server/services/users/routes/users/[user_id]/follow/post.js @@ -0,0 +1,12 @@ +import User from "@classes/users" + +export default { + middlewares: ["withAuthentication"], + fn: async (req) => { + return await User.toggleFollow({ + user_id: req.params.user_id, + from_user_id: req.auth.session.user_id, + to: req.body.to, + }) + } +} \ No newline at end of file diff --git a/packages/server/services/users/routes/users/[user_id]/followers/get.js b/packages/server/services/users/routes/users/[user_id]/followers/get.js new file mode 100644 index 00000000..53768f0e --- /dev/null +++ b/packages/server/services/users/routes/users/[user_id]/followers/get.js @@ -0,0 +1,12 @@ +import User from "@classes/users" + +export default { + fn: async (req) => { + return await User.getFollowers({ + user_id: req.params.user_id, + data: ToBoolean(req.query.fetchData), + limit: req.query.limit, + offset: req.query.offset, + }) + } +} \ No newline at end of file diff --git a/packages/server/services/users/routes/users/[user_id]/resolve-user_id/get.js b/packages/server/services/users/routes/users/[user_id]/resolve-user_id/get.js new file mode 100644 index 00000000..68fe7803 --- /dev/null +++ b/packages/server/services/users/routes/users/[user_id]/resolve-user_id/get.js @@ -0,0 +1,10 @@ +import Users from "@classes/users" + +// resolve user id from a username (passed from params) +export default { + fn: async (req) => { + return await Users.resolveUserId({ + username: req.params.user_id, + }) + }, +} \ No newline at end of file diff --git a/packages/server/services/users/routes/users/self/get.js b/packages/server/services/users/routes/users/self/get.js new file mode 100644 index 00000000..b5056338 --- /dev/null +++ b/packages/server/services/users/routes/users/self/get.js @@ -0,0 +1,10 @@ +import Users from "@classes/users" + +export default { + middlewares: ["withAuthentication"], + fn: async (req) => { + return await Users.data({ + user_id: req.auth.session.user_id, + }) + } +} \ No newline at end of file diff --git a/packages/server/services/users/routes/users/self/update/post.js b/packages/server/services/users/routes/users/self/update/post.js new file mode 100644 index 00000000..e4b53d1a --- /dev/null +++ b/packages/server/services/users/routes/users/self/update/post.js @@ -0,0 +1,64 @@ +import UserClass from "@classes/users" +import { User } from "@db_models" + +const AllowedPublicUpdateFields = [ + "public_name", + "avatar", + "email", + "cover", + "description", + "location", + "links", + "birthday", +] + +const MaxStringsLengths = { + public_name: 120, + email: 320, + description: 320, +} + +export default { + middlewares: ["withAuthentication"], + fn: async (req) => { + let { update } = req.body + + if (!update) { + throw new OperationError(400, "Missing update") + } + + if (typeof update === "string") { + update = JSON.parse(update) + } + + // sanitize update + AllowedPublicUpdateFields.forEach((key) => { + if (typeof update[key] !== "undefined") { + // check maximung strings length + if (typeof update[key] === "string" && MaxStringsLengths[key]) { + if (update[key].length > MaxStringsLengths[key]) { + // create a substring + update[key] = update[key].substring(0, MaxStringsLengths[key]) + } + } + } + }) + + if (typeof update.email !== "undefined") { + const user = await User.findOne({ + email: update.email, + }).catch((err) => { + return false + }) + + if (user) { + throw new OperationError(400, "Email is already in use") + } + } + + return await UserClass.update({ + user_id: req.auth.session.user_id, + update: update, + }) + } +} \ No newline at end of file diff --git a/packages/server/services/users/users.service.js b/packages/server/services/users/users.service.js index 8f878048..f2a433e2 100644 --- a/packages/server/services/users/users.service.js +++ b/packages/server/services/users/users.service.js @@ -15,6 +15,8 @@ export default class API extends Server { ...SharedMiddlewares } + handleWsAuth = require("@shared-lib/handleWsAuth").default + contexts = { db: new DbManager(), redis: RedisClient() diff --git a/packages/server/utils/obscureEmail.js b/packages/server/utils/obscureEmail.js new file mode 100644 index 00000000..454ccce4 --- /dev/null +++ b/packages/server/utils/obscureEmail.js @@ -0,0 +1,5 @@ +export default (str) => { + const [name, domain] = str.split("@") + + return `${name[0]}${new Array(name.length).join("*")}@${domain}` +}