diff --git a/linebridge b/linebridge index 6d553830..c4b0fbaf 160000 --- a/linebridge +++ b/linebridge @@ -1 +1 @@ -Subproject commit 6d553830ab4661ffab952253d77ccb0bfc1363d8 +Subproject commit c4b0fbafea7b240eda4c7fa4a6a119e9c4b93dbe diff --git a/packages/app/src/components/Login/index.jsx b/packages/app/src/components/Login/index.jsx index 3d387a30..6e59ad78 100755 --- a/packages/app/src/components/Login/index.jsx +++ b/packages/app/src/components/Login/index.jsx @@ -141,6 +141,11 @@ export default class Login extends React.Component { } onUpdateInput = (input, value) => { + if (input === "username") { + value = value.toLowerCase() + value = value.trim() + } + // remove error from ref this.formRef.current.setFields([ { diff --git a/packages/app/src/components/Poll/index.jsx b/packages/app/src/components/Poll/index.jsx new file mode 100644 index 00000000..82f19a50 --- /dev/null +++ b/packages/app/src/components/Poll/index.jsx @@ -0,0 +1,33 @@ +import React from "react" +import { createIconRender } from "components/Icon" + +import "./index.less" + +const PollOption = (props) => { + return
+
+ { + createIconRender(props.option.icon) + } + + + {props.option.label} + +
+
+} + +const Poll = (props) => { + return
+ { + props.options.map((option) => { + return + }) + } +
+} + +export default Poll \ No newline at end of file diff --git a/packages/app/src/components/Poll/index.less b/packages/app/src/components/Poll/index.less new file mode 100644 index 00000000..6a15e297 --- /dev/null +++ b/packages/app/src/components/Poll/index.less @@ -0,0 +1,26 @@ +.poll { + display: flex; + flex-direction: column; + + gap: 8px; + + .poll-option { + display: flex; + flex-direction: row; + + width: 100%; + + padding: 10px 20px; + + transition: all 150ms ease-in-out; + + background-color: var(--background-color-accent); + border-radius: 12px; + + cursor: pointer; + + &:hover { + background-color: var(--background-color-accent-hover); + } + } +} \ No newline at end of file diff --git a/packages/app/src/components/PostCard/components/header/index.jsx b/packages/app/src/components/PostCard/components/header/index.jsx index 8644464f..17e7c6e5 100755 --- a/packages/app/src/components/PostCard/components/header/index.jsx +++ b/packages/app/src/components/PostCard/components/header/index.jsx @@ -1,28 +1,14 @@ import React from "react" import { DateTime } from "luxon" -import { Tag, Skeleton } from "antd" +import { Tag } from "antd" import { Image } from "components" import { Icons } from "components/Icons" -import PostLink from "components/PostLink" -import PostService from "models/post" +import PostReplieView from "components/PostReplieView" import "./index.less" -const PostReplieView = (props) => { - const { data } = props - - if (!data) { - return null - } - - return
- @{data.user.username} - {data.message} -
-} - const PostCardHeader = (props) => { const [timeAgo, setTimeAgo] = React.useState(0) @@ -60,11 +46,13 @@ const PostCardHeader = (props) => { !props.disableReplyTag && props.postData.reply_to &&
- +
+ - - Replied to - + + Replied to + +
{

{ - props.postData.user?.public_name ?? `${props.postData.user?.username}` + props.postData.user?.public_name ?? `@${props.postData.user?.username}` } { diff --git a/packages/app/src/components/PostCard/components/header/index.less b/packages/app/src/components/PostCard/components/header/index.less index b9cc748a..256af0f4 100755 --- a/packages/app/src/components/PostCard/components/header/index.less +++ b/packages/app/src/components/PostCard/components/header/index.less @@ -6,18 +6,25 @@ .post-header-replied_to { display: flex; - flex-direction: row; - - align-items: center; + flex-direction: column; gap: 7px; - svg { - color: var(--text-color); - margin: 0 !important; - } + .post-header-replied_to-label { + display: flex; + flex-direction: row; - line-height: 1.5rem; + gap: 7px; + + align-items: center; + + svg { + color: var(--text-color); + margin: 0 !important; + } + + line-height: 1.5rem; + } } .post-header-user { diff --git a/packages/app/src/components/PostCard/index.jsx b/packages/app/src/components/PostCard/index.jsx index bdf48a8c..fe7f99e5 100755 --- a/packages/app/src/components/PostCard/index.jsx +++ b/packages/app/src/components/PostCard/index.jsx @@ -10,7 +10,6 @@ import PostActions from "./components/actions" import PostAttachments from "./components/attachments" import "./index.less" -import { Divider } from "antd" const messageRegexs = [ { @@ -230,12 +229,13 @@ export default class PostCard extends React.PureComponent { /> { - !this.props.disableHasReplies && this.state.hasReplies && <> - -

View replies

- + !this.props.disableHasReplies && !!this.state.hasReplies &&
app.navigation.goToPost(this.state.data._id)} + > + View {this.state.hasReplies} replies +
} - } } \ No newline at end of file diff --git a/packages/app/src/components/PostCard/index.less b/packages/app/src/components/PostCard/index.less index 9a1e77da..cc178712 100755 --- a/packages/app/src/components/PostCard/index.less +++ b/packages/app/src/components/PostCard/index.less @@ -48,6 +48,11 @@ .message { white-space: break-spaces; user-select: text; + + text-wrap: balance; + word-break: break-word; + + overflow: hidden; } .nsfw_alert { @@ -78,4 +83,26 @@ } } } + + .post-card-has_replies { + display: flex; + flex-direction: column; + + align-items: center; + + width: 100%; + padding: 10px; + + gap: 10px; + + transition: all 150ms ease-in-out; + cursor: pointer; + + border-radius: 8px; + background-color: rgba(var(--layoutBackgroundColor), 0.7); + + &:hover { + background-color: rgba(var(--layoutBackgroundColor), 1); + } + } } \ No newline at end of file diff --git a/packages/app/src/components/PostCreator/index.jsx b/packages/app/src/components/PostCreator/index.jsx index cff2f855..f4f2c1e7 100755 --- a/packages/app/src/components/PostCreator/index.jsx +++ b/packages/app/src/components/PostCreator/index.jsx @@ -4,6 +4,8 @@ import classnames from "classnames" import humanSize from "@tsmx/human-readable" import PostLink from "components/PostLink" import { Icons } from "components/Icons" +import { DateTime } from "luxon" +import lodash from "lodash" import clipboardEventFileToFile from "utils/clipboardEventFileToFile" import PostModel from "models/post" @@ -16,7 +18,6 @@ const DEFAULT_POST_POLICY = { maximunFilesPerRequest: 10 } - export default class PostCreator extends React.Component { state = { pending: [], @@ -76,10 +77,18 @@ export default class PostCreator extends React.Component { return true } - submit = async () => { - if (!this.canSubmit()) return + debounceSubmit = lodash.debounce(() => this.submit(), 50) - this.setState({ + submit = async () => { + if (this.state.loading) { + return false + } + + if (!this.canSubmit()) { + return false + } + + await this.setState({ loading: true, uploaderVisible: false }) @@ -89,7 +98,7 @@ export default class PostCreator extends React.Component { const payload = { message: postMessage, attachments: postAttachments, - //timestamp: DateTime.local().toISO(), + timestamp: DateTime.local().toISO(), } let response = null @@ -255,13 +264,17 @@ export default class PostCreator extends React.Component { }) } - handleKeyDown = (e) => { + handleKeyDown = async (e) => { // detect if the user pressed `enter` key and submit the form, but only if the `shift` key is not pressed if (e.keyCode === 13 && !e.shiftKey) { e.preventDefault() e.stopPropagation() - this.submit() + if (this.state.loading) { + return false + } + + return await this.debounceSubmit() } } @@ -551,7 +564,7 @@ export default class PostCreator extends React.Component { : (editMode ? : )} />

diff --git a/packages/app/src/components/PostReplieView/index.jsx b/packages/app/src/components/PostReplieView/index.jsx new file mode 100644 index 00000000..5874524e --- /dev/null +++ b/packages/app/src/components/PostReplieView/index.jsx @@ -0,0 +1,51 @@ +import React from "react" + +import { Image } from "components" +import { Icons } from "components/Icons" + +import "./index.less" + +const PostReplieView = (props) => { + const { data } = props + + if (!data) { + return null + } + + return
app.navigation.goToPost(data._id)} + > +
+ {data.user.username} + + + { + data.user.public_name ?? `@${data.user.username}` + } + + { + data.user.verified && + } + +
+ +
+ + {data.message} + + { + data.message.length === 0 && data.attachments.length > 0 && <> + + Image + + } + +
+
+} + +export default PostReplieView \ No newline at end of file diff --git a/packages/app/src/components/PostReplieView/index.less b/packages/app/src/components/PostReplieView/index.less new file mode 100644 index 00000000..ce33b39e --- /dev/null +++ b/packages/app/src/components/PostReplieView/index.less @@ -0,0 +1,72 @@ +.post-replie-view { + display: flex; + flex-direction: column; + + gap: 8px; + + padding: 10px; + + background-color: rgba(var(--layoutBackgroundColor), 0.7); + + border-radius: 12px; + + transition: all 150ms ease-in-out; + + cursor: pointer; + + .user { + display: flex; + flex-direction: row; + + align-items: center; + + gap: 7px; + + .lazy-load-image-background { + width: 20px; + height: 20px; + border-radius: 4px; + + overflow: hidden; + + img { + width: 100%; + height: 100%; + object-fit: cover; + } + } + + span { + display: flex; + flex-direction: row; + + align-items: center; + + gap: 3px; + } + } + + .content { + position: relative; + + width: 100%; + overflow: hidden; + + span { + display: flex; + flex-direction: row; + + align-items: center; + + gap: 3px; + + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + } + + &:hover { + background-color: rgba(var(--layoutBackgroundColor), 0.9); + } +} \ No newline at end of file diff --git a/packages/app/src/components/PostsList/index.jsx b/packages/app/src/components/PostsList/index.jsx index 360708ec..822dc280 100755 --- a/packages/app/src/components/PostsList/index.jsx +++ b/packages/app/src/components/PostsList/index.jsx @@ -43,6 +43,7 @@ const Entry = React.memo((props) => { key: data._id, data: data, disableReplyTag: props.disableReplyTag, + disableHasReplies: props.disableHasReplies, events: { onClickLike: props.onLikePost, onClickSave: props.onSavePost, @@ -447,6 +448,7 @@ export class PostsListsComponent extends React.Component { list: this.state.list, disableReplyTag: this.props.disableReplyTag, + disableHasReplies: this.props.disableHasReplies, onLikePost: this.onLikePost, onSavePost: this.onSavePost, diff --git a/packages/app/src/cores/remoteStorage/remoteStorage.core.js b/packages/app/src/cores/remoteStorage/remoteStorage.core.js index 551608d1..f3f71bae 100755 --- a/packages/app/src/cores/remoteStorage/remoteStorage.core.js +++ b/packages/app/src/cores/remoteStorage/remoteStorage.core.js @@ -28,7 +28,7 @@ export default class RemoteStorage extends Core { service = "standard", } = {}, ) { - return new Promise((_resolve, _reject) => { + return await new Promise((_resolve, _reject) => { const fn = async () => new Promise((resolve, reject) => { const uploader = new ChunkedUpload({ endpoint: `${app.cores.api.client().mainOrigin}/upload/chunk`, @@ -41,7 +41,7 @@ export default class RemoteStorage extends Core { uploader.on("error", ({ message }) => { this.console.error("[Uploader] Error", message) - app.notification.new({ + app.cores.notifications.new({ title: "Could not upload file", description: message }, { @@ -65,7 +65,7 @@ export default class RemoteStorage extends Core { uploader.on("finish", (data) => { this.console.debug("[Uploader] Finish", data) - app.notification.new({ + app.cores.notifications.new({ title: "File uploaded", }, { type: "success" diff --git a/packages/app/src/cores/sync/sync.core.js b/packages/app/src/cores/sync/sync.core.js index f2ad3bd2..688b883e 100755 --- a/packages/app/src/cores/sync/sync.core.js +++ b/packages/app/src/cores/sync/sync.core.js @@ -44,7 +44,7 @@ class MusicSyncSubCore { "invite:received": (data) => { this.console.log("invite:received", data) - app.notification.new({ + app.cores.notifications.new({ title: "Sync", description: `${data.invitedBy.username} invited you to join a sync room`, icon: React.createElement(Image, { @@ -91,7 +91,7 @@ class MusicSyncSubCore { this.dettachCard() this.currentRoomData = null - app.notification.new({ + app.cores.notifications.new({ title: "Sync", description: "Disconnected from sync server" }, { @@ -177,7 +177,7 @@ class MusicSyncSubCore { app.cores.player.toggleSyncMode(false, false) - app.notification.new({ + app.cores.notifications.new({ title: "Kicked", description: "You have been kicked from the sync room" }, { diff --git a/packages/app/src/cores/widgets/widgets.core.js b/packages/app/src/cores/widgets/widgets.core.js index 85bf881e..a4a0489d 100755 --- a/packages/app/src/cores/widgets/widgets.core.js +++ b/packages/app/src/cores/widgets/widgets.core.js @@ -107,7 +107,7 @@ export default class WidgetsCore extends Core { store.set(WidgetsCore.storeKey, currentStore) - app.notification.new({ + app.cores.notifications.new({ title: params.update ? "Widget updated" : "Widget installed", description: `Widget [${manifest.name}] has been ${params.update ? "updated" : "installed"}. ${params.update ? `Using current version ${manifest.version}` : ""}`, }, { @@ -141,7 +141,7 @@ export default class WidgetsCore extends Core { store.set(WidgetsCore.storeKey, newStore) - app.notification.new({ + app.cores.notifications.new({ title: "Widget uninstalled", description: `Widget [${widget_id}] has been uninstalled.`, }, { diff --git a/packages/app/src/pages/auth/forms/register/steps/email/index.jsx b/packages/app/src/pages/auth/forms/register/steps/email/index.jsx index a9506f95..7bfda2ed 100755 --- a/packages/app/src/pages/auth/forms/register/steps/email/index.jsx +++ b/packages/app/src/pages/auth/forms/register/steps/email/index.jsx @@ -53,9 +53,9 @@ const EmailStepComponent = (props) => { }) if (request) { - setEmailAvailable(request.available) + setEmailAvailable(!request.exist) - if (!request.available) { + if (request.exist) { antd.message.error("Email is already in use") props.updateValue(null) } else { diff --git a/packages/app/src/pages/auth/forms/register/steps/username/index.jsx b/packages/app/src/pages/auth/forms/register/steps/username/index.jsx index 013eba8d..c2397cfb 100755 --- a/packages/app/src/pages/auth/forms/register/steps/username/index.jsx +++ b/packages/app/src/pages/auth/forms/register/steps/username/index.jsx @@ -102,10 +102,12 @@ export const UsernameStepComponent = (props) => { return false }) - if (request) { - setUsernameAvailable(request.available) + console.log(request) - if (!request.available) { + if (request) { + setUsernameAvailable(!request.exists) + + if (request.exists) { props.updateValue(null) } else { props.updateValue(username) diff --git a/packages/app/src/pages/debug/polls/index.jsx b/packages/app/src/pages/debug/polls/index.jsx new file mode 100644 index 00000000..04657061 --- /dev/null +++ b/packages/app/src/pages/debug/polls/index.jsx @@ -0,0 +1,22 @@ +import Poll from "components/Poll" + +const PollsDebug = (props) => { + return +} + +export default PollsDebug \ No newline at end of file diff --git a/packages/app/src/pages/home/components/feed/index.jsx b/packages/app/src/pages/home/components/feed/index.jsx index 19b7c9a0..05a78909 100755 --- a/packages/app/src/pages/home/components/feed/index.jsx +++ b/packages/app/src/pages/home/components/feed/index.jsx @@ -21,6 +21,7 @@ const emptyListRender = () => { export class Feed extends React.Component { render() { return { +const PostPage = (props) => { const post_id = props.params.post_id const [loading, result, error, repeat] = app.cores.api.useRequest(PostService.getPost, { @@ -29,22 +31,31 @@ export default (props) => { return
-

Post

+

+ + Post +

-
-

Replies

- -
+ { + !!result.hasReplies &&
+

Replies

+ + +
+ }
-} \ No newline at end of file +} + +export default PostPage \ No newline at end of file diff --git a/packages/server/classes/ChunkFileUpload/index.js b/packages/server/classes/ChunkFileUpload/index.js index 28757b27..cc5280b9 100755 --- a/packages/server/classes/ChunkFileUpload/index.js +++ b/packages/server/classes/ChunkFileUpload/index.js @@ -42,14 +42,23 @@ export function createAssembleChunksPromise({ return () => new Promise(async (resolve, reject) => { let fileSize = 0 + if (!fs.existsSync(chunksPath)) { + return reject(new OperationError(500,"No chunks found")) + } + const chunks = await fs.promises.readdir(chunksPath) if (chunks.length === 0) { - throw new Error("No chunks found") + throw new OperationError(500, "No chunks found") } for await (const chunk of chunks) { const chunkPath = path.join(chunksPath, chunk) + + if (!fs.existsSync(chunkPath)) { + return reject(new OperationError(500, "No chunk data found")) + } + const data = await fs.promises.readFile(chunkPath) fileSize += data.length @@ -85,13 +94,17 @@ export async function handleChunkFile(fileStream, { tmpDir, headers, maxFileSize // make sure chunk is in range if (chunkCount < 0 || chunkCount >= totalChunks) { - throw new Error("Chunk is out of range") + throw new OperationError(500, "Chunk is out of range") } // if is the first chunk check if dir exists before write things if (chunkCount === 0) { - if (!await fs.promises.stat(chunksPath).catch(() => false)) { - await fs.promises.mkdir(chunksPath, { recursive: true }) + try { + if (!await fs.promises.stat(chunksPath).catch(() => false)) { + await fs.promises.mkdir(chunksPath, { recursive: true }) + } + } catch (error) { + return reject(new OperationError(500, error.message)) } } diff --git a/packages/server/db_models/user/index.js b/packages/server/db_models/user/index.js index 7f267cee..709a9b44 100755 --- a/packages/server/db_models/user/index.js +++ b/packages/server/db_models/user/index.js @@ -16,5 +16,6 @@ export default { links: { type: Array, default: [] }, location: { type: String, default: null }, birthday: { type: Date, default: null, select: false }, + accept_tos: { type: Boolean, default: false }, } } \ No newline at end of file diff --git a/packages/server/services/auth/classes/account/methods/create.js b/packages/server/services/auth/classes/account/methods/create.js index fc45e06d..b39b3628 100644 --- a/packages/server/services/auth/classes/account/methods/create.js +++ b/packages/server/services/auth/classes/account/methods/create.js @@ -6,9 +6,9 @@ import Account from "@classes/account" export default async (payload) => { requiredFields(["username", "password", "email"], payload) - let { username, password, email, public_name, roles, avatar, acceptTos } = payload + let { username, password, email, public_name, roles, avatar, accept_tos } = payload - if (ToBoolean(acceptTos) !== true) { + if (ToBoolean(accept_tos) !== true) { throw new OperationError(400, "You must accept the terms of service in order to create an account.") } @@ -44,7 +44,7 @@ export default async (payload) => { avatar: avatar ?? `https://api.dicebear.com/7.x/thumbs/svg?seed=${username}`, roles: roles, created_at: new Date().getTime(), - acceptTos: acceptTos, + accept_tos: accept_tos, }) await user.save() diff --git a/packages/server/services/files/routes/upload/chunk/post.js b/packages/server/services/files/routes/upload/chunk/post.js index d3e44d07..e2988ca4 100644 --- a/packages/server/services/files/routes/upload/chunk/post.js +++ b/packages/server/services/files/routes/upload/chunk/post.js @@ -37,14 +37,18 @@ export default { cachePath: tmpPath, }) - fs.promises.rm(tmpPath, { recursive: true, force: true }) + fs.promises.rm(tmpPath, { recursive: true, force: true }).catch(() => { + return false + }) return result } catch (error) { - fs.promises.rm(tmpPath, { recursive: true, force: true }) + fs.promises.rm(tmpPath, { recursive: true, force: true }).catch(() => { + return false + }) throw new OperationError(error.code ?? 500, error.message ?? "Failed to upload file") - } + } } return { diff --git a/packages/server/services/files/services/remoteUpload/index.js b/packages/server/services/files/services/remoteUpload/index.js index 976bb5b3..c435ea61 100644 --- a/packages/server/services/files/services/remoteUpload/index.js +++ b/packages/server/services/files/services/remoteUpload/index.js @@ -35,6 +35,10 @@ export async function b2Upload({ bucketId: process.env.B2_BUCKET_ID, }) + if (!fs.existsSync(source)) { + throw new OperationError(500, "File not found") + } + const data = await fs.promises.readFile(source) await global.b2Storage.uploadFile({ diff --git a/packages/server/services/music/middlewares/withAuth/index.js b/packages/server/services/music/middlewares/withAuth/index.js deleted file mode 100755 index 52a30f4a..00000000 --- a/packages/server/services/music/middlewares/withAuth/index.js +++ /dev/null @@ -1,25 +0,0 @@ -export default async function (req, res, next) { - // extract authentification header - let auth = req.headers.authorization - - if (!auth) { - return res.status(401).json({ error: "Unauthorized, missing token" }) - } - - auth = auth.replace("Bearer ", "") - - // check if authentification is valid - const validation = await comty.rest.session.validateToken(auth).catch((error) => { - return { - valid: false, - } - }) - - if (!validation.valid) { - return res.status(401).json({ error: "Unauthorized" }) - } - - req.session = validation.data - - return next() -} \ No newline at end of file diff --git a/packages/server/services/music/middlewares/withOptionalAuth/index.js b/packages/server/services/music/middlewares/withOptionalAuth/index.js deleted file mode 100755 index 9689973d..00000000 --- a/packages/server/services/music/middlewares/withOptionalAuth/index.js +++ /dev/null @@ -1,26 +0,0 @@ -export default async function (req, res, next) { - // extract authentification header - let auth = req.headers.authorization - - if (!auth) { - return next() - } - - auth = auth.replace("Bearer ", "") - - // check if authentification is valid - const validation = await comty.rest.session.validateToken(auth).catch((error) => { - return { - valid: false, - } - }) - - if (!validation.valid) { - return next() - } - - req.sessionToken = auth - req.session = validation.data - - return next() -} \ No newline at end of file diff --git a/packages/server/services/music/middlewares/withWsAuth.js b/packages/server/services/music/middlewares/withWsAuth.js deleted file mode 100755 index 31fed9d9..00000000 --- a/packages/server/services/music/middlewares/withWsAuth.js +++ /dev/null @@ -1,55 +0,0 @@ -export default async (socket, next) => { - try { - const token = socket.handshake.auth.token - - if (!token) { - return next(new Error(`auth:token_missing`)) - } - - const validation = await global.comty.rest.session.validateToken(token).catch((err) => { - console.error(`[${socket.id}] failed to validate session caused by server error`, err) - - return { - valid: false, - error: err, - } - }) - - if (!validation.valid) { - if (validation.error) { - return next(new Error(`auth:server_error`)) - } - - return next(new Error(`auth:token_invalid`)) - } - - const session = validation.data - - const userData = await global.comty.rest.user.data({ - user_id: session.user_id, - }).catch((err) => { - console.error(`[${socket.id}] failed to get user data caused by server error`, err) - - return null - }) - - if (!userData) { - return next(new Error(`auth:user_failed`)) - } - - try { - socket.userData = userData - socket.token = token - socket.session = session - } - catch (err) { - return next(new Error(`auth:decode_failed`)) - } - - next() - } catch (error) { - console.error(`[${socket.id}] failed to connect caused by server error`, error) - - next(new Error(`auth:authentification_failed`)) - } -} \ No newline at end of file diff --git a/packages/server/services/music/music.service.js b/packages/server/services/music/music.service.js index 11436219..4b5aca73 100755 --- a/packages/server/services/music/music.service.js +++ b/packages/server/services/music/music.service.js @@ -1,188 +1,29 @@ -import fs from "fs" -import path from "path" +import { Server } from "linebridge/src/server" -import express from "express" -import http from "http" -import EventEmitter from "@foxify/events" - -import ComtyClient from "@shared-classes/ComtyClient" import DbManager from "@shared-classes/DbManager" -import RedisClient from "@shared-classes/RedisClient" -import StorageClient from "@shared-classes/StorageClient" -import WebsocketServer from "./ws" +import SharedMiddlewares from "@shared-middlewares" +import LimitsClass from "@shared-classes/Limits" -import pkg from "./package.json" +export default class API extends Server { + static refName = "music" + static useEngine = "hyper-express" + static routesPath = `${__dirname}/routes` + static listen_port = process.env.HTTP_LISTEN_PORT ?? 3003 -export default class API { - static useMiddlewaresOrder = ["useLogger", "useCors", "useAuth", "useErrorHandler"] - - eventBus = global.eventBus = new EventEmitter() - - internalRouter = express.Router() - - constructor(options = {}) { - this.server = express() - this._http = http.createServer(this.server) - this.websocketServer = global.ws = new WebsocketServer(this._http) - - this.options = { - listenHost: process.env.HTTP_LISTEN_IP ?? "0.0.0.0", - listenPort: process.env.HTTP_LISTEN_PORT ?? 3003, - ...options - } + middlewares = { + ...SharedMiddlewares } - comty = global.comty = ComtyClient() - - db = new DbManager() - - redis = global.redis = RedisClient({ - withWsAdapter: true - }) - - storage = global.storage = StorageClient() - - async __registerControllers() { - let controllersPath = fs.readdirSync(path.resolve(__dirname, "controllers")) - - this.internalRouter.routes = [] - - for await (const controllerPath of controllersPath) { - const controller = require(path.resolve(__dirname, "controllers", controllerPath)).default - - if (!controller) { - console.error(`Controller ${controllerPath} not found.`) - - continue - } - - const handler = await controller(express.Router()) - - if (!handler) { - console.error(`Controller ${controllerPath} returning not valid handler.`) - - continue - } - - // let middlewares = [] - - // if (Array.isArray(handler.useMiddlewares)) { - // middlewares = await getMiddlewares(handler.useMiddlewares) - // } - - // for (const middleware of middlewares) { - // handler.router.use(middleware) - // } - - this.internalRouter.use(handler.path ?? "/", handler.router) - - this.internalRouter.routes.push({ - path: handler.path ?? "/", - routers: handler.router.routes - }) - - continue - } + contexts = { + db: new DbManager(), + limits: {}, } - async __registerInternalMiddlewares() { - let middlewaresPath = fs.readdirSync(path.resolve(__dirname, "useMiddlewares")) + async onInitialize() { + await this.contexts.db.initialize() - // sort middlewares - if (this.constructor.useMiddlewaresOrder) { - middlewaresPath = middlewaresPath.sort((a, b) => { - const aIndex = this.constructor.useMiddlewaresOrder.indexOf(a.replace(".js", "")) - const bIndex = this.constructor.useMiddlewaresOrder.indexOf(b.replace(".js", "")) - - if (aIndex === -1) { - return 1 - } - - if (bIndex === -1) { - return -1 - } - - return aIndex - bIndex - }) - } - - for await (const middlewarePath of middlewaresPath) { - const middleware = require(path.resolve(__dirname, "useMiddlewares", middlewarePath)).default - - if (!middleware) { - console.error(`Middleware ${middlewarePath} not found.`) - - continue - } - - this.server.use(middleware) - } - } - - __registerInternalRoutes() { - this.internalRouter.get("/", (req, res) => { - return res.status(200).json({ - name: pkg.name, - version: pkg.version, - }) - }) - - this.internalRouter.get("/_routes", (req, res) => { - return res.status(200).json(this.__getRegisteredRoutes(this.internalRouter.routes)) - }) - - this.internalRouter.get("*", (req, res) => { - return res.status(404).json({ - error: "Not found", - }) - }) - } - - __getRegisteredRoutes(router) { - return router.map((entry) => { - if (Array.isArray(entry.routers)) { - return { - path: entry.path, - routes: this.__getRegisteredRoutes(entry.routers), - } - } - - return { - method: entry.method, - path: entry.path, - } - }) - } - - initialize = async () => { - const startHrTime = process.hrtime() - - await this.websocketServer.initialize() - - // initialize clients - await this.db.initialize() - await this.redis.initialize() - await this.storage.initialize() - - // register controllers & middlewares - this.server.use(express.json({ extended: false })) - this.server.use(express.urlencoded({ extended: true })) - - await this.__registerControllers() - await this.__registerInternalMiddlewares() - await this.__registerInternalRoutes() - - this.server.use(this.internalRouter) - - await this._http.listen(this.options.listenPort, this.options.listenHost) - - // calculate elapsed time - const elapsedHrTime = process.hrtime(startHrTime) - const elapsedTimeInMs = elapsedHrTime[0] * 1000 + elapsedHrTime[1] / 1e6 - - // log server started - console.log(`🚀 Server started ready on \n\t - http://${this.options.listenHost}:${this.options.listenPort} \n\t - Tooks ${elapsedTimeInMs}ms`) + this.contexts.limits = await LimitsClass.get() } } diff --git a/packages/server/services/music/routes/music/releases/self/get.js b/packages/server/services/music/routes/music/releases/self/get.js new file mode 100644 index 00000000..186545f9 --- /dev/null +++ b/packages/server/services/music/routes/music/releases/self/get.js @@ -0,0 +1,66 @@ +import { Playlist, Release, Track } from "@db_models" + +export default { + middlewares: ["withAuthentication"], + fn: async (req) => { + const { keywords, limit = 10, offset = 0 } = req.query + + const user_id = req.auth.session.user_id + + let searchQuery = { + user_id, + } + + if (keywords) { + searchQuery = { + ...searchQuery, + title: { + $regex: keywords, + $options: "i", + }, + } + } + + const playlistsCount = await Playlist.count(searchQuery) + const releasesCount = await Release.count(searchQuery) + + let total_length = playlistsCount + releasesCount + + let playlists = await Playlist.find(searchQuery) + .sort({ created_at: -1 }) + .limit(limit) + .skip(offset) + + playlists = playlists.map((playlist) => { + playlist = playlist.toObject() + + playlist.type = "playlist" + + return playlist + }) + + let releases = await Release.find(searchQuery) + .sort({ created_at: -1 }) + .limit(limit) + .skip(offset) + + let result = [...playlists, ...releases] + + if (req.query.resolveItemsData === "true") { + result = await Promise.all( + playlists.map(async playlist => { + playlist.list = await Track.find({ + _id: [...playlist.list], + }) + + return playlist + }), + ) + } + + return { + total_length: total_length, + items: result, + } + } +} \ No newline at end of file diff --git a/packages/server/services/music/useMiddlewares/useCors/index.js b/packages/server/services/music/useMiddlewares/useCors/index.js deleted file mode 100755 index 7ebac0fc..00000000 --- a/packages/server/services/music/useMiddlewares/useCors/index.js +++ /dev/null @@ -1,8 +0,0 @@ -import cors from "cors" - -export default cors({ - origin: "*", - methods: ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD", "CONNECT", "TRACE"], - preflightContinue: false, - optionsSuccessStatus: 204, -}) \ No newline at end of file diff --git a/packages/server/services/music/useMiddlewares/useLogger/index.js b/packages/server/services/music/useMiddlewares/useLogger/index.js deleted file mode 100755 index 1d398fc1..00000000 --- a/packages/server/services/music/useMiddlewares/useLogger/index.js +++ /dev/null @@ -1,19 +0,0 @@ -export default (req, res, next) => { - const startHrTime = process.hrtime() - - res.on("finish", () => { - const elapsedHrTime = process.hrtime(startHrTime) - const elapsedTimeInMs = elapsedHrTime[0] * 1000 + elapsedHrTime[1] / 1e6 - - res._responseTimeMs = elapsedTimeInMs - - // cut req.url if is too long - if (req.url.length > 100) { - req.url = req.url.substring(0, 100) + "..." - } - - console.log(`${req.method} ${res._status_code ?? res.statusCode ?? 200} ${req.url} ${elapsedTimeInMs}ms`) - }) - - next() -} \ No newline at end of file diff --git a/packages/server/services/music/utils/composePayloadData/index.js b/packages/server/services/music/utils/composePayloadData/index.js deleted file mode 100755 index de5761f5..00000000 --- a/packages/server/services/music/utils/composePayloadData/index.js +++ /dev/null @@ -1,12 +0,0 @@ -export default function composePayloadData(socket, data = {}) { - return { - user: { - user_id: socket.userData._id, - username: socket.userData.username, - fullName: socket.userData.fullName, - avatar: socket.userData.avatar, - }, - command_issuer: data.command_issuer ?? socket.userData._id, - ...data - } -} \ No newline at end of file diff --git a/packages/server/services/music/utils/createRoutesFromDirectory/index.js b/packages/server/services/music/utils/createRoutesFromDirectory/index.js deleted file mode 100755 index c83df2e9..00000000 --- a/packages/server/services/music/utils/createRoutesFromDirectory/index.js +++ /dev/null @@ -1,75 +0,0 @@ -import fs from "fs" - -function createRouteHandler(route, fn) { - if (typeof route !== "string") { - fn = route - route = "Unknown route" - } - - return async (req, res) => { - try { - await fn(req, res) - } catch (error) { - console.error(`[ERROR] (${route}) >`, error) - - return res.status(500).json({ - error: error.message, - }) - } - } -} - -function createRoutesFromDirectory(startFrom, directoryPath, router) { - const files = fs.readdirSync(directoryPath) - - if (typeof router.routes !== "object") { - router.routes = [] - } - - files.forEach((file) => { - const filePath = `${directoryPath}/${file}` - - const stat = fs.statSync(filePath) - - if (stat.isDirectory()) { - createRoutesFromDirectory(startFrom, filePath, router) - } else if (file.endsWith(".js") || file.endsWith(".jsx") || file.endsWith(".ts") || file.endsWith(".tsx")) { - let splitedFilePath = filePath.split("/") - - // slice the startFrom path - splitedFilePath = splitedFilePath.slice(splitedFilePath.indexOf(startFrom) + 1) - - const method = splitedFilePath[0] - - let route = splitedFilePath.slice(1, splitedFilePath.length).join("/") - - route = route.replace(".jsx", "") - route = route.replace(".js", "") - route = route.replace(".ts", "") - route = route.replace(".tsx", "") - - if (route.endsWith("/index")) { - route = route.replace("/index", "") - } - - route = `/${route}` - - let handler = require(filePath) - - handler = handler.default || handler - - router[method](route, createRouteHandler(route, handler)) - - //console.log(`[ROUTE] ${method.toUpperCase()} [${route}]`, handler) - - router.routes.push({ - method, - path: route, - }) - } - }) - - return router -} - -export default createRoutesFromDirectory \ No newline at end of file diff --git a/packages/server/services/music/utils/generateFnHandler/index.js b/packages/server/services/music/utils/generateFnHandler/index.js deleted file mode 100755 index 4c5962d5..00000000 --- a/packages/server/services/music/utils/generateFnHandler/index.js +++ /dev/null @@ -1,21 +0,0 @@ -export default function generateFnHandler(fn, socket) { - return async (...args) => { - if (typeof socket === "undefined") { - socket = arguments[0] - } - - try { - fn(socket, ...args) - } catch (error) { - console.error(`[HANDLER_ERROR] ${error.message} >`, error.stack) - - if (typeof socket.emit !== "function") { - return false - } - - return socket.emit("error", { - message: error.message, - }) - } - } -} \ No newline at end of file diff --git a/packages/server/services/music/utils/getMiddlewares/index.js b/packages/server/services/music/utils/getMiddlewares/index.js deleted file mode 100755 index 78a65ea0..00000000 --- a/packages/server/services/music/utils/getMiddlewares/index.js +++ /dev/null @@ -1,46 +0,0 @@ -import fs from "node:fs" -import path from "node:path" - -export default async (middlewares, middlewaresPath) => { - if (typeof middlewaresPath === "undefined") { - middlewaresPath = path.resolve(globalThis["__src"], "middlewares") - } - - if (!fs.existsSync(middlewaresPath)) { - return undefined - } - - if (typeof middlewares === "string") { - middlewares = [middlewares] - } - - let fns = [] - - for await (const middlewareName of middlewares) { - const middlewarePath = path.resolve(middlewaresPath, middlewareName) - - if (!fs.existsSync(middlewarePath)) { - console.error(`Middleware ${middlewareName} not found.`) - - continue - } - - const middleware = require(middlewarePath).default - - if (!middleware) { - console.error(`Middleware ${middlewareName} not valid export.`) - - continue - } - - if (typeof middleware !== "function") { - console.error(`Middleware ${middlewareName} not valid function.`) - - continue - } - - fns.push(middleware) - } - - return fns -} \ No newline at end of file diff --git a/packages/server/services/music/utils/resolveUrl/index.js b/packages/server/services/music/utils/resolveUrl/index.js deleted file mode 100755 index a9a33785..00000000 --- a/packages/server/services/music/utils/resolveUrl/index.js +++ /dev/null @@ -1,20 +0,0 @@ -export default (from, to) => { - const resolvedUrl = new URL(to, new URL(from, "resolve://")) - - if (resolvedUrl.protocol === "resolve:") { - let { pathname, search, hash } = resolvedUrl - - if (to.includes("@")) { - const fromUrl = new URL(from) - const toUrl = new URL(to, fromUrl.origin) - - pathname = toUrl.pathname - search = toUrl.search - hash = toUrl.hash - } - - return pathname + search + hash - } - - return resolvedUrl.toString() -} \ No newline at end of file diff --git a/packages/server/services/posts/classes/posts/methods/delete.js b/packages/server/services/posts/classes/posts/methods/delete.js index b1cda1b0..db23889c 100644 --- a/packages/server/services/posts/classes/posts/methods/delete.js +++ b/packages/server/services/posts/classes/posts/methods/delete.js @@ -29,6 +29,13 @@ export default async (payload = {}) => { throw new OperationError(500, `An error has occurred: ${err.message}`) }) + // delete replies + await Post.deleteMany({ + reply_to: post_id, + }).catch((err) => { + throw new OperationError(500, `An error has occurred: ${err.message}`) + }) + global.rtengine.io.of("/").emit(`post.delete`, post_id) global.rtengine.io.of("/").emit(`post.delete.${post_id}`, post_id) diff --git a/packages/server/services/posts/classes/posts/methods/fullfill.js b/packages/server/services/posts/classes/posts/methods/fullfill.js index 927c2f98..3345b529 100644 --- a/packages/server/services/posts/classes/posts/methods/fullfill.js +++ b/packages/server/services/posts/classes/posts/methods/fullfill.js @@ -23,7 +23,7 @@ export default async (payload = {}) => { postsSavesIds = postsSaves.map((postSave) => postSave.post_id) } - let [usersData, likesData, repliesData] = await Promise.all([ + let [usersData, likesData] = await Promise.all([ User.find({ _id: { $in: posts.map((post) => post.user_id) @@ -63,8 +63,18 @@ export default async (payload = {}) => { if (post.reply_to) { post.reply_to_data = await Post.findById(post.reply_to) + + if (post.reply_to_data) { + post.reply_to_data = post.reply_to_data.toObject() + + const replyUserData = await User.findById(post.reply_to_data.user_id) + + post.reply_to_data.user = replyUserData.toObject() + } } + post.hasReplies = await Post.count({ reply_to: post._id }) + let likes = likesData[post._id.toString()] ?? [] post.countLikes = likes.length