mirror of
https://github.com/ragestudio/comty.git
synced 2025-06-09 10:34:17 +00:00
merge from local
This commit is contained in:
parent
500fa23384
commit
d6a074a859
@ -1 +1 @@
|
|||||||
Subproject commit 6d553830ab4661ffab952253d77ccb0bfc1363d8
|
Subproject commit c4b0fbafea7b240eda4c7fa4a6a119e9c4b93dbe
|
@ -141,6 +141,11 @@ export default class Login extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onUpdateInput = (input, value) => {
|
onUpdateInput = (input, value) => {
|
||||||
|
if (input === "username") {
|
||||||
|
value = value.toLowerCase()
|
||||||
|
value = value.trim()
|
||||||
|
}
|
||||||
|
|
||||||
// remove error from ref
|
// remove error from ref
|
||||||
this.formRef.current.setFields([
|
this.formRef.current.setFields([
|
||||||
{
|
{
|
||||||
|
33
packages/app/src/components/Poll/index.jsx
Normal file
33
packages/app/src/components/Poll/index.jsx
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import React from "react"
|
||||||
|
import { createIconRender } from "components/Icon"
|
||||||
|
|
||||||
|
import "./index.less"
|
||||||
|
|
||||||
|
const PollOption = (props) => {
|
||||||
|
return <div className="poll-option">
|
||||||
|
<div className="label">
|
||||||
|
{
|
||||||
|
createIconRender(props.option.icon)
|
||||||
|
}
|
||||||
|
|
||||||
|
<span>
|
||||||
|
{props.option.label}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
const Poll = (props) => {
|
||||||
|
return <div className="poll">
|
||||||
|
{
|
||||||
|
props.options.map((option) => {
|
||||||
|
return <PollOption
|
||||||
|
key={option.id}
|
||||||
|
option={option}
|
||||||
|
/>
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Poll
|
26
packages/app/src/components/Poll/index.less
Normal file
26
packages/app/src/components/Poll/index.less
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,28 +1,14 @@
|
|||||||
import React from "react"
|
import React from "react"
|
||||||
import { DateTime } from "luxon"
|
import { DateTime } from "luxon"
|
||||||
import { Tag, Skeleton } from "antd"
|
import { Tag } from "antd"
|
||||||
|
|
||||||
import { Image } from "components"
|
import { Image } from "components"
|
||||||
import { Icons } from "components/Icons"
|
import { Icons } from "components/Icons"
|
||||||
import PostLink from "components/PostLink"
|
|
||||||
|
|
||||||
import PostService from "models/post"
|
import PostReplieView from "components/PostReplieView"
|
||||||
|
|
||||||
import "./index.less"
|
import "./index.less"
|
||||||
|
|
||||||
const PostReplieView = (props) => {
|
|
||||||
const { data } = props
|
|
||||||
|
|
||||||
if (!data) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return <div>
|
|
||||||
@{data.user.username}
|
|
||||||
{data.message}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
const PostCardHeader = (props) => {
|
const PostCardHeader = (props) => {
|
||||||
const [timeAgo, setTimeAgo] = React.useState(0)
|
const [timeAgo, setTimeAgo] = React.useState(0)
|
||||||
|
|
||||||
@ -60,11 +46,13 @@ const PostCardHeader = (props) => {
|
|||||||
!props.disableReplyTag && props.postData.reply_to && <div
|
!props.disableReplyTag && props.postData.reply_to && <div
|
||||||
className="post-header-replied_to"
|
className="post-header-replied_to"
|
||||||
>
|
>
|
||||||
<Icons.Repeat />
|
<div className="post-header-replied_to-label">
|
||||||
|
<Icons.Repeat />
|
||||||
|
|
||||||
<span>
|
<span>
|
||||||
Replied to
|
Replied to
|
||||||
</span>
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<PostReplieView
|
<PostReplieView
|
||||||
data={props.postData.reply_to_data}
|
data={props.postData.reply_to_data}
|
||||||
@ -83,7 +71,7 @@ const PostCardHeader = (props) => {
|
|||||||
<div className="post-header-user-info">
|
<div className="post-header-user-info">
|
||||||
<h1 onClick={goToProfile}>
|
<h1 onClick={goToProfile}>
|
||||||
{
|
{
|
||||||
props.postData.user?.public_name ?? `${props.postData.user?.username}`
|
props.postData.user?.public_name ?? `@${props.postData.user?.username}`
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -6,18 +6,25 @@
|
|||||||
|
|
||||||
.post-header-replied_to {
|
.post-header-replied_to {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: column;
|
||||||
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
gap: 7px;
|
gap: 7px;
|
||||||
|
|
||||||
svg {
|
.post-header-replied_to-label {
|
||||||
color: var(--text-color);
|
display: flex;
|
||||||
margin: 0 !important;
|
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 {
|
.post-header-user {
|
||||||
|
@ -10,7 +10,6 @@ import PostActions from "./components/actions"
|
|||||||
import PostAttachments from "./components/attachments"
|
import PostAttachments from "./components/attachments"
|
||||||
|
|
||||||
import "./index.less"
|
import "./index.less"
|
||||||
import { Divider } from "antd"
|
|
||||||
|
|
||||||
const messageRegexs = [
|
const messageRegexs = [
|
||||||
{
|
{
|
||||||
@ -230,12 +229,13 @@ export default class PostCard extends React.PureComponent {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{
|
{
|
||||||
!this.props.disableHasReplies && this.state.hasReplies && <>
|
!this.props.disableHasReplies && !!this.state.hasReplies && <div
|
||||||
<Divider />
|
className="post-card-has_replies"
|
||||||
<h1>View replies</h1>
|
onClick={() => app.navigation.goToPost(this.state.data._id)}
|
||||||
</>
|
>
|
||||||
|
<span>View {this.state.hasReplies} replies</span>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
</motion.div>
|
</motion.div>
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -48,6 +48,11 @@
|
|||||||
.message {
|
.message {
|
||||||
white-space: break-spaces;
|
white-space: break-spaces;
|
||||||
user-select: text;
|
user-select: text;
|
||||||
|
|
||||||
|
text-wrap: balance;
|
||||||
|
word-break: break-word;
|
||||||
|
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nsfw_alert {
|
.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -4,6 +4,8 @@ import classnames from "classnames"
|
|||||||
import humanSize from "@tsmx/human-readable"
|
import humanSize from "@tsmx/human-readable"
|
||||||
import PostLink from "components/PostLink"
|
import PostLink from "components/PostLink"
|
||||||
import { Icons } from "components/Icons"
|
import { Icons } from "components/Icons"
|
||||||
|
import { DateTime } from "luxon"
|
||||||
|
import lodash from "lodash"
|
||||||
|
|
||||||
import clipboardEventFileToFile from "utils/clipboardEventFileToFile"
|
import clipboardEventFileToFile from "utils/clipboardEventFileToFile"
|
||||||
import PostModel from "models/post"
|
import PostModel from "models/post"
|
||||||
@ -16,7 +18,6 @@ const DEFAULT_POST_POLICY = {
|
|||||||
maximunFilesPerRequest: 10
|
maximunFilesPerRequest: 10
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export default class PostCreator extends React.Component {
|
export default class PostCreator extends React.Component {
|
||||||
state = {
|
state = {
|
||||||
pending: [],
|
pending: [],
|
||||||
@ -76,10 +77,18 @@ export default class PostCreator extends React.Component {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
submit = async () => {
|
debounceSubmit = lodash.debounce(() => this.submit(), 50)
|
||||||
if (!this.canSubmit()) return
|
|
||||||
|
|
||||||
this.setState({
|
submit = async () => {
|
||||||
|
if (this.state.loading) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.canSubmit()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.setState({
|
||||||
loading: true,
|
loading: true,
|
||||||
uploaderVisible: false
|
uploaderVisible: false
|
||||||
})
|
})
|
||||||
@ -89,7 +98,7 @@ export default class PostCreator extends React.Component {
|
|||||||
const payload = {
|
const payload = {
|
||||||
message: postMessage,
|
message: postMessage,
|
||||||
attachments: postAttachments,
|
attachments: postAttachments,
|
||||||
//timestamp: DateTime.local().toISO(),
|
timestamp: DateTime.local().toISO(),
|
||||||
}
|
}
|
||||||
|
|
||||||
let response = null
|
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
|
// 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) {
|
if (e.keyCode === 13 && !e.shiftKey) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
e.stopPropagation()
|
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 {
|
|||||||
<antd.Button
|
<antd.Button
|
||||||
type="primary"
|
type="primary"
|
||||||
disabled={loading || !this.canSubmit()}
|
disabled={loading || !this.canSubmit()}
|
||||||
onClick={this.submit}
|
onClick={this.debounceSubmit}
|
||||||
icon={loading ? <Icons.LoadingOutlined spin /> : (editMode ? <Icons.MdEdit /> : <Icons.Send />)}
|
icon={loading ? <Icons.LoadingOutlined spin /> : (editMode ? <Icons.MdEdit /> : <Icons.Send />)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
51
packages/app/src/components/PostReplieView/index.jsx
Normal file
51
packages/app/src/components/PostReplieView/index.jsx
Normal file
@ -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 <div
|
||||||
|
className="post-replie-view"
|
||||||
|
onClick={() => app.navigation.goToPost(data._id)}
|
||||||
|
>
|
||||||
|
<div className="user">
|
||||||
|
<Image
|
||||||
|
src={data.user.avatar}
|
||||||
|
alt={data.user.username}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<span>
|
||||||
|
{
|
||||||
|
data.user.public_name ?? `@${data.user.username}`
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
data.user.verified && <Icons.verifiedBadge />
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="content">
|
||||||
|
<span>
|
||||||
|
{data.message}
|
||||||
|
|
||||||
|
{
|
||||||
|
data.message.length === 0 && data.attachments.length > 0 && <>
|
||||||
|
<Icons.MdAttachment />
|
||||||
|
Image
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PostReplieView
|
72
packages/app/src/components/PostReplieView/index.less
Normal file
72
packages/app/src/components/PostReplieView/index.less
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -43,6 +43,7 @@ const Entry = React.memo((props) => {
|
|||||||
key: data._id,
|
key: data._id,
|
||||||
data: data,
|
data: data,
|
||||||
disableReplyTag: props.disableReplyTag,
|
disableReplyTag: props.disableReplyTag,
|
||||||
|
disableHasReplies: props.disableHasReplies,
|
||||||
events: {
|
events: {
|
||||||
onClickLike: props.onLikePost,
|
onClickLike: props.onLikePost,
|
||||||
onClickSave: props.onSavePost,
|
onClickSave: props.onSavePost,
|
||||||
@ -447,6 +448,7 @@ export class PostsListsComponent extends React.Component {
|
|||||||
list: this.state.list,
|
list: this.state.list,
|
||||||
|
|
||||||
disableReplyTag: this.props.disableReplyTag,
|
disableReplyTag: this.props.disableReplyTag,
|
||||||
|
disableHasReplies: this.props.disableHasReplies,
|
||||||
|
|
||||||
onLikePost: this.onLikePost,
|
onLikePost: this.onLikePost,
|
||||||
onSavePost: this.onSavePost,
|
onSavePost: this.onSavePost,
|
||||||
|
@ -28,7 +28,7 @@ export default class RemoteStorage extends Core {
|
|||||||
service = "standard",
|
service = "standard",
|
||||||
} = {},
|
} = {},
|
||||||
) {
|
) {
|
||||||
return new Promise((_resolve, _reject) => {
|
return await new Promise((_resolve, _reject) => {
|
||||||
const fn = async () => new Promise((resolve, reject) => {
|
const fn = async () => new Promise((resolve, reject) => {
|
||||||
const uploader = new ChunkedUpload({
|
const uploader = new ChunkedUpload({
|
||||||
endpoint: `${app.cores.api.client().mainOrigin}/upload/chunk`,
|
endpoint: `${app.cores.api.client().mainOrigin}/upload/chunk`,
|
||||||
@ -41,7 +41,7 @@ export default class RemoteStorage extends Core {
|
|||||||
uploader.on("error", ({ message }) => {
|
uploader.on("error", ({ message }) => {
|
||||||
this.console.error("[Uploader] Error", message)
|
this.console.error("[Uploader] Error", message)
|
||||||
|
|
||||||
app.notification.new({
|
app.cores.notifications.new({
|
||||||
title: "Could not upload file",
|
title: "Could not upload file",
|
||||||
description: message
|
description: message
|
||||||
}, {
|
}, {
|
||||||
@ -65,7 +65,7 @@ export default class RemoteStorage extends Core {
|
|||||||
uploader.on("finish", (data) => {
|
uploader.on("finish", (data) => {
|
||||||
this.console.debug("[Uploader] Finish", data)
|
this.console.debug("[Uploader] Finish", data)
|
||||||
|
|
||||||
app.notification.new({
|
app.cores.notifications.new({
|
||||||
title: "File uploaded",
|
title: "File uploaded",
|
||||||
}, {
|
}, {
|
||||||
type: "success"
|
type: "success"
|
||||||
|
@ -44,7 +44,7 @@ class MusicSyncSubCore {
|
|||||||
"invite:received": (data) => {
|
"invite:received": (data) => {
|
||||||
this.console.log("invite:received", data)
|
this.console.log("invite:received", data)
|
||||||
|
|
||||||
app.notification.new({
|
app.cores.notifications.new({
|
||||||
title: "Sync",
|
title: "Sync",
|
||||||
description: `${data.invitedBy.username} invited you to join a sync room`,
|
description: `${data.invitedBy.username} invited you to join a sync room`,
|
||||||
icon: React.createElement(Image, {
|
icon: React.createElement(Image, {
|
||||||
@ -91,7 +91,7 @@ class MusicSyncSubCore {
|
|||||||
this.dettachCard()
|
this.dettachCard()
|
||||||
this.currentRoomData = null
|
this.currentRoomData = null
|
||||||
|
|
||||||
app.notification.new({
|
app.cores.notifications.new({
|
||||||
title: "Sync",
|
title: "Sync",
|
||||||
description: "Disconnected from sync server"
|
description: "Disconnected from sync server"
|
||||||
}, {
|
}, {
|
||||||
@ -177,7 +177,7 @@ class MusicSyncSubCore {
|
|||||||
|
|
||||||
app.cores.player.toggleSyncMode(false, false)
|
app.cores.player.toggleSyncMode(false, false)
|
||||||
|
|
||||||
app.notification.new({
|
app.cores.notifications.new({
|
||||||
title: "Kicked",
|
title: "Kicked",
|
||||||
description: "You have been kicked from the sync room"
|
description: "You have been kicked from the sync room"
|
||||||
}, {
|
}, {
|
||||||
|
@ -107,7 +107,7 @@ export default class WidgetsCore extends Core {
|
|||||||
|
|
||||||
store.set(WidgetsCore.storeKey, currentStore)
|
store.set(WidgetsCore.storeKey, currentStore)
|
||||||
|
|
||||||
app.notification.new({
|
app.cores.notifications.new({
|
||||||
title: params.update ? "Widget updated" : "Widget installed",
|
title: params.update ? "Widget updated" : "Widget installed",
|
||||||
description: `Widget [${manifest.name}] has been ${params.update ? "updated" : "installed"}. ${params.update ? `Using current version ${manifest.version}` : ""}`,
|
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)
|
store.set(WidgetsCore.storeKey, newStore)
|
||||||
|
|
||||||
app.notification.new({
|
app.cores.notifications.new({
|
||||||
title: "Widget uninstalled",
|
title: "Widget uninstalled",
|
||||||
description: `Widget [${widget_id}] has been uninstalled.`,
|
description: `Widget [${widget_id}] has been uninstalled.`,
|
||||||
}, {
|
}, {
|
||||||
|
@ -53,9 +53,9 @@ const EmailStepComponent = (props) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if (request) {
|
if (request) {
|
||||||
setEmailAvailable(request.available)
|
setEmailAvailable(!request.exist)
|
||||||
|
|
||||||
if (!request.available) {
|
if (request.exist) {
|
||||||
antd.message.error("Email is already in use")
|
antd.message.error("Email is already in use")
|
||||||
props.updateValue(null)
|
props.updateValue(null)
|
||||||
} else {
|
} else {
|
||||||
|
@ -102,10 +102,12 @@ export const UsernameStepComponent = (props) => {
|
|||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
|
|
||||||
if (request) {
|
console.log(request)
|
||||||
setUsernameAvailable(request.available)
|
|
||||||
|
|
||||||
if (!request.available) {
|
if (request) {
|
||||||
|
setUsernameAvailable(!request.exists)
|
||||||
|
|
||||||
|
if (request.exists) {
|
||||||
props.updateValue(null)
|
props.updateValue(null)
|
||||||
} else {
|
} else {
|
||||||
props.updateValue(username)
|
props.updateValue(username)
|
||||||
|
22
packages/app/src/pages/debug/polls/index.jsx
Normal file
22
packages/app/src/pages/debug/polls/index.jsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import Poll from "components/Poll"
|
||||||
|
|
||||||
|
const PollsDebug = (props) => {
|
||||||
|
return <Poll
|
||||||
|
options={[
|
||||||
|
{
|
||||||
|
id: "option_1",
|
||||||
|
label: "I like Comty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "option_2",
|
||||||
|
label: "I don't like Comty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "option_3",
|
||||||
|
label: "I prefer Twitter"
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PollsDebug
|
@ -21,6 +21,7 @@ const emptyListRender = () => {
|
|||||||
export class Feed extends React.Component {
|
export class Feed extends React.Component {
|
||||||
render() {
|
render() {
|
||||||
return <PostsList
|
return <PostsList
|
||||||
|
disableHasReplies
|
||||||
ref={this.props.innerRef}
|
ref={this.props.innerRef}
|
||||||
emptyListRender={emptyListRender}
|
emptyListRender={emptyListRender}
|
||||||
loadFromModel={FeedModel.getTimelineFeed}
|
loadFromModel={FeedModel.getTimelineFeed}
|
||||||
|
@ -9,6 +9,7 @@ import "./index.less"
|
|||||||
export default class ExplorePosts extends React.Component {
|
export default class ExplorePosts extends React.Component {
|
||||||
render() {
|
render() {
|
||||||
return <PostsList
|
return <PostsList
|
||||||
|
disableHasReplies
|
||||||
loadFromModel={Feed.getGlobalTimelineFeed}
|
loadFromModel={Feed.getGlobalTimelineFeed}
|
||||||
watchTimeline={[
|
watchTimeline={[
|
||||||
"post.new",
|
"post.new",
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import React from "react"
|
import React from "react"
|
||||||
import * as antd from "antd"
|
import * as antd from "antd"
|
||||||
|
|
||||||
|
import { Icons } from "components/Icons"
|
||||||
|
|
||||||
import PostCard from "components/PostCard"
|
import PostCard from "components/PostCard"
|
||||||
import PostsList from "components/PostsList"
|
import PostsList from "components/PostsList"
|
||||||
|
|
||||||
@ -8,7 +10,7 @@ import PostService from "models/post"
|
|||||||
|
|
||||||
import "./index.less"
|
import "./index.less"
|
||||||
|
|
||||||
export default (props) => {
|
const PostPage = (props) => {
|
||||||
const post_id = props.params.post_id
|
const post_id = props.params.post_id
|
||||||
|
|
||||||
const [loading, result, error, repeat] = app.cores.api.useRequest(PostService.getPost, {
|
const [loading, result, error, repeat] = app.cores.api.useRequest(PostService.getPost, {
|
||||||
@ -29,22 +31,31 @@ export default (props) => {
|
|||||||
|
|
||||||
return <div className="post-page">
|
return <div className="post-page">
|
||||||
<div className="post-page-original">
|
<div className="post-page-original">
|
||||||
<h1>Post</h1>
|
<h1>
|
||||||
|
<Icons.MdTextSnippet />
|
||||||
|
Post
|
||||||
|
</h1>
|
||||||
|
|
||||||
<PostCard
|
<PostCard
|
||||||
data={result}
|
data={result}
|
||||||
|
disableHasReplies
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="post-page-replies">
|
{
|
||||||
<h1>Replies</h1>
|
!!result.hasReplies && <div className="post-page-replies">
|
||||||
<PostsList
|
<h1><Icons.Repeat />Replies</h1>
|
||||||
disableReplyTag
|
|
||||||
loadFromModel={PostService.replies}
|
<PostsList
|
||||||
loadFromModelProps={{
|
disableReplyTag
|
||||||
post_id,
|
loadFromModel={PostService.replies}
|
||||||
}}
|
loadFromModelProps={{
|
||||||
/>
|
post_id,
|
||||||
</div>
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default PostPage
|
@ -42,14 +42,23 @@ export function createAssembleChunksPromise({
|
|||||||
return () => new Promise(async (resolve, reject) => {
|
return () => new Promise(async (resolve, reject) => {
|
||||||
let fileSize = 0
|
let fileSize = 0
|
||||||
|
|
||||||
|
if (!fs.existsSync(chunksPath)) {
|
||||||
|
return reject(new OperationError(500,"No chunks found"))
|
||||||
|
}
|
||||||
|
|
||||||
const chunks = await fs.promises.readdir(chunksPath)
|
const chunks = await fs.promises.readdir(chunksPath)
|
||||||
|
|
||||||
if (chunks.length === 0) {
|
if (chunks.length === 0) {
|
||||||
throw new Error("No chunks found")
|
throw new OperationError(500, "No chunks found")
|
||||||
}
|
}
|
||||||
|
|
||||||
for await (const chunk of chunks) {
|
for await (const chunk of chunks) {
|
||||||
const chunkPath = path.join(chunksPath, chunk)
|
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)
|
const data = await fs.promises.readFile(chunkPath)
|
||||||
|
|
||||||
fileSize += data.length
|
fileSize += data.length
|
||||||
@ -85,13 +94,17 @@ export async function handleChunkFile(fileStream, { tmpDir, headers, maxFileSize
|
|||||||
|
|
||||||
// make sure chunk is in range
|
// make sure chunk is in range
|
||||||
if (chunkCount < 0 || chunkCount >= totalChunks) {
|
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 is the first chunk check if dir exists before write things
|
||||||
if (chunkCount === 0) {
|
if (chunkCount === 0) {
|
||||||
if (!await fs.promises.stat(chunksPath).catch(() => false)) {
|
try {
|
||||||
await fs.promises.mkdir(chunksPath, { recursive: true })
|
if (!await fs.promises.stat(chunksPath).catch(() => false)) {
|
||||||
|
await fs.promises.mkdir(chunksPath, { recursive: true })
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return reject(new OperationError(500, error.message))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,5 +16,6 @@ export default {
|
|||||||
links: { type: Array, default: [] },
|
links: { type: Array, default: [] },
|
||||||
location: { type: String, default: null },
|
location: { type: String, default: null },
|
||||||
birthday: { type: Date, default: null, select: false },
|
birthday: { type: Date, default: null, select: false },
|
||||||
|
accept_tos: { type: Boolean, default: false },
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -6,9 +6,9 @@ import Account from "@classes/account"
|
|||||||
export default async (payload) => {
|
export default async (payload) => {
|
||||||
requiredFields(["username", "password", "email"], 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.")
|
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}`,
|
avatar: avatar ?? `https://api.dicebear.com/7.x/thumbs/svg?seed=${username}`,
|
||||||
roles: roles,
|
roles: roles,
|
||||||
created_at: new Date().getTime(),
|
created_at: new Date().getTime(),
|
||||||
acceptTos: acceptTos,
|
accept_tos: accept_tos,
|
||||||
})
|
})
|
||||||
|
|
||||||
await user.save()
|
await user.save()
|
||||||
|
@ -37,14 +37,18 @@ export default {
|
|||||||
cachePath: tmpPath,
|
cachePath: tmpPath,
|
||||||
})
|
})
|
||||||
|
|
||||||
fs.promises.rm(tmpPath, { recursive: true, force: true })
|
fs.promises.rm(tmpPath, { recursive: true, force: true }).catch(() => {
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
return result
|
return result
|
||||||
} catch (error) {
|
} 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")
|
throw new OperationError(error.code ?? 500, error.message ?? "Failed to upload file")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -35,6 +35,10 @@ export async function b2Upload({
|
|||||||
bucketId: process.env.B2_BUCKET_ID,
|
bucketId: process.env.B2_BUCKET_ID,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (!fs.existsSync(source)) {
|
||||||
|
throw new OperationError(500, "File not found")
|
||||||
|
}
|
||||||
|
|
||||||
const data = await fs.promises.readFile(source)
|
const data = await fs.promises.readFile(source)
|
||||||
|
|
||||||
await global.b2Storage.uploadFile({
|
await global.b2Storage.uploadFile({
|
||||||
|
@ -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()
|
|
||||||
}
|
|
@ -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()
|
|
||||||
}
|
|
@ -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`))
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,188 +1,29 @@
|
|||||||
import fs from "fs"
|
import { Server } from "linebridge/src/server"
|
||||||
import path from "path"
|
|
||||||
|
|
||||||
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 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 {
|
middlewares = {
|
||||||
static useMiddlewaresOrder = ["useLogger", "useCors", "useAuth", "useErrorHandler"]
|
...SharedMiddlewares
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
comty = global.comty = ComtyClient()
|
contexts = {
|
||||||
|
db: new DbManager(),
|
||||||
db = new DbManager()
|
limits: {},
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async __registerInternalMiddlewares() {
|
async onInitialize() {
|
||||||
let middlewaresPath = fs.readdirSync(path.resolve(__dirname, "useMiddlewares"))
|
await this.contexts.db.initialize()
|
||||||
|
|
||||||
// sort middlewares
|
this.contexts.limits = await LimitsClass.get()
|
||||||
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`)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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,
|
|
||||||
})
|
|
@ -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()
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
|
@ -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,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
@ -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()
|
|
||||||
}
|
|
@ -29,6 +29,13 @@ export default async (payload = {}) => {
|
|||||||
throw new OperationError(500, `An error has occurred: ${err.message}`)
|
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)
|
||||||
global.rtengine.io.of("/").emit(`post.delete.${post_id}`, post_id)
|
global.rtengine.io.of("/").emit(`post.delete.${post_id}`, post_id)
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ export default async (payload = {}) => {
|
|||||||
postsSavesIds = postsSaves.map((postSave) => postSave.post_id)
|
postsSavesIds = postsSaves.map((postSave) => postSave.post_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
let [usersData, likesData, repliesData] = await Promise.all([
|
let [usersData, likesData] = await Promise.all([
|
||||||
User.find({
|
User.find({
|
||||||
_id: {
|
_id: {
|
||||||
$in: posts.map((post) => post.user_id)
|
$in: posts.map((post) => post.user_id)
|
||||||
@ -63,8 +63,18 @@ export default async (payload = {}) => {
|
|||||||
|
|
||||||
if (post.reply_to) {
|
if (post.reply_to) {
|
||||||
post.reply_to_data = await Post.findById(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()] ?? []
|
let likes = likesData[post._id.toString()] ?? []
|
||||||
|
|
||||||
post.countLikes = likes.length
|
post.countLikes = likes.length
|
||||||
|
Loading…
x
Reference in New Issue
Block a user