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.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
-
+ {
+ !!result.hasReplies &&
+ }
-}
\ 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