-
+ return
+
@@ -44,18 +45,10 @@ const TVStudioPage = (props) => {
}
{
- !selectedProfileId &&
-
+ !selectedProfileId &&
+
Select profile or create new
-
+
}
diff --git a/packages/app/src/pages/studio/tv/index.less b/packages/app/src/pages/studio/tv/index.less
index cf4d28d3..2ceb17cf 100644
--- a/packages/app/src/pages/studio/tv/index.less
+++ b/packages/app/src/pages/studio/tv/index.less
@@ -1,17 +1,19 @@
-.main-page {
+.tvstudio-page {
display: flex;
flex-direction: column;
- height: 100%;
+ width: 100%;
gap: 10px;
- .main-page-actions {
+ .tvstudio-page-actions {
display: flex;
flex-direction: row;
justify-content: space-between;
+ width: 100%;
+
gap: 10px;
.profile-selector {
@@ -21,4 +23,15 @@
width: 100%;
}
}
+
+ .tvstudio-page-selector-hint {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+
+ width: 100%;
+
+ padding: 50px 0;
+ }
}
\ No newline at end of file
diff --git a/packages/app/src/pages/timeline/index.jsx b/packages/app/src/pages/timeline/index.jsx
index 0bf86aa6..34e85eb1 100755
--- a/packages/app/src/pages/timeline/index.jsx
+++ b/packages/app/src/pages/timeline/index.jsx
@@ -3,31 +3,52 @@ import { Translation } from "react-i18next"
import { PagePanelWithNavMenu } from "@components/PagePanels"
+import usePageWidgets from "@hooks/usePageWidgets"
+
import Tabs from "./tabs"
-export default class Home extends React.Component {
- render() {
- return
{(t) => t("Create")},
- props: {
- type: "primary",
- onClick: app.controls.openPostCreator
- }
- },
- ]}
- onTabChange={() => {
- app.layout.scrollTo({
- top: 0,
- })
- }}
- useSetQueryType
- transition
- masked
- />
- }
-}
\ No newline at end of file
+const TrendingsCard = () => {
+ return
+
+ Trendings
+
+
+
+ XD
+
+
+}
+
+const TimelinePage = () => {
+ usePageWidgets([
+ {
+ id: "trendings",
+ component: TrendingsCard
+ }
+ ])
+
+ return {(t) => t("Create")},
+ props: {
+ type: "primary",
+ onClick: app.controls.openPostCreator
+ }
+ },
+ ]}
+ onTabChange={() => {
+ app.layout.scrollTo({
+ top: 0,
+ })
+ }}
+ useSetQueryType
+ transition
+ masked
+ />
+}
+
+export default TimelinePage
\ No newline at end of file
diff --git a/packages/app/src/settings/about/index.jsx b/packages/app/src/settings/about/index.jsx
index 8c274792..ab28ae36 100755
--- a/packages/app/src/settings/about/index.jsx
+++ b/packages/app/src/settings/about/index.jsx
@@ -1,8 +1,6 @@
import React from "react"
import * as antd from "antd"
-// import { version as linebridgeVersion } from "linebridge/package.json"
-
import { Icons } from "@components/Icons"
import LatencyIndicator from "@components/PerformanceIndicators/latency"
@@ -198,7 +196,7 @@ export default {
- {app.__eviteVersion ?? "Unknown"}
+ {app.__version ?? "Unknown"}
diff --git a/packages/app/src/settings/apparence/index.jsx b/packages/app/src/settings/apparence/index.jsx
index c3cdc989..c76719c5 100755
--- a/packages/app/src/settings/apparence/index.jsx
+++ b/packages/app/src/settings/apparence/index.jsx
@@ -90,6 +90,10 @@ export default {
label: "Varela Round",
value: "'Varela Round', sans-serif"
},
+ {
+ label: "Manrope",
+ value: "'Manrope', sans-serif"
+ }
]
},
defaultValue: () => {
diff --git a/packages/app/src/settings/tap_share/badge_editor/index.jsx b/packages/app/src/settings/tap_share/badge_editor/index.jsx
new file mode 100644
index 00000000..cf412779
--- /dev/null
+++ b/packages/app/src/settings/tap_share/badge_editor/index.jsx
@@ -0,0 +1,15 @@
+import React from "react"
+import * as antd from "antd"
+
+import "./index.less"
+
+const BadgeEditor = (props) => {
+ return
+}
+
+export default BadgeEditor
\ No newline at end of file
diff --git a/packages/app/src/components/MusicStudio/VideoEditor/index.less b/packages/app/src/settings/tap_share/badge_editor/index.less
similarity index 100%
rename from packages/app/src/components/MusicStudio/VideoEditor/index.less
rename to packages/app/src/settings/tap_share/badge_editor/index.less
diff --git a/packages/app/src/settings/tap_share/steps/data_editor/index.jsx b/packages/app/src/settings/tap_share/steps/data_editor/index.jsx
index 29fef5c4..0ef1a00c 100755
--- a/packages/app/src/settings/tap_share/steps/data_editor/index.jsx
+++ b/packages/app/src/settings/tap_share/steps/data_editor/index.jsx
@@ -1,6 +1,5 @@
import React from "react"
import * as antd from "antd"
-import { Input } from "antd"
import { Icons } from "@components/Icons"
@@ -8,8 +7,14 @@ import NFCModel from "comty.js/models/nfc"
import StepsContext from "../../context"
+import "./index.less"
+
export default (props) => {
const context = React.useContext(StepsContext)
+ const ref = React.useRef()
+ const [form] = antd.Form.useForm()
+
+ const behaviorType = antd.Form.useWatch("behavior", form)
if (!context.values.serial) {
app.message.error("Serial not available.")
@@ -20,17 +25,19 @@ export default (props) => {
}
const handleOnFinish = async (values) => {
+ console.log({ values })
+
context.setValue("alias", values.alias)
context.setValue("behavior", values.behavior)
const result = await NFCModel.registerTag(context.values.serial, {
alias: values.alias,
- behavior: values.behavior
+ behavior: values.behavior,
}).catch((err) => {
console.error(err)
app.message.error("Cannot register your tag. Please try again.")
-
+
return false
})
@@ -55,7 +62,10 @@ export default (props) => {
return context.next()
}
- return
+ return
Tag Data
@@ -68,6 +78,7 @@ export default (props) => {
alias: context.values.alias,
behavior: context.values.behavior,
}}
+ form={form}
>
{
Serial
>}
>
-
@@ -109,10 +120,11 @@ export default (props) => {
What will happen when someone taps your tag?
-
+
{
message: "Please select your tag behavior."
}
]}
+ initialValue={"url"}
+ noStyle
>
-
- Custom URL
-
-
-
- Profile
-
-
-
- Random list
-
-
-
-
-
- ref.current}
+ options={[
+ {
+ value: "url",
+ label:
+
+ Custom URL
+
+ },
+ {
+ value: "badge",
+ label:
+
+ Badge
+
+ },
+ {
+ value: "random_list",
+ label:
+
+ Random list
+
+ }
+ ]}
/>
+
+ {
+ behaviorType?.type !== "badge" &&
+
+
+ }
diff --git a/packages/app/src/settings/tap_share/steps/data_editor/index.less b/packages/app/src/settings/tap_share/steps/data_editor/index.less
new file mode 100644
index 00000000..1bf45b00
--- /dev/null
+++ b/packages/app/src/settings/tap_share/steps/data_editor/index.less
@@ -0,0 +1,15 @@
+.ant-form_with_selector {
+ display: flex;
+ flex-direction: column !important;
+
+ align-items: flex-start !important;
+ justify-content: flex-start !important;
+
+ .ant-select {
+ width: 100% !important;
+ }
+
+ .ant-input {
+ width: 100% !important;
+ }
+}
\ No newline at end of file
diff --git a/packages/app/src/styles/animations.less b/packages/app/src/styles/animations.less
index 2fc35b5c..f70c6d68 100755
--- a/packages/app/src/styles/animations.less
+++ b/packages/app/src/styles/animations.less
@@ -112,4 +112,17 @@
60% {
transform: translateY(-3px);
}
+}
+
+@keyframes rootScaleOut {
+ 0% {
+ transform: scale(1);
+ }
+
+ 100% {
+ border-radius: 24px;
+ transform: perspective(1000px) scale(0.99);
+ box-shadow: 0 0 500px 10px var(--colorPrimary);
+ overflow: hidden;
+ }
}
\ No newline at end of file
diff --git a/packages/app/src/styles/fixments.less b/packages/app/src/styles/fixments.less
index cc41d020..c8524e52 100755
--- a/packages/app/src/styles/fixments.less
+++ b/packages/app/src/styles/fixments.less
@@ -83,7 +83,19 @@
}
.ant-dropdown-menu {
- background-color: var(--background-color-primary) !important;
+ background-color: rgba(var(--layoutBackgroundColor), 0.8) !important;
+ backdrop-filter: blur(3px);
+
+ .ant-dropdown-menu-item {
+ .ant-dropdown-menu-title-content {
+ display: flex;
+ flex-direction: row;
+
+ align-items: center;
+
+ gap: 8px;
+ }
+ }
}
// fix buttons
diff --git a/packages/app/src/styles/fonts.less b/packages/app/src/styles/fonts.less
index 7319703b..f932545c 100755
--- a/packages/app/src/styles/fonts.less
+++ b/packages/app/src/styles/fonts.less
@@ -2,6 +2,7 @@
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;600;700;800;900&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Varela+Round&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100..900;1,100..900&display=swap');
+@import url('https://fonts.googleapis.com/css2?family=Manrope:wght@200..800&display=swap');
/* Required secondary fonts */
@import url('https://fonts.googleapis.com/css2?family=DM+Mono:ital,wght@0,300;0,400;0,500;1,300;1,400;1,500&display=swap');
diff --git a/packages/app/src/styles/index.less b/packages/app/src/styles/index.less
index 8549a3b4..febf227e 100755
--- a/packages/app/src/styles/index.less
+++ b/packages/app/src/styles/index.less
@@ -1,3 +1,4 @@
+@import "@styles/layout.less";
@import "@styles/animations.less";
@import "@styles/vars.less";
@import "@styles/fonts.less";
@@ -40,6 +41,10 @@ p {
font-size: calc(1em * var(--fontScale));
}
+code {
+ user-select: text !important;
+}
+
h1,
h2,
h3,
@@ -49,6 +54,12 @@ h6,
p,
span,
a {
+ display: inline-flex;
+ flex-direction: row;
+ align-items: center;
+
+ gap: 5px;
+
margin: 0;
}
@@ -147,14 +158,14 @@ html {
-webkit-overflow-scrolling: touch;
- &.root-scale-effect {
- background-color: black !important;
+ // &.root-scale-effect {
+ // background-color: black !important;
- #root {
- animation: rootScaleOut 250ms ease-in-out forwards;
- animation-delay: 0;
- }
- }
+ // #root {
+ // animation: rootScaleOut 250ms ease-in-out forwards;
+ // animation-delay: 0;
+ // }
+ // }
&.centered-content {
.content_layout {
@@ -206,6 +217,7 @@ svg {
}
*:not(input):not(textarea):not(a) {
+ user-select: none;
-webkit-user-select: none;
/* disable selection/Copy of UIWebView */
-webkit-touch-callout: none;
@@ -368,15 +380,58 @@ svg {
gap: 10px;
}
-@keyframes rootScaleOut {
- 0% {
- transform: scale(1);
+.key-value-field {
+ display: flex;
+ flex-direction: column;
+
+ gap: 5px;
+
+ .key-value-field-key {
+ display: flex;
+ flex-direction: row;
+
+ align-items: center;
+
+ gap: 10px;
+
+ font-weight: bold;
+ font-size: 0.9rem;
}
- 100% {
- border-radius: 24px;
- transform: perspective(1000px) scale(0.99);
- box-shadow: 0 0 500px 10px var(--colorPrimary);
- overflow: hidden;
+ .key-value-field-description {
+ display: flex;
+ flex-direction: column;
+
+ gap: 5px;
+ opacity: 0.8;
+ }
+
+ .key-value-field-value {
+ display: flex;
+ flex-direction: row;
+
+ align-items: center;
+
+ gap: 10px;
+ padding: 7px 5px;
+
+ border-radius: 8px;
+ background-color: var(--background-color-accent);
+
+ font-family: "DM Mono", monospace;
+
+ font-size: 0.7rem;
+
+ h1,
+ h2,
+ h3,
+ h4,
+ h5,
+ h6,
+ p,
+ span,
+ a {
+ user-select: all !important;
+ }
}
}
\ No newline at end of file
diff --git a/packages/app/src/styles/layout.less b/packages/app/src/styles/layout.less
new file mode 100644
index 00000000..e04f26c8
--- /dev/null
+++ b/packages/app/src/styles/layout.less
@@ -0,0 +1,67 @@
+// FLEX
+.flex-row {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+}
+
+.flex-column {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+}
+
+// ALINGMENT
+.align-start {
+ align-items: flex-start;
+}
+
+.align-center {
+ align-items: center;
+}
+
+.align-end {
+ align-items: flex-end;
+}
+
+// JUSTIFICATION
+.justify-center {
+ justify-content: center;
+}
+
+.justify-start {
+ justify-content: flex-start;
+}
+
+.justify-end {
+ justify-content: flex-end;
+}
+
+.justify-space-between {
+ justify-content: space-between;
+}
+
+.justify-space-around {
+ justify-content: space-around;
+}
+
+.justify-space-evenly {
+ justify-content: space-evenly;
+}
+
+// GAPS
+.gap-10,
+.gap10 {
+ gap: 10px;
+}
+
+.gap-5,
+.gap5 {
+ gap: 5px;
+}
+
+// COLORS & BG
+.acrylic-bg {
+ background-color: rgba(var(--layoutBackgroundColor), 0.8) !important;
+ backdrop-filter: blur(3px);
+}
\ No newline at end of file
diff --git a/packages/server/boot b/packages/server/boot
index 6b80c823..673f3360 100755
--- a/packages/server/boot
+++ b/packages/server/boot
@@ -2,13 +2,13 @@
require("dotenv").config()
require("sucrase/register")
-const path = require("path")
-const Module = require("module")
-const { Buffer } = require("buffer")
-const { webcrypto: crypto } = require("crypto")
+const path = require("node:path")
+const Module = require("node:module")
+const { Buffer } = require("node:buffer")
+const { webcrypto: crypto } = require("node:crypto")
const { InfisicalClient } = require("@infisical/sdk")
-
const moduleAlias = require("module-alias")
+const { onExit } = require("signal-exit")
// Override file execution arg
process.argv.splice(1, 1)
@@ -135,6 +135,8 @@ async function Boot(main) {
throw new Error("main class is not defined")
}
+ console.log(`[BOOT] Booting in [${global.isProduction ? "production" : "development"}] mode...`)
+
if (process.env.INFISICAL_CLIENT_ID && process.env.INFISICAL_CLIENT_SECRET) {
console.log(`[BOOT] INFISICAL Credentials found, injecting env variables from INFISICAL...`)
await injectEnvFromInfisical()
@@ -142,6 +144,18 @@ async function Boot(main) {
const instance = new main()
+ onExit((code, signal) => {
+ console.log(`[BOOT] Cleaning up...`)
+
+ if (typeof instance.onClose === "function") {
+ instance.onClose()
+ }
+
+ instance.engine.close()
+ }, {
+ alwaysLast: true,
+ })
+
await instance.initialize()
if (process.env.lb_service && process.send) {
@@ -153,13 +167,15 @@ async function Boot(main) {
return instance
}
-console.log(`[BOOT] Booting in [${global.isProduction ? "production" : "development"}] mode...`)
+try {
+ // Apply patches
+ registerPatches()
-// Apply patches
-registerPatches()
+ // Apply aliases
+ registerAliases()
-// Apply aliases
-registerAliases()
-
-// execute main
-Module.runMain()
\ No newline at end of file
+ // execute main
+ Module.runMain()
+} catch (error) {
+ console.error("[BOOT] ❌ Boot error: ", error)
+}
\ No newline at end of file
diff --git a/packages/server/classes/ChunkFileUpload/index.js b/packages/server/classes/ChunkFileUpload/index.js
index ace09795..590c5f11 100755
--- a/packages/server/classes/ChunkFileUpload/index.js
+++ b/packages/server/classes/ChunkFileUpload/index.js
@@ -20,39 +20,57 @@ export function checkTotalSize(
}
export function checkChunkUploadHeaders(headers) {
- if (
- !headers["uploader-chunk-number"] ||
- !headers["uploader-chunks-total"] ||
- !headers["uploader-original-name"] ||
- !headers["uploader-file-id"] ||
- !headers["uploader-chunks-total"].match(/^[0-9]+$/) ||
- !headers["uploader-chunk-number"].match(/^[0-9]+$/)
- ) {
- return false
+ const requiredHeaders = [
+ "uploader-chunk-number",
+ "uploader-chunks-total",
+ "uploader-original-name",
+ "uploader-file-id"
+ ]
+
+ for (const header of requiredHeaders) {
+ if (!headers[header] || typeof headers[header] !== "string") {
+ return false
+ }
+
+ if (
+ (header === "uploader-chunk-number" || header === "uploader-chunks-total")
+ && !/^[0-9]+$/.test(headers[header])
+ ) {
+ return false
+ }
}
return true
}
+
export function createAssembleChunksPromise({
- chunksPath, // chunks to assemble
- filePath, // final assembled file path
+ chunksPath,
+ filePath,
maxFileSize,
}) {
return () => new Promise(async (resolve, reject) => {
let fileSize = 0
if (!fs.existsSync(chunksPath)) {
- return reject(new OperationError(500,"No chunks found"))
+ return reject(new OperationError(500, "No chunks found"))
}
- const chunks = await fs.promises.readdir(chunksPath)
+ let chunks = await fs.promises.readdir(chunksPath)
if (chunks.length === 0) {
- throw new OperationError(500, "No chunks found")
+ return reject(new OperationError(500, "No chunks found"))
}
- for await (const chunk of chunks) {
+ // Ordenar los chunks numéricamente
+ chunks = chunks.sort((a, b) => {
+ const aNum = parseInt(a, 10)
+ const bNum = parseInt(b, 10)
+
+ return aNum - bNum
+ })
+
+ for (const chunk of chunks) {
const chunkPath = path.join(chunksPath, chunk)
if (!fs.existsSync(chunkPath)) {
@@ -60,18 +78,13 @@ export function createAssembleChunksPromise({
}
const data = await fs.promises.readFile(chunkPath)
-
fileSize += data.length
- // check if final file gonna exceed max file size
- // in case early estimation is wrong (due client send bad headers)
if (fileSize > maxFileSize) {
return reject(new OperationError(413, "File exceeds max total file size, aborting assembly..."))
}
await fs.promises.appendFile(filePath, data)
-
- continue
}
return resolve({
@@ -81,7 +94,15 @@ export function createAssembleChunksPromise({
})
}
-export async function handleChunkFile(fileStream, { tmpDir, headers, maxFileSize, maxChunkSize }) {
+export async function handleChunkFile(
+ fileStream,
+ {
+ tmpDir,
+ headers,
+ maxFileSize,
+ maxChunkSize
+ }
+) {
return await new Promise(async (resolve, reject) => {
const chunksPath = path.join(tmpDir, headers["uploader-file-id"], "chunks")
const chunkPath = path.join(chunksPath, headers["uploader-chunk-number"])
@@ -94,7 +115,7 @@ export async function handleChunkFile(fileStream, { tmpDir, headers, maxFileSize
// make sure chunk is in range
if (chunkCount < 0 || chunkCount >= totalChunks) {
- throw new OperationError(500, "Chunk is out of range")
+ return reject(new OperationError(500, "Chunk is out of range"))
}
// if is the first chunk check if dir exists before write things
@@ -162,11 +183,14 @@ export async function handleChunkFile(fileStream, { tmpDir, headers, maxFileSize
})
}
-export async function uploadChunkFile(req, {
- tmpDir,
- maxFileSize,
- maxChunkSize,
-}) {
+export async function uploadChunkFile(
+ req,
+ {
+ tmpDir,
+ maxFileSize,
+ maxChunkSize,
+ }
+) {
return await new Promise(async (resolve, reject) => {
if (!checkChunkUploadHeaders(req.headers)) {
reject(new OperationError(400, "Missing header(s)"))
diff --git a/packages/server/classes/MultiqualityHLSJob/index.js b/packages/server/classes/MultiqualityHLSJob/index.js
new file mode 100644
index 00000000..0bbf0e74
--- /dev/null
+++ b/packages/server/classes/MultiqualityHLSJob/index.js
@@ -0,0 +1,147 @@
+import fs from "node:fs"
+import path from "node:path"
+import { exec } from "node:child_process"
+import { EventEmitter } from "node:events"
+
+export default class MultiqualityHLSJob {
+ constructor({
+ input,
+ outputDir,
+ outputMasterName = "master.m3u8",
+ levels,
+ }) {
+ this.input = input
+ this.outputDir = outputDir
+ this.levels = levels
+ this.outputMasterName = outputMasterName
+
+ this.bin = require("ffmpeg-static")
+
+ return this
+ }
+
+ events = new EventEmitter()
+
+ buildCommand = () => {
+ const cmdStr = [
+ this.bin,
+ `-v quiet -stats`,
+ `-i ${this.input}`,
+ `-filter_complex`,
+ ]
+
+ // set split args
+ let splitLevels = [
+ `[0:v]split=${this.levels.length}`
+ ]
+
+ this.levels.forEach((level, i) => {
+ splitLevels[0] += (`[v${i + 1}]`)
+ })
+
+ for (const [index, level] of this.levels.entries()) {
+ if (level.original) {
+ splitLevels.push(`[v1]copy[v1out]`)
+ continue
+ }
+
+ let scaleFilter = `[v${index + 1}]scale=w=${level.width}:h=trunc(ow/a/2)*2[v${index + 1}out]`
+
+ splitLevels.push(scaleFilter)
+ }
+
+ cmdStr.push(`"${splitLevels.join(";")}"`)
+
+ // set levels map
+ for (const [index, level] of this.levels.entries()) {
+ let mapArgs = [
+ `-map "[v${index + 1}out]"`,
+ `-x264-params "nal-hrd=cbr:force-cfr=1"`,
+ `-c:v:${index} ${level.codec}`,
+ `-b:v:${index} ${level.bitrate}`,
+ `-maxrate:v:${index} ${level.bitrate}`,
+ `-minrate:v:${index} ${level.bitrate}`,
+ `-bufsize:v:${index} ${level.bitrate}`,
+ `-preset ${level.preset}`,
+ `-g 48`,
+ `-sc_threshold 0`,
+ `-keyint_min 48`,
+ ]
+
+ cmdStr.push(...mapArgs)
+ }
+
+ // set output
+ cmdStr.push(`-f hls`)
+ cmdStr.push(`-hls_time 2`)
+ cmdStr.push(`-hls_playlist_type vod`)
+ cmdStr.push(`-hls_flags independent_segments`)
+ cmdStr.push(`-hls_segment_type mpegts`)
+ cmdStr.push(`-hls_segment_filename stream_%v/data%02d.ts`)
+ cmdStr.push(`-master_pl_name ${this.outputMasterName}`)
+
+ cmdStr.push(`-var_stream_map`)
+
+ let streamMapVar = []
+
+ for (const [index, level] of this.levels.entries()) {
+ streamMapVar.push(`v:${index}`)
+ }
+
+ cmdStr.push(`"${streamMapVar.join(" ")}"`)
+ cmdStr.push(`"stream_%v/stream.m3u8"`)
+
+ return cmdStr.join(" ")
+ }
+
+ run = () => {
+ const cmdStr = this.buildCommand()
+
+ console.log(cmdStr)
+
+ const cwd = `${path.dirname(this.input)}/hls`
+
+ if (!fs.existsSync(cwd)) {
+ fs.mkdirSync(cwd, { recursive: true })
+ }
+
+ console.log(`[HLS] Started multiquality transcode`, {
+ input: this.input,
+ cwd: cwd,
+ })
+
+ const process = exec(
+ cmdStr,
+ {
+ cwd: cwd,
+ },
+ (error, stdout, stderr) => {
+ if (error) {
+ console.log(`[HLS] Failed to transcode >`, error)
+
+ return this.events.emit("error", error)
+ }
+
+ if (stderr) {
+ //return this.events.emit("error", stderr)
+ }
+
+ console.log(`[HLS] Finished transcode >`, cwd)
+
+ return this.events.emit("end", {
+ filepath: path.join(cwd, this.outputMasterName),
+ isDirectory: true,
+ })
+ }
+ )
+
+ process.stdout.on("data", (data) => {
+ console.log(data.toString())
+ })
+ }
+
+ on = (key, cb) => {
+ this.events.on(key, cb)
+ return this
+ }
+}
\ No newline at end of file
diff --git a/packages/server/classes/StorageClient/index.js b/packages/server/classes/StorageClient/index.js
index 9d350cbd..021c11e5 100755
--- a/packages/server/classes/StorageClient/index.js
+++ b/packages/server/classes/StorageClient/index.js
@@ -38,8 +38,12 @@ export class StorageClient extends Minio.Client {
this.defaultRegion = String(options.defaultRegion)
}
- composeRemoteURL = (key) => {
- const _path = path.join(this.defaultBucket, key)
+ composeRemoteURL = (key, extraKey) => {
+ let _path = path.join(this.defaultBucket, key)
+
+ if (typeof extraKey === "string") {
+ _path = path.join(_path, extraKey)
+ }
return `${this.protocol}//${this.host}:${this.port}/${_path}`
}
diff --git a/packages/server/db_models/NFCTags/index.js b/packages/server/db_models/NFCTags/index.js
index d83c9e39..506ab666 100755
--- a/packages/server/db_models/NFCTags/index.js
+++ b/packages/server/db_models/NFCTags/index.js
@@ -32,6 +32,10 @@ export default {
endpoint_url: {
type: String,
default: "https://comty.app/nfc/no_endpoint"
+ },
+ origin: {
+ type: String,
+ default: "comty.app"
}
}
}
\ No newline at end of file
diff --git a/packages/server/db_models/activationCode/index.js b/packages/server/db_models/activationCode/index.js
new file mode 100644
index 00000000..98dea374
--- /dev/null
+++ b/packages/server/db_models/activationCode/index.js
@@ -0,0 +1,21 @@
+export default {
+ name: "ActivationCode",
+ collection: "activation_codes",
+ schema: {
+ event: {
+ type: String,
+ required: true,
+ },
+ user_id: {
+ type: String,
+ required: true,
+ },
+ code: {
+ type: String,
+ required: true,
+ },
+ date: {
+ type: Date,
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/server/db_models/post/index.js b/packages/server/db_models/post/index.js
index 73702094..23878c92 100755
--- a/packages/server/db_models/post/index.js
+++ b/packages/server/db_models/post/index.js
@@ -2,12 +2,36 @@ export default {
name: "Post",
collection: "posts",
schema: {
- user_id: { type: String, required: true },
- created_at: { type: String, required: true },
- message: { type: String },
- attachments: { type: Array, default: [] },
- flags: { type: Array, default: [] },
- reply_to: { type: String, default: null },
- updated_at: { type: String, default: null },
+ user_id: {
+ type: String,
+ required: true
+ },
+ created_at: {
+ type: String,
+ required: true
+ },
+ message: {
+ type: String
+ },
+ attachments: {
+ type: Array,
+ default: []
+ },
+ flags: {
+ type: Array,
+ default: []
+ },
+ reply_to: {
+ type: String,
+ default: null
+ },
+ updated_at: {
+ type: String,
+ default: null
+ },
+ poll_options: {
+ type: Array,
+ default: null
+ }
}
}
\ No newline at end of file
diff --git a/packages/server/db_models/track/index.js b/packages/server/db_models/track/index.js
index 744ba32f..57d97664 100755
--- a/packages/server/db_models/track/index.js
+++ b/packages/server/db_models/track/index.js
@@ -38,5 +38,9 @@ export default {
type: Object,
required: true,
},
+ lyrics_enabled: {
+ type: Boolean,
+ default: false
+ }
}
}
\ No newline at end of file
diff --git a/packages/server/db_models/musicLyrics/index.js b/packages/server/db_models/trackLyrics/index.js
similarity index 61%
rename from packages/server/db_models/musicLyrics/index.js
rename to packages/server/db_models/trackLyrics/index.js
index 577e4771..4bc97a5c 100644
--- a/packages/server/db_models/musicLyrics/index.js
+++ b/packages/server/db_models/trackLyrics/index.js
@@ -8,6 +8,13 @@ export default {
},
lrc: {
type: Object,
+ default: {}
+ },
+ video_source: {
+ type: String,
+ },
+ sync_audio_at: {
+ type: String,
}
}
}
\ No newline at end of file
diff --git a/packages/server/db_models/user/index.js b/packages/server/db_models/user/index.js
index 709a9b44..8139851b 100755
--- a/packages/server/db_models/user/index.js
+++ b/packages/server/db_models/user/index.js
@@ -2,20 +2,72 @@ export default {
name: "User",
collection: "accounts",
schema: {
- username: { type: String, required: true },
- password: { type: String, required: true, select: false },
- email: { type: String, required: true, select: false },
- description: { type: String, default: null },
- created_at: { type: String },
- public_name: { type: String, default: null },
- cover: { type: String, default: null },
- avatar: { type: String, default: null },
- roles: { type: Array, default: [] },
- verified: { type: Boolean, default: false },
- badges: { type: Array, default: [] },
- links: { type: Array, default: [] },
- location: { type: String, default: null },
- birthday: { type: Date, default: null, select: false },
- accept_tos: { type: Boolean, default: false },
+ username: {
+ type: String,
+ required: true
+ },
+ password: {
+ type: String,
+ required: true,
+ select: false
+ },
+ email: {
+ type: String,
+ required: true,
+ select: false
+ },
+ description: {
+ type: String,
+ default: null
+ },
+ created_at: {
+ type: String
+ },
+ public_name: {
+ type: String,
+ default: null
+ },
+ cover: {
+ type: String,
+ default: null
+ },
+ avatar: {
+ type:
+ String,
+ default: null
+ },
+ roles: {
+ type: Array,
+ default: []
+ },
+ verified: {
+ type: Boolean,
+ default: false
+ },
+ badges: {
+ type: Array,
+ default: []
+ },
+ links: {
+ type: Array,
+ default: []
+ },
+ location: {
+ type: String,
+ default: null
+ },
+ birthday: {
+ type: Date,
+ default: null,
+ select: false
+ },
+ accept_tos: {
+ type: Boolean,
+ default: false
+ },
+ activated: {
+ type: Boolean,
+ default: false,
+ }
}
}
\ No newline at end of file
diff --git a/packages/server/db_models/votePoll/index.js b/packages/server/db_models/votePoll/index.js
new file mode 100644
index 00000000..4dde0d05
--- /dev/null
+++ b/packages/server/db_models/votePoll/index.js
@@ -0,0 +1,18 @@
+export default {
+ name: "VotePoll",
+ collection: "votes_poll",
+ schema: {
+ user_id: {
+ type: String,
+ required: true
+ },
+ post_id: {
+ type: String,
+ required: true
+ },
+ option_id: {
+ type: String,
+ required: true
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/server/gateway/index.js b/packages/server/gateway/index.js
index 633e006f..c07292a5 100644
--- a/packages/server/gateway/index.js
+++ b/packages/server/gateway/index.js
@@ -5,7 +5,7 @@ import Spinnies from "spinnies"
import { Observable } from "@gullerya/object-observer"
import { dots as DefaultSpinner } from "spinnies/spinners.json"
import EventEmitter from "@foxify/events"
-import IPCRouter from "linebridge/dist/server/classes/IPCRouter"
+import IPCRouter from "linebridge/dist/classes/IPCRouter"
import chokidar from "chokidar"
import { onExit } from "signal-exit"
import chalk from "chalk"
@@ -76,6 +76,7 @@ export default class Gateway {
cwd,
onReload: this.serviceHandlers.onReload,
onClose: this.serviceHandlers.onClose,
+ onError: this.serviceHandlers.onError,
onIPCData: this.serviceHandlers.onIPCData,
})
@@ -218,6 +219,7 @@ export default class Gateway {
cwd,
onReload: this.serviceHandlers.onReload,
onClose: this.serviceHandlers.onClose,
+ onError: this.serviceHandlers.onError,
onIPCData: this.serviceHandlers.onIPCData,
})
@@ -246,11 +248,18 @@ export default class Gateway {
console.log(`[${id}] Exit with code ${code}`)
+ if (err) {
+ console.error(err)
+ }
+
// try to unregister from proxy
this.proxy.unregisterAllFromService(id)
this.serviceRegistry[id].ready = false
},
+ onError: (id, err) => {
+ console.error(`[${id}] Error`, err)
+ },
}
onAllServicesReload = (id) => {
@@ -382,7 +391,7 @@ export default class Gateway {
process.stdout.setMaxListeners(50)
process.stderr.setMaxListeners(50)
-
+
this.services = await scanServices()
this.proxy = new Proxy()
this.ipcRouter = new IPCRouter()
diff --git a/packages/server/gateway/proxy.js b/packages/server/gateway/proxy.js
index 41de2bb7..a4510c76 100644
--- a/packages/server/gateway/proxy.js
+++ b/packages/server/gateway/proxy.js
@@ -1,5 +1,5 @@
import httpProxy from "http-proxy"
-import defaults from "linebridge/dist/server/defaults"
+import defaults from "linebridge/dist/defaults"
import pkg from "../package.json"
diff --git a/packages/server/gateway/utils/spawnService.js b/packages/server/gateway/utils/spawnService.js
index 9a57e120..16c49cc0 100644
--- a/packages/server/gateway/utils/spawnService.js
+++ b/packages/server/gateway/utils/spawnService.js
@@ -9,6 +9,7 @@ export default async ({
cwd,
onReload,
onClose,
+ onError,
onIPCData,
}) => {
const instanceEnv = {
@@ -59,8 +60,12 @@ export default async ({
return onIPCData(id, data)
})
- instance.on("close", (code, err) => {
- return onClose(id, code, err)
+ instance.on("error", (err) => {
+ return onError(id, err)
+ })
+
+ instance.on("close", (code) => {
+ return onClose(id, code)
})
global.ipcRouter.register({ id, instance })
diff --git a/packages/server/package.json b/packages/server/package.json
index 91d36243..f78c4c97 100755
--- a/packages/server/package.json
+++ b/packages/server/package.json
@@ -3,10 +3,10 @@
"version": "1.0.0-beta",
"license": "ComtyLicense",
"private": true,
- "workspaces": [
+ "workspaces": [
"services/*"
- ],
- "scripts": {
+ ],
+ "scripts": {
"start:prod": "cross-env NODE_ENV=production hermes-node ./index.js",
"dev": "cross-env NODE_ENV=development hermes-node ./index.js",
"build:bin": "cd build && pkg ./index.js"
@@ -21,9 +21,10 @@
"comty.js": "^0.60.3",
"dotenv": "^16.4.4",
"http-proxy": "^1.18.1",
+ "hyper-express": "^6.17.2",
"ioredis": "^5.4.1",
"jsonwebtoken": "^9.0.2",
- "linebridge": "^0.20.3",
+ "linebridge": "^0.22.0",
"minio": "^8.0.1",
"module-alias": "^2.2.3",
"mongoose": "^8.5.3",
diff --git a/packages/server/services/auth/auth.service.js b/packages/server/services/auth/auth.service.js
index c673830a..85f81694 100644
--- a/packages/server/services/auth/auth.service.js
+++ b/packages/server/services/auth/auth.service.js
@@ -1,4 +1,4 @@
-import { Server } from "linebridge/dist/server"
+import { Server } from "linebridge"
import DbManager from "@shared-classes/DbManager"
import SharedMiddlewares from "@shared-middlewares"
diff --git a/packages/server/services/auth/classes/account/index.js b/packages/server/services/auth/classes/account/index.js
index 171dd72f..325771bc 100644
--- a/packages/server/services/auth/classes/account/index.js
+++ b/packages/server/services/auth/classes/account/index.js
@@ -6,4 +6,6 @@ export default class Account {
static create = require("./methods/create").default
static sessions = require("./methods/sessions").default
static deleteSession = require("./methods/deleteSession").default
+ static sendActivationCode = require("./methods/sendActivationCode").default
+ static activateAccount = require("./methods/activateAccount").default
}
\ No newline at end of file
diff --git a/packages/server/services/auth/classes/account/methods/activateAccount.js b/packages/server/services/auth/classes/account/methods/activateAccount.js
new file mode 100644
index 00000000..35e2181e
--- /dev/null
+++ b/packages/server/services/auth/classes/account/methods/activateAccount.js
@@ -0,0 +1,50 @@
+import { User, ActivationCode } from "@db_models"
+
+export default async (payload) => {
+ const { code, user_id } = payload
+
+ if (!code) {
+ throw new OperationError(400, "Missing code")
+ }
+
+ if (!user_id) {
+ throw new OperationError(400, "Missing user_id")
+ }
+
+ let user = await User.findOne({
+ _id: user_id,
+ }).select("+email")
+
+ if (!user) {
+ throw new OperationError(404, "User not found")
+ }
+
+ if (user.activated) {
+ throw new OperationError(400, "User already activated")
+ }
+
+ let activationCode = await ActivationCode.findOne({
+ user_id: user._id.toString(),
+ code: code,
+ })
+
+ if (!activationCode) {
+ throw new OperationError(400, "Invalid activation code")
+ }
+
+ user = await User.findOneAndUpdate(
+ {
+ _id: user._id.toString(),
+ },
+ {
+ activated: true
+ },
+ )
+
+ await ActivationCode.deleteOne({
+ user_id: user._id.toString(),
+ code: code,
+ })
+
+ return user.toObject()
+}
\ 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 b39b3628..d64c280a 100644
--- a/packages/server/services/auth/classes/account/methods/create.js
+++ b/packages/server/services/auth/classes/account/methods/create.js
@@ -1,6 +1,7 @@
import bcrypt from "bcrypt"
import { User } from "@db_models"
import requiredFields from "@shared-utils/requiredFields"
+
import Account from "@classes/account"
export default async (payload) => {
@@ -45,12 +46,15 @@ export default async (payload) => {
roles: roles,
created_at: new Date().getTime(),
accept_tos: accept_tos,
+ activated: false,
})
await user.save()
- // TODO: dispatch event bus
- //global.eventBus.emit("user.create", user)
+ await Account.sendActivationCode(user._id.toString())
- return user
+ return {
+ activation_required: true,
+ user: user,
+ }
}
\ No newline at end of file
diff --git a/packages/server/services/auth/classes/account/methods/deleteSession.js b/packages/server/services/auth/classes/account/methods/deleteSession.js
index c887c063..c251d36f 100644
--- a/packages/server/services/auth/classes/account/methods/deleteSession.js
+++ b/packages/server/services/auth/classes/account/methods/deleteSession.js
@@ -20,7 +20,9 @@ export default async (payload = {}) => {
throw new OperationError(400, "Session not found")
}
- await session.delete()
+ await Session.findOneAndDelete({
+ _id: session._id
+ })
return {
success: true
diff --git a/packages/server/services/auth/classes/account/methods/sendActivationCode.js b/packages/server/services/auth/classes/account/methods/sendActivationCode.js
new file mode 100644
index 00000000..6a669d73
--- /dev/null
+++ b/packages/server/services/auth/classes/account/methods/sendActivationCode.js
@@ -0,0 +1,51 @@
+import { User, ActivationCode } from "@db_models"
+
+// set waiting time to 1 minute
+const waitingTime = 60 * 1000
+
+export default async (user_id, event = "account:activation") => {
+ if (!user_id) {
+ throw new OperationError(400, "Missing user_id")
+ }
+
+ const user = await User.findOne({
+ _id: user_id,
+ }).select("+email")
+
+ if (!user) {
+ throw new OperationError(404, "User not found")
+ }
+
+ if (user.activated) {
+ throw new OperationError(400, "User already activated")
+ }
+
+ let activationCode = await ActivationCode.findOne({
+ user_id: user._id,
+ })
+
+ if (activationCode) {
+ // check if activation code is too recent
+ if (activationCode.date.getTime() + waitingTime > new Date().getTime()) {
+ throw new OperationError(400, "Activation code timeout, please try again later")
+ }
+
+ await ActivationCode.deleteOne({
+ user_id: user._id.toString()
+ })
+ }
+
+ activationCode = await ActivationCode.create({
+ event: event,
+ user_id: user._id.toString(),
+ code: Math.floor(Math.random() * 900000) + 100000,
+ date: new Date(),
+ })
+
+ ipc.invoke("ems", "account:activation:send", {
+ activation_code: activationCode.code,
+ user: user.toObject(),
+ })
+
+ return activationCode.toObject()
+}
\ No newline at end of file
diff --git a/packages/server/services/auth/routes/auth/activate/post.js b/packages/server/services/auth/routes/auth/activate/post.js
new file mode 100644
index 00000000..2e1989ac
--- /dev/null
+++ b/packages/server/services/auth/routes/auth/activate/post.js
@@ -0,0 +1,5 @@
+import Account from "@classes/account"
+
+export default async (req, res) => {
+ return await Account.activateAccount(req.body)
+}
\ No newline at end of file
diff --git a/packages/server/services/auth/routes/auth/post.js b/packages/server/services/auth/routes/auth/post.js
index 0d5a123e..cba507ac 100644
--- a/packages/server/services/auth/routes/auth/post.js
+++ b/packages/server/services/auth/routes/auth/post.js
@@ -29,6 +29,13 @@ export default async (req, res) => {
})
}
+ if (user.activated === false) {
+ return res.status(401).json({
+ user_id: user._id.toString(),
+ activation_required: true,
+ })
+ }
+
const userConfig = await UserConfig.findOne({ user_id: user._id.toString() }).catch(() => {
return {}
})
diff --git a/packages/server/services/auth/routes/auth/resend-activation-code/post.js b/packages/server/services/auth/routes/auth/resend-activation-code/post.js
new file mode 100644
index 00000000..8b64752b
--- /dev/null
+++ b/packages/server/services/auth/routes/auth/resend-activation-code/post.js
@@ -0,0 +1,18 @@
+import Account from "@classes/account"
+
+export default async (req, res) => {
+ const { user_id } = req.body
+
+ if (!user_id) {
+ throw new OperationError(400, "Missing user_id")
+ }
+
+ const activationObj = await Account.sendActivationCode(user_id)
+
+ return {
+ ok: true,
+ event: activationObj.event,
+ user_id: activationObj.user_id,
+ date: activationObj.date,
+ }
+}
\ No newline at end of file
diff --git a/packages/server/services/auth/routes/auth/token/post.js b/packages/server/services/auth/routes/auth/token/post.js
new file mode 100644
index 00000000..478444b7
--- /dev/null
+++ b/packages/server/services/auth/routes/auth/token/post.js
@@ -0,0 +1,22 @@
+import AuthToken from "@shared-classes/AuthToken"
+
+export default async (req, res) => {
+ const { token } = req.body
+
+ if (!token) {
+ throw new OperationError(400, "Missing token")
+ }
+
+ const validation = await AuthToken.validate(token)
+
+ if (!validation.valid) {
+ throw new OperationError(401, "Invalid token")
+ }
+
+ return {
+ token: token,
+ decoded: validation.data,
+ session: validation.session,
+ user: validation.user
+ }
+}
\ No newline at end of file
diff --git a/packages/server/services/chats/chats.service.js b/packages/server/services/chats/chats.service.js
index 45fab2e8..eefe36d9 100755
--- a/packages/server/services/chats/chats.service.js
+++ b/packages/server/services/chats/chats.service.js
@@ -1,4 +1,4 @@
-import { Server } from "linebridge/dist/server"
+import { Server } from "linebridge"
import DbManager from "@shared-classes/DbManager"
import RedisClient from "@shared-classes/RedisClient"
@@ -8,7 +8,7 @@ import SharedMiddlewares from "@shared-middlewares"
class API extends Server {
static refName = "chats"
- static useEngine = "hyper-express"
+ static enableWebsockets = true
static routesPath = `${__dirname}/routes`
static wsRoutesPath = `${__dirname}/routes_ws`
static listen_port = process.env.HTTP_LISTEN_PORT ?? 3004
diff --git a/packages/server/services/ems/ems.service.js b/packages/server/services/ems/ems.service.js
index b0b47e2b..3efc85af 100644
--- a/packages/server/services/ems/ems.service.js
+++ b/packages/server/services/ems/ems.service.js
@@ -1,9 +1,8 @@
-import { Server } from "linebridge/dist/server"
+import { Server } from "linebridge"
import nodemailer from "nodemailer"
import DbManager from "@shared-classes/DbManager"
import SharedMiddlewares from "@shared-middlewares"
-
export default class API extends Server {
static refName = "ems"
static useEngine = "hyper-express"
@@ -28,6 +27,7 @@ export default class API extends Server {
}
ipcEvents = {
+ "account:activation:send": require("./ipcEvents/accountActivation").default,
"new:login": require("./ipcEvents/newLogin").default,
"mfa:send": require("./ipcEvents/mfaSend").default,
"apr:send": require("./ipcEvents/aprSend").default,
diff --git a/packages/server/services/ems/ipcEvents/accountActivation.js b/packages/server/services/ems/ipcEvents/accountActivation.js
new file mode 100644
index 00000000..1c6bf946
--- /dev/null
+++ b/packages/server/services/ems/ipcEvents/accountActivation.js
@@ -0,0 +1,19 @@
+import templates from "../templates"
+
+export default async (ctx, data) => {
+ console.log(`EMS Send account activation `, data)
+
+ const { user, activation_code } = data
+
+ const result = await ctx.mailTransporter.sendMail({
+ from: process.env.SMTP_USERNAME,
+ to: user.email,
+ subject: "Account activation",
+ html: templates.account_activation({
+ username: user.username,
+ code: activation_code,
+ }),
+ })
+
+ return result
+}
diff --git a/packages/server/services/ems/templates/account_activation/index.handlebars b/packages/server/services/ems/templates/account_activation/index.handlebars
new file mode 100644
index 00000000..b4f14e22
--- /dev/null
+++ b/packages/server/services/ems/templates/account_activation/index.handlebars
@@ -0,0 +1,328 @@
+
+
+
+
+
Comty
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+
+
+
+ |
+
+
+
+
+
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Hi
+ @{{username}}
+
+
+ |
+
+
+
+
+
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ This is your activation code to continue creating your new
+ account.
+
+
+
+ |
+
+
+
+
+
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/server/services/ems/templates/index.js b/packages/server/services/ems/templates/index.js
index a6f86801..ee344fa0 100644
--- a/packages/server/services/ems/templates/index.js
+++ b/packages/server/services/ems/templates/index.js
@@ -4,6 +4,7 @@ import path from "node:path"
import Handlebars from "handlebars"
export default {
+ account_activation: Handlebars.compile(fs.readFileSync(path.resolve(__dirname, "account_activation/index.handlebars"), "utf-8")),
account_disabled: Handlebars.compile(fs.readFileSync(path.resolve(__dirname, "account_disabled/index.handlebars"), "utf-8")),
new_login: Handlebars.compile(fs.readFileSync(path.resolve(__dirname, "new_login/index.handlebars"), "utf-8")),
mfa_code: Handlebars.compile(fs.readFileSync(path.resolve(__dirname, "mfa_code/index.handlebars"), "utf-8")),
diff --git a/packages/server/services/ems/templates/mfa_code/index.handlebars b/packages/server/services/ems/templates/mfa_code/index.handlebars
index 0914eeff..9fb4dd29 100644
--- a/packages/server/services/ems/templates/mfa_code/index.handlebars
+++ b/packages/server/services/ems/templates/mfa_code/index.handlebars
@@ -227,7 +227,8 @@
style="Margin:0;text-align:left;mso-line-height-alt:24px;mso-ansi-font-size:16px;">
Hi
- @{{username}}
+ @{{username}}
+
@@ -267,7 +268,8 @@
style="Margin:0;text-align:left;mso-line-height-alt:18px;mso-ansi-font-size:16px;">
Here
- is the verification code you need to log in.
+ is the verification code you need to log in.
+
@@ -346,7 +348,7 @@
{{mfa_code}}
+ style="font-size:16px;font-family:'Inter','Arial',sans-serif;font-weight:400;color:#777777;line-height:150%;mso-line-height-alt:24px;mso-ansi-font-size:16px;">{{code}}
@@ -428,7 +430,8 @@
Wasn't
it you? So it looks like someone new is trying to log into
- your account.
+ your account.
+
¡In
this case, we recommend change your password as soon as
- possible!
+ possible!
+
Here's
- more information about this login attempt.
+ more information about this login attempt.
+
diff --git a/packages/server/services/files/file.service.js b/packages/server/services/files/file.service.js
index 88f39622..a57ab147 100755
--- a/packages/server/services/files/file.service.js
+++ b/packages/server/services/files/file.service.js
@@ -1,7 +1,9 @@
-import { Server } from "linebridge/dist/server"
+import { Server } from "linebridge"
import B2 from "backblaze-b2"
+import hePkg from "hyper-express/package.json"
+
import DbManager from "@shared-classes/DbManager"
import RedisClient from "@shared-classes/RedisClient"
import StorageClient from "@shared-classes/StorageClient"
@@ -30,6 +32,8 @@ class API extends Server {
}
async onInitialize() {
+ console.log(`Using HyperExpress v${hePkg.version}`)
+
global.storage = this.contexts.storage
if (process.env.B2_KEY_ID && process.env.B2_APP_KEY) {
diff --git a/packages/server/services/files/package.json b/packages/server/services/files/package.json
index 02adaaba..a2a06202 100755
--- a/packages/server/services/files/package.json
+++ b/packages/server/services/files/package.json
@@ -5,15 +5,16 @@
"backblaze-b2": "^1.7.0",
"busboy": "^1.6.0",
"content-range": "^2.0.2",
+ "ffmpeg-static": "^5.2.0",
"fluent-ffmpeg": "^2.1.2",
"merge-files": "^0.1.2",
"mime-types": "^2.1.35",
- "sharp": "0.32.6",
"minio": "^7.0.32",
"normalize-url": "^8.0.0",
- "p-map": "4.0.0",
+ "p-map": "4",
"p-queue": "^7.3.4",
"redis": "^4.6.6",
+ "sharp": "0.32.6",
"split-chunk-merge": "^1.0.0"
}
-}
\ No newline at end of file
+}
diff --git a/packages/server/services/files/routes/transcode/get.js b/packages/server/services/files/routes/transcode/get.js
new file mode 100644
index 00000000..b358bf41
--- /dev/null
+++ b/packages/server/services/files/routes/transcode/get.js
@@ -0,0 +1,88 @@
+import path from "node:path"
+import fs from "node:fs"
+import axios from "axios"
+
+import MultiqualityHLSJob from "@shared-classes/MultiqualityHLSJob"
+import { standardUpload } from "@services/remoteUpload"
+
+export default {
+ useContext: ["cache", "limits"],
+ middlewares: ["withAuthentication"],
+ fn: async (req, res) => {
+ const { url } = req.query
+
+ const userPath = path.join(this.default.contexts.cache.constructor.cachePath, req.auth.session.user_id)
+
+ const jobId = String(new Date().getTime())
+ const jobPath = path.resolve(userPath, "jobs", jobId)
+
+ const sourcePath = path.resolve(jobPath, `${jobId}.source`)
+
+ if (!fs.existsSync(jobPath)) {
+ fs.mkdirSync(jobPath, { recursive: true })
+ }
+
+ const sourceStream = fs.createWriteStream(sourcePath)
+
+ const response = await axios({
+ method: "get",
+ url,
+ responseType: "stream",
+ })
+
+ response.data.pipe(sourceStream)
+
+ await new Promise((resolve, reject) => {
+ sourceStream.on("finish", () => {
+ resolve()
+ })
+ sourceStream.on("error", (err) => {
+ reject(err)
+ })
+ })
+
+ const job = new MultiqualityHLSJob({
+ input: sourcePath,
+ outputDir: jobPath,
+ levels: [
+ {
+ original: true,
+ codec: "libx264",
+ bitrate: "10M",
+ preset: "ultrafast",
+ },
+ {
+ codec: "libx264",
+ width: 1280,
+ bitrate: "3M",
+ preset: "ultrafast",
+ }
+ ]
+ })
+
+ await new Promise((resolve, reject) => {
+ job
+ .on("error", (err) => {
+ console.error(`[TRANSMUX] Transmuxing failed`, err)
+ reject(err)
+ })
+ .on("end", () => {
+ console.debug(`[TRANSMUX] Finished transmuxing > ${sourcePath}`)
+ resolve()
+ })
+ .run()
+ })
+
+ const result = await standardUpload({
+ isDirectory: true,
+ source: path.join(jobPath, "hls"),
+ remotePath: `${req.auth.session.user_id}/jobs/${jobId}`,
+ })
+
+ fs.rmSync(jobPath, { recursive: true, force: true })
+
+ return {
+ result
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/server/services/files/routes/upload/chunk/post.js b/packages/server/services/files/routes/upload/chunk/post.js
index ff18b53d..d37bffca 100644
--- a/packages/server/services/files/routes/upload/chunk/post.js
+++ b/packages/server/services/files/routes/upload/chunk/post.js
@@ -57,6 +57,8 @@ export default {
source: build.filePath,
service: limits.useProvider,
useCompression: limits.useCompression,
+ transmux: req.headers["transmux"] ?? false,
+ transmuxOptions: req.headers["transmux-options"],
cachePath: tmpPath,
})
diff --git a/packages/server/services/files/services/remoteUpload/index.js b/packages/server/services/files/services/remoteUpload/index.js
index 23aa97c6..60cea929 100644
--- a/packages/server/services/files/services/remoteUpload/index.js
+++ b/packages/server/services/files/services/remoteUpload/index.js
@@ -4,59 +4,10 @@ import mimeTypes from "mime-types"
import getFileHash from "@shared-utils/readFileHash"
import PostProcess from "../post-process"
+import Transmux from "../transmux"
-export async function standardUpload({
- source,
- remotePath,
- metadata,
-}) {
- // upload to storage
- await global.storage.fPutObject(process.env.S3_BUCKET, remotePath, source, metadata)
-
- // compose url
- const url = storage.composeRemoteURL(remotePath)
-
- return {
- id: remotePath,
- url: url,
- metadata: metadata,
- }
-}
-
-export async function b2Upload({
- source,
- remotePath,
- metadata,
-}) {
- // use backblaze b2
- await b2Storage.authorize()
-
- const uploadUrl = await global.b2Storage.getUploadUrl({
- 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({
- uploadUrl: uploadUrl.data.uploadUrl,
- uploadAuthToken: uploadUrl.data.authorizationToken,
- fileName: remotePath,
- data: data,
- info: metadata
- })
-
- const url = `https://${process.env.B2_CDN_ENDPOINT}/${process.env.B2_BUCKET}/${remotePath}`
-
- return {
- id: remotePath,
- url: url,
- metadata: metadata,
- }
-}
+import StandardUpload from "./providers/standard"
+import B2Upload from "./providers/b2"
export default async ({
source,
@@ -64,6 +15,9 @@ export default async ({
service,
useCompression,
cachePath,
+ transmux,
+ transmuxOptions,
+ isDirectory,
}) => {
if (!source) {
throw new OperationError(500, "source is required")
@@ -77,9 +31,16 @@ export default async ({
parentDir = "/"
}
+ if (transmuxOptions) {
+ transmuxOptions = JSON.parse(transmuxOptions)
+ }
+
if (useCompression) {
try {
- const processOutput = await PostProcess({ filepath: source, cachePath })
+ const processOutput = await PostProcess({
+ filepath: source,
+ cachePath: cachePath,
+ })
if (processOutput) {
if (processOutput.filepath) {
@@ -92,6 +53,30 @@ export default async ({
}
}
+ if (transmux) {
+ try {
+ const processOutput = await Transmux({
+ transmuxer: transmux,
+ transmuxOptions: transmuxOptions,
+ filepath: source,
+ cachePath: cachePath
+ })
+
+ if (processOutput) {
+ if (processOutput.filepath) {
+ source = processOutput.filepath
+ }
+
+ if (processOutput.isDirectory) {
+ isDirectory = true
+ }
+ }
+ } catch (error) {
+ console.error(error)
+ throw new OperationError(500, `Failed to transmux file`)
+ }
+ }
+
const type = mimeTypes.lookup(path.basename(source))
const hash = await getFileHash(fs.createReadStream(source))
@@ -104,27 +89,38 @@ export default async ({
"File-Hash": hash,
}
- switch (service) {
- case "b2":
- if (!global.b2Storage) {
- throw new OperationError(500, "B2 storage not configured on environment, unsupported service. Please use `standard` service.")
- }
+ try {
+ switch (service) {
+ case "b2":
+ if (isDirectory) {
+ throw new OperationError(500, "B2 storage does not support directory upload. yet...")
+ }
+ if (!global.b2Storage) {
+ throw new OperationError(500, "B2 storage not configured on environment, unsupported service. Please use `standard` service.")
+ }
- result = await b2Upload({
- remotePath,
- source,
- metadata,
- })
- break
- case "standard":
- result = await standardUpload({
- remotePath,
- source,
- metadata,
- })
- break
- default:
- throw new OperationError(500, "Unsupported service")
+ result = await B2Upload({
+ source,
+ remotePath,
+ metadata,
+ isDirectory,
+ })
+ break
+ case "standard":
+ result = await StandardUpload({
+ source: isDirectory ? path.dirname(source) : source,
+ remotePath,
+ metadata,
+ isDirectory,
+ targetFilename: isDirectory ? path.basename(source) : null,
+ })
+ break
+ default:
+ throw new OperationError(500, "Unsupported service")
+ }
+ } catch (error) {
+ console.error(error)
+ throw new OperationError(500, "Failed to upload to storage")
}
return result
diff --git a/packages/server/services/files/services/remoteUpload/providers/b2/index.js b/packages/server/services/files/services/remoteUpload/providers/b2/index.js
new file mode 100644
index 00000000..cb3cfdfe
--- /dev/null
+++ b/packages/server/services/files/services/remoteUpload/providers/b2/index.js
@@ -0,0 +1,39 @@
+import fs from "node:fs"
+import path from "node:path"
+
+export default async ({
+ source,
+ remotePath,
+ metadata = {},
+ targetFilename,
+ isDirectory,
+}) => {
+ // use backblaze b2
+ await global.b2Storage.authorize()
+
+ const uploadUrl = await global.b2Storage.getUploadUrl({
+ 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({
+ uploadUrl: uploadUrl.data.uploadUrl,
+ uploadAuthToken: uploadUrl.data.authorizationToken,
+ fileName: remotePath,
+ data: data,
+ info: metadata
+ })
+
+ const url = `https://${process.env.B2_CDN_ENDPOINT}/${process.env.B2_BUCKET}/${remotePath}`
+
+ return {
+ id: remotePath,
+ url: url,
+ metadata: metadata,
+ }
+}
\ No newline at end of file
diff --git a/packages/server/services/files/services/remoteUpload/providers/standard/index.js b/packages/server/services/files/services/remoteUpload/providers/standard/index.js
new file mode 100644
index 00000000..a11db6ee
--- /dev/null
+++ b/packages/server/services/files/services/remoteUpload/providers/standard/index.js
@@ -0,0 +1,58 @@
+import fs from "node:fs"
+import path from "node:path"
+import pMap from "p-map"
+
+export default async function standardUpload({
+ source,
+ remotePath,
+ metadata = {},
+ targetFilename,
+ isDirectory,
+}) {
+ if (isDirectory) {
+ let files = await fs.promises.readdir(source)
+
+ files = files.map((file) => {
+ const filePath = path.join(source, file)
+
+ const isTargetDirectory = fs.lstatSync(filePath).isDirectory()
+
+ return {
+ source: filePath,
+ remotePath: path.join(remotePath, file),
+ isDirectory: isTargetDirectory,
+ }
+ })
+
+ await pMap(
+ files,
+ standardUpload,
+ {
+ concurrency: 3
+ }
+ )
+
+ return {
+ id: remotePath,
+ url: global.storage.composeRemoteURL(remotePath, targetFilename),
+ metadata: metadata,
+ }
+ }
+
+ // console.debug(`Uploading object to S3 Minio >`, {
+ // source: source,
+ // remote: remotePath,
+ // })
+
+ // upload to storage
+ await global.storage.fPutObject(process.env.S3_BUCKET, remotePath, source, metadata)
+
+ // compose url
+ const url = global.storage.composeRemoteURL(remotePath)
+
+ return {
+ id: remotePath,
+ url: url,
+ metadata: metadata,
+ }
+}
\ No newline at end of file
diff --git a/packages/server/services/files/services/transmux/index.js b/packages/server/services/files/services/transmux/index.js
new file mode 100644
index 00000000..7465851f
--- /dev/null
+++ b/packages/server/services/files/services/transmux/index.js
@@ -0,0 +1,89 @@
+import fs from "node:fs"
+import path from "node:path"
+
+import MultiqualityHLSJob from "@shared-classes/MultiqualityHLSJob"
+
+const transmuxers = [
+ {
+ id: "mq-hls",
+ container: "hls",
+ extension: "m3u8",
+ multipleOutput: true,
+ buildCommand: (input, outputDir) => {
+ return new MultiqualityHLSJob({
+ input: input,
+ outputDir: outputDir,
+ outputMasterName: "master.m3u8",
+ levels: [
+ {
+ original: true,
+ codec: "libx264",
+ bitrate: "10M",
+ preset: "ultrafast",
+ },
+ {
+ codec: "libx264",
+ width: 1280,
+ bitrate: "3M",
+ preset: "ultrafast",
+ }
+ ]
+ })
+ },
+ }
+]
+
+export default async (params) => {
+ if (!params) {
+ throw new Error("params is required")
+ }
+
+ if (!params.filepath) {
+ throw new Error("filepath is required")
+ }
+
+ if (!params.cachePath) {
+ throw new Error("cachePath is required")
+ }
+
+ if (!params.transmuxer) {
+ throw new Error("transmuxer is required")
+ }
+
+ if (!fs.existsSync(params.filepath)) {
+ throw new Error(`File ${params.filepath} not found`)
+ }
+
+ const transmuxer = transmuxers.find((item) => item.id === params.transmuxer)
+
+ if (!transmuxer) {
+ throw new Error(`Transmuxer ${params.transmuxer} not found`)
+ }
+
+ const jobPath = path.dirname(params.filepath)
+
+ if (!fs.existsSync(path.dirname(jobPath))) {
+ fs.mkdirSync(path.dirname(jobPath), { recursive: true })
+ }
+
+ return await new Promise((resolve, reject) => {
+ try {
+ const command = transmuxer.buildCommand(params.filepath, jobPath)
+
+ command
+ .on("progress", function (progress) {
+ console.log("Processing: " + progress.percent + "% done")
+ })
+ .on("error", (err) => {
+ reject(err)
+ })
+ .on("end", (data) => {
+ resolve(data)
+ })
+ .run()
+ } catch (error) {
+ console.error(`[TRANSMUX] Transmuxing failed`, error)
+ reject(error)
+ }
+ })
+}
\ No newline at end of file
diff --git a/packages/server/services/files/utils/downloadFFMPEG/index.js b/packages/server/services/files/utils/downloadFFMPEG/index.js
new file mode 100644
index 00000000..030a68d1
--- /dev/null
+++ b/packages/server/services/files/utils/downloadFFMPEG/index.js
@@ -0,0 +1,20 @@
+import fs from "node:fs"
+import os from "node:os"
+import axios from "axios"
+
+export default async (outputDir) => {
+ const arch = os.arch()
+
+ console.log(`Downloading ffmpeg for ${arch}...`)
+ const baseURL = `https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-${arch}-static.tar.xz`
+
+
+ const response = await axios.get(baseURL, {
+ responseType: "stream"
+ })
+
+ const ffmpegPath = path.join(outputDir, `ffmpeg-${arch}.tar.xz`)
+ const ffmpegFile = fs.createWriteStream(ffmpegPath)
+
+ response.data.pipe(ffmpegFile)
+}
\ No newline at end of file
diff --git a/packages/server/services/main/main.service.js b/packages/server/services/main/main.service.js
index 387c2f93..6ead7e87 100755
--- a/packages/server/services/main/main.service.js
+++ b/packages/server/services/main/main.service.js
@@ -1,4 +1,4 @@
-import { Server } from "linebridge/dist/server"
+import { Server } from "linebridge"
import DbManager from "@shared-classes/DbManager"
@@ -8,7 +8,7 @@ import SharedMiddlewares from "@shared-middlewares"
export default class API extends Server {
static refName = "main"
- static useEngine = "hyper-express"
+ static enableWebsockets = true
static routesPath = `${__dirname}/routes`
static listen_port = process.env.HTTP_LISTEN_PORT || 3000
diff --git a/packages/server/services/main/routes/nfc/tag/id/[id]/execute/get.js b/packages/server/services/main/routes/nfc/tag/id/[id]/execute/get.js
index f8f75b01..3dbcd54b 100644
--- a/packages/server/services/main/routes/nfc/tag/id/[id]/execute/get.js
+++ b/packages/server/services/main/routes/nfc/tag/id/[id]/execute/get.js
@@ -11,7 +11,18 @@ export default async (req, res) => {
})
}
+ if (tag.active === false) {
+ return res.status(404).json({
+ error: "Tag is not active"
+ })
+ }
+
+ console.log(tag)
+
switch (tag.behavior.type) {
+ case "badge": {
+ return res.redirect(`https://${tag.origin}/badge/${tag.user_id}`)
+ }
case "url": {
if (!tag.behavior.value.startsWith("https://")) {
tag.behavior.value = `https://${tag.behavior.value}`
@@ -19,9 +30,6 @@ export default async (req, res) => {
return res.redirect(tag.behavior.value)
}
- case "profile": {
- return new OperationError(501, `Not implemented.`)
- }
case "random_list": {
const values = result.behavior.value.split(";")
@@ -35,5 +43,11 @@ export default async (req, res) => {
return res.redirect(values[index])
}
+ case "default": {
+ return res.json({
+ error: "Cannot execute tag",
+ description: "Tag contains a command that cannot exist"
+ })
+ }
}
}
\ No newline at end of file
diff --git a/packages/server/services/main/routes/nfc/tag/register/[serial]/post.js b/packages/server/services/main/routes/nfc/tag/register/[serial]/post.js
index 4a99c0c3..565fa56b 100644
--- a/packages/server/services/main/routes/nfc/tag/register/[serial]/post.js
+++ b/packages/server/services/main/routes/nfc/tag/register/[serial]/post.js
@@ -6,6 +6,7 @@ const allowedUpdateFields = [
"active",
"behavior",
"icon",
+ "origin",
]
function buildEndpoint(id) {
@@ -29,6 +30,7 @@ export default {
alias: req.body.alias,
behavior: req.body.behavior,
active: req.body.active,
+ origin: req.body.origin,
})
tag.endpoint_url = buildEndpoint(tag._id.toString())
@@ -62,8 +64,6 @@ export default {
}
}
- console.log(tag)
-
return tag
}
}
\ No newline at end of file
diff --git a/packages/server/services/marketplace/marketplace.service.js b/packages/server/services/marketplace/marketplace.service.js
index a6396478..d73414b6 100755
--- a/packages/server/services/marketplace/marketplace.service.js
+++ b/packages/server/services/marketplace/marketplace.service.js
@@ -1,4 +1,4 @@
-import { Server } from "linebridge/dist/server"
+import { Server } from "linebridge"
import DbManager from "@shared-classes/DbManager"
@@ -6,7 +6,6 @@ import SharedMiddlewares from "@shared-middlewares"
class API extends Server {
static refName = "marketplace"
- static useEngine = "hyper-express"
static wsRoutesPath = `${__dirname}/ws_routes`
static routesPath = `${__dirname}/routes`
static listen_port = process.env.HTTP_LISTEN_PORT ?? 3005
diff --git a/packages/server/services/music/classes/track/methods/create.js b/packages/server/services/music/classes/track/methods/create.js
index 9bf99349..932b06ed 100644
--- a/packages/server/services/music/classes/track/methods/create.js
+++ b/packages/server/services/music/classes/track/methods/create.js
@@ -59,6 +59,7 @@ export default async (payload = {}) => {
artists: [],
source: payload.source,
metadata: metadata,
+ lyrics_enabled: payload.lyrics_enabled,
}
if (Array.isArray(payload.artists)) {
diff --git a/packages/server/services/music/controllers/featured/index.js b/packages/server/services/music/controllers/featured/index.js
deleted file mode 100755
index 1044cbb0..00000000
--- a/packages/server/services/music/controllers/featured/index.js
+++ /dev/null
@@ -1,21 +0,0 @@
-import path from "path"
-import createRoutesFromDirectory from "@utils/createRoutesFromDirectory"
-import getMiddlewares from "@utils/getMiddlewares"
-
-export default async (router) => {
- // create a file based router
- const routesPath = path.resolve(__dirname, "routes")
-
- const middlewares = await getMiddlewares(["withOptionalAuth"])
-
- for (const middleware of middlewares) {
- router.use(middleware)
- }
-
- router = createRoutesFromDirectory("routes", routesPath, router)
-
- return {
- path: "/featured",
- router,
- }
-}
\ No newline at end of file
diff --git a/packages/server/services/music/controllers/featured/routes/get/playlists.js b/packages/server/services/music/controllers/featured/routes/get/playlists.js
deleted file mode 100755
index 354ffa54..00000000
--- a/packages/server/services/music/controllers/featured/routes/get/playlists.js
+++ /dev/null
@@ -1,19 +0,0 @@
-import { FeaturedPlaylist } from "@db_models"
-
-export default async (req, res) => {
- const includeDisabled = req.query["include-disabled"] === "true"
-
- const query = {
- enabled: true
- }
-
- if (includeDisabled) {
- query.enabled = undefined
- }
-
- let playlists = await FeaturedPlaylist.find(query).catch((error) => {
- return []
- })
-
- return res.json(playlists)
-}
\ No newline at end of file
diff --git a/packages/server/services/music/controllers/lyrics/index.js b/packages/server/services/music/controllers/lyrics/index.js
deleted file mode 100755
index 19ed6e30..00000000
--- a/packages/server/services/music/controllers/lyrics/index.js
+++ /dev/null
@@ -1,21 +0,0 @@
-import path from "path"
-import createRoutesFromDirectory from "@utils/createRoutesFromDirectory"
-import getMiddlewares from "@utils/getMiddlewares"
-
-export default async (router) => {
- // create a file based router
- const routesPath = path.resolve(__dirname, "routes")
-
- const middlewares = await getMiddlewares(["withOptionalAuth"])
-
- for (const middleware of middlewares) {
- router.use(middleware)
- }
-
- router = createRoutesFromDirectory("routes", routesPath, router)
-
- return {
- path: "/lyrics",
- router,
- }
-}
\ No newline at end of file
diff --git a/packages/server/services/music/controllers/lyrics/routes/get/[track_id].js b/packages/server/services/music/controllers/lyrics/routes/get/[track_id].js
deleted file mode 100755
index 09ff199a..00000000
--- a/packages/server/services/music/controllers/lyrics/routes/get/[track_id].js
+++ /dev/null
@@ -1,56 +0,0 @@
-import { Track } from "@db_models"
-import getEnhancedLyricsFromTrack from "@services/getEnhancedLyricsFromTrack"
-
-export default async (req, res) => {
- let track = await Track.findOne({
- _id: req.params.track_id,
- }).catch((error) => {
- return null
- })
-
- if (!track) {
- return res.status(404).json({
- error: "Track not found",
- })
- }
-
- if (!track.lyricsEnabled) {
- return res.status(403).json({
- error: "Lyrics disabled for this track",
- })
- }
-
- const noCache = req.query["no-cache"] === "true"
-
- let data = null
-
- if (!noCache) {
- data = await global.redis.get(`lyrics:${track._id.toString()}`)
-
- if (data) {
- data = JSON.parse(data)
- }
- }
-
- try {
- if (!data) {
- data = await getEnhancedLyricsFromTrack(track, { req })
-
- await global.redis.set(`lyrics:${track._id.toString()}`, JSON.stringify(data), "EX", 60 * 60 * 24 * 30)
- }
-
- if (!data.lines) {
- return res.status(404).json({
- error: "Lyrics not found",
- })
- }
-
- return res.json(data)
- } catch (error) {
- console.error(error)
-
- return res.status(500).json({
- error: "Failed to generate lyrics",
- })
- }
-}
\ No newline at end of file
diff --git a/packages/server/services/music/controllers/playlists/index.js b/packages/server/services/music/controllers/playlists/index.js
deleted file mode 100755
index 5599f191..00000000
--- a/packages/server/services/music/controllers/playlists/index.js
+++ /dev/null
@@ -1,21 +0,0 @@
-import path from "path"
-import createRoutesFromDirectory from "@utils/createRoutesFromDirectory"
-import getMiddlewares from "@utils/getMiddlewares"
-
-export default async (router) => {
- // create a file based router
- const routesPath = path.resolve(__dirname, "routes")
-
- const middlewares = await getMiddlewares(["withOptionalAuth"])
-
- for (const middleware of middlewares) {
- router.use(middleware)
- }
-
- router = createRoutesFromDirectory("routes", routesPath, router)
-
- return {
- path: "/playlists",
- router,
- }
-}
\ No newline at end of file
diff --git a/packages/server/services/music/controllers/playlists/routes/delete/[playlist_id].js b/packages/server/services/music/controllers/playlists/routes/delete/[playlist_id].js
deleted file mode 100755
index ea61bbd9..00000000
--- a/packages/server/services/music/controllers/playlists/routes/delete/[playlist_id].js
+++ /dev/null
@@ -1,40 +0,0 @@
-import { Playlist, Track } from "@db_models"
-import { AuthorizationError, PermissionError, NotFoundError } from "@shared-classes/Errors"
-import RemoveTracks from "@services/removeTracks"
-
-export default async (req, res) => {
- if (!req.session) {
- return new AuthorizationError(req, res)
- }
-
- let removedTracksIds = []
-
- // const removeWithTracks = req.query.remove_with_tracks === "true"
-
- let playlist = await Playlist.findOne({
- _id: req.params.playlist_id,
- }).catch((err) => {
- return false
- })
-
- if (!playlist) {
- return new NotFoundError(req, res, "Playlist not found")
- }
-
- if (playlist.user_id !== req.session.user_id.toString()) {
- return new PermissionError(req, res, "You don't have permission to edit this playlist")
- }
-
- await Playlist.deleteOne({
- _id: req.params.playlist_id,
- })
-
- // if (removeWithTracks) {
- // removedTracksIds = await RemoveTracks(playlist.list)
- // }
-
- return res.json({
- success: true,
- removedTracksIds,
- })
-}
\ No newline at end of file
diff --git a/packages/server/services/music/controllers/playlists/routes/get/[playlist_id]/data.js b/packages/server/services/music/controllers/playlists/routes/get/[playlist_id]/data.js
deleted file mode 100755
index 9e205a5c..00000000
--- a/packages/server/services/music/controllers/playlists/routes/get/[playlist_id]/data.js
+++ /dev/null
@@ -1,65 +0,0 @@
-import { Playlist, Release, TrackLike, Track } from "@db_models"
-import { NotFoundError } from "@shared-classes/Errors"
-
-export default async (req, res) => {
- const { playlist_id } = req.params
- const { limit, offset } = req.query
-
- let playlist = await Playlist.findOne({
- _id: playlist_id,
- }).catch((err) => {
- return false
- })
-
- if (!playlist) {
- playlist = await Release.findOne({
- _id: playlist_id,
- }).catch((err) => {
- return false
- })
- }
-
- playlist = playlist.toObject()
-
- if (playlist.public === false) {
- if (req.session) {
- if (req.session.user_id !== playlist.user_id) {
- playlist = false
- }
- } else {
- playlist = false
- }
- }
-
- if (!playlist) {
- return new NotFoundError(req, res, "Playlist not found")
- }
-
- const orderedIds = playlist.list
-
- playlist.list = await Track.find({
- _id: [...playlist.list],
- public: true,
- })
-
- playlist.list = playlist.list.sort((a, b) => {
- return orderedIds.findIndex((id) => id === a._id.toString()) - orderedIds.findIndex((id) => id === b._id.toString())
- })
-
- if (req.session) {
- const likes = await TrackLike.find({
- user_id: req.session.user_id,
- track_id: [...playlist.list.map((track) => track._id.toString())],
- })
-
- playlist.list = playlist.list.map((track) => {
- track = track.toObject()
-
- track.liked = likes.findIndex((like) => like.track_id === track._id.toString()) !== -1
-
- return track
- })
- }
-
- return res.json(playlist)
-}
\ No newline at end of file
diff --git a/packages/server/services/music/controllers/playlists/routes/get/search.js b/packages/server/services/music/controllers/playlists/routes/get/search.js
deleted file mode 100755
index b8621aea..00000000
--- a/packages/server/services/music/controllers/playlists/routes/get/search.js
+++ /dev/null
@@ -1,44 +0,0 @@
-import { Playlist, Track } from "@db_models"
-
-export default async (req, res) => {
- const { keywords, limit = 5, offset = 0 } = req.query
-
- let results = {
- playlists: [],
- artists: [],
- albums: [],
- tracks: [],
- }
-
- let searchQuery = {
- public: true,
- }
-
- if (keywords) {
- searchQuery = {
- ...searchQuery,
- title: {
- $regex: keywords,
- $options: "i",
- },
- }
- }
-
- let playlists = await Playlist.find(searchQuery)
- .limit(limit)
- .skip(offset)
-
- if (playlists) {
- results.playlists = playlists
- }
-
- let tracks = await Track.find(searchQuery)
- .limit(limit)
- .skip(offset)
-
- if (tracks) {
- results.tracks = tracks
- }
-
- return res.json(results)
-}
\ No newline at end of file
diff --git a/packages/server/services/music/controllers/playlists/routes/get/self.js b/packages/server/services/music/controllers/playlists/routes/get/self.js
deleted file mode 100755
index 0af4af23..00000000
--- a/packages/server/services/music/controllers/playlists/routes/get/self.js
+++ /dev/null
@@ -1,68 +0,0 @@
-import { Playlist, Release, Track } from "@db_models"
-import { AuthorizationError, NotFoundError } from "@shared-classes/Errors"
-
-export default async (req, res) => {
- if (!req.session) {
- return new AuthorizationError(req, res)
- }
-
- const { keywords, limit = 10, offset = 0 } = req.query
-
- const user_id = req.session.user_id.toString()
-
- let searchQuery = {
- user_id,
- }
-
- if (keywords) {
- searchQuery = {
- ...searchQuery,
- title: {
- $regex: keywords,
- $options: "i",
- },
- }
- }
-
- const playlistsCount = await Playlist.countDocuments(searchQuery)
- const releasesCount = await Release.countDocuments(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 res.json({
- total_length: total_length,
- items: result,
- })
-}
diff --git a/packages/server/services/music/controllers/playlists/routes/post/new.js b/packages/server/services/music/controllers/playlists/routes/post/new.js
deleted file mode 100755
index f94da757..00000000
--- a/packages/server/services/music/controllers/playlists/routes/post/new.js
+++ /dev/null
@@ -1,47 +0,0 @@
-import { Playlist } from "@db_models"
-import { AuthorizationError } from "@shared-classes/Errors"
-
-export default async (req, res) => {
- if (!req.session) {
- return new AuthorizationError(req, res)
- }
-
- const userData = await global.comty.rest.user.data({
- user_id: req.session.user_id.toString(),
- })
- .catch((err) => {
- console.log("err", err)
- return false
- })
-
- if (!userData) {
- return new AuthorizationError(req, res)
- }
-
- let playlist = await Playlist.findOne({
- title: req.body.title,
- user_id: req.session.user_id.toString(),
- })
-
- if (playlist) {
- return res.status(400).json({
- message: "Playlist already exists",
- })
- }
-
- playlist = new Playlist({
- user_id: req.session.user_id.toString(),
- created_at: Date.now(),
- title: req.body.title ?? "Untitled",
- description: req.body.description,
- cover: req.body.cover,
- explicit: req.body.explicit,
- public: req.body.public,
- list: req.body.list ?? [],
- public: req.body.public,
- })
-
- await playlist.save()
-
- return res.json(playlist)
-}
\ No newline at end of file
diff --git a/packages/server/services/music/controllers/playlists/services/getTrackById.js b/packages/server/services/music/controllers/playlists/services/getTrackById.js
deleted file mode 100755
index 4962ac8f..00000000
--- a/packages/server/services/music/controllers/playlists/services/getTrackById.js
+++ /dev/null
@@ -1,19 +0,0 @@
-import { Track } from "@db_models"
-
-export default async (_id) => {
- if (!_id) {
- throw new Error("Missing _id")
- }
-
- let track = await Track.findById(_id).catch((err) => false)
-
- if (!track) {
- throw new Error("Track not found")
- }
-
- track = track.toObject()
-
- track.artist = track.artist ?? "Unknown artist"
-
- return track
-}
\ No newline at end of file
diff --git a/packages/server/services/music/controllers/releases/index.js b/packages/server/services/music/controllers/releases/index.js
deleted file mode 100755
index 1226bdf5..00000000
--- a/packages/server/services/music/controllers/releases/index.js
+++ /dev/null
@@ -1,21 +0,0 @@
-import path from "path"
-import createRoutesFromDirectory from "@utils/createRoutesFromDirectory"
-import getMiddlewares from "@utils/getMiddlewares"
-
-export default async (router) => {
- // create a file based router
- const routesPath = path.resolve(__dirname, "routes")
-
- const middlewares = await getMiddlewares(["withOptionalAuth"])
-
- for (const middleware of middlewares) {
- router.use(middleware)
- }
-
- router = createRoutesFromDirectory("routes", routesPath, router)
-
- return {
- path: "/releases",
- router,
- }
-}
\ No newline at end of file
diff --git a/packages/server/services/music/controllers/releases/routes/delete/[release_id].js b/packages/server/services/music/controllers/releases/routes/delete/[release_id].js
deleted file mode 100755
index dc90cfd7..00000000
--- a/packages/server/services/music/controllers/releases/routes/delete/[release_id].js
+++ /dev/null
@@ -1,40 +0,0 @@
-import { Release } from "@db_models"
-import { AuthorizationError, PermissionError, NotFoundError } from "@shared-classes/Errors"
-import RemoveTracks from "@services/removeTracks"
-
-export default async (req, res) => {
- if (!req.session) {
- return new AuthorizationError(req, res)
- }
-
- let removedTracksIds = []
-
- const removeWithTracks = req.query.remove_with_tracks === "true"
-
- let release = await Release.findOne({
- _id: req.params.release_id,
- }).catch((err) => {
- return false
- })
-
- if (!release) {
- return new NotFoundError(req, res, "Release not found")
- }
-
- if (release.user_id !== req.session.user_id.toString()) {
- return new PermissionError(req, res, "You don't have permission to edit this release")
- }
-
- await Release.deleteOne({
- _id: req.params.release_id,
- })
-
- if (removeWithTracks) {
- removedTracksIds = await RemoveTracks(release.list)
- }
-
- return res.json({
- success: true,
- removedTracksIds,
- })
-}
\ No newline at end of file
diff --git a/packages/server/services/music/controllers/releases/routes/get/[release_id]/data.js b/packages/server/services/music/controllers/releases/routes/get/[release_id]/data.js
deleted file mode 100755
index 3d942bcb..00000000
--- a/packages/server/services/music/controllers/releases/routes/get/[release_id]/data.js
+++ /dev/null
@@ -1,57 +0,0 @@
-import { Release, TrackLike, Track } from "@db_models"
-import { NotFoundError } from "@shared-classes/Errors"
-
-export default async (req, res) => {
- const { release_id } = req.params
- const { limit, offset } = req.query
-
- let release = await Release.findOne({
- _id: release_id,
- }).catch((err) => {
- return false
- })
-
- release = release.toObject()
-
- if (release.public === false) {
- if (req.session) {
- if (req.session.user_id !== release.user_id) {
- release = false
- }
- } else {
- release = false
- }
- }
-
- if (!release) {
- return new NotFoundError(req, res, "Release not found")
- }
-
- const orderedIds = release.list
-
- release.list = await Track.find({
- _id: [...release.list],
- public: true,
- })
-
- release.list = release.list.sort((a, b) => {
- return orderedIds.findIndex((id) => id === a._id.toString()) - orderedIds.findIndex((id) => id === b._id.toString())
- })
-
- if (req.session) {
- const likes = await TrackLike.find({
- user_id: req.session.user_id,
- track_id: [...release.list.map((track) => track._id.toString())],
- })
-
- release.list = release.list.map((track) => {
- track = track.toObject()
-
- track.liked = likes.findIndex((like) => like.track_id === track._id.toString()) !== -1
-
- return track
- })
- }
-
- return res.json(release)
-}
\ No newline at end of file
diff --git a/packages/server/services/music/controllers/releases/routes/get/self.js b/packages/server/services/music/controllers/releases/routes/get/self.js
deleted file mode 100755
index a566d69d..00000000
--- a/packages/server/services/music/controllers/releases/routes/get/self.js
+++ /dev/null
@@ -1,54 +0,0 @@
-import { Release, Track } from "@db_models"
-import { AuthorizationError, NotFoundError } from "@shared-classes/Errors"
-
-export default async (req, res) => {
- if (!req.session) {
- return new AuthorizationError(req, res)
- }
-
- const { keywords, limit = 10, offset = 0 } = req.query
-
- const user_id = req.session.user_id.toString()
-
- let searchQuery = {
- user_id,
- }
-
- if (keywords) {
- searchQuery = {
- ...searchQuery,
- title: {
- $regex: keywords,
- $options: "i",
- },
- }
- }
-
- const total_length = await Release.countDocuments(searchQuery)
-
- let releases = await Release.find(searchQuery)
- .sort({ created_at: -1 })
- .limit(limit)
- .skip(offset)
-
- if (!releases) {
- return new NotFoundError("Releases not found")
- }
-
- if (req.query.resolveItemsData === "true") {
- releases = await Promise.all(
- releases.map(async (release) => {
- release.list = await Track.find({
- _id: [...release.list],
- })
-
- return release
- }),
- )
- }
-
- return res.json({
- total_length: total_length,
- items: releases,
- })
-}
diff --git a/packages/server/services/music/controllers/releases/routes/get/user/[user_id].js b/packages/server/services/music/controllers/releases/routes/get/user/[user_id].js
deleted file mode 100755
index c7a64b12..00000000
--- a/packages/server/services/music/controllers/releases/routes/get/user/[user_id].js
+++ /dev/null
@@ -1,27 +0,0 @@
-import { Release } from "@db_models"
-
-export default async (req, res) => {
- if (!req.session) {
- return new AuthorizationError(req, res)
- }
-
- const { user_id } = req.params
- const { keywords, limit = 10, offset = 0 } = req.query
-
- const total_length = await Release.countDocuments({
- user_id,
- })
-
- let releases = await Release.find({
- user_id,
- public: true,
- })
- .limit(limit)
- .skip(offset)
- .sort({ created_at: -1 })
-
- return res.json({
- total_length,
- items: releases
- })
-}
\ No newline at end of file
diff --git a/packages/server/services/music/controllers/releases/routes/put/release.js b/packages/server/services/music/controllers/releases/routes/put/release.js
deleted file mode 100755
index 61647fd1..00000000
--- a/packages/server/services/music/controllers/releases/routes/put/release.js
+++ /dev/null
@@ -1,186 +0,0 @@
-import { Release, Track } from "@db_models"
-import { AuthorizationError, NotFoundError, PermissionError, BadRequestError } from "@shared-classes/Errors"
-import axios from "axios"
-
-const AllowedUpdateFields = [
- "title",
- "cover",
- "album",
- "artist",
- "type",
- "public",
-]
-
-const TrackAllowedUpdateFields = [
- "title",
- "album",
- "artist",
- "cover",
- "explicit",
- "metadata",
- "public",
- "spotifyId",
- "lyricsEnabled",
- "public",
-]
-
-async function fetchTrackSourceMetadata(track) {
- // get headers of source url (X-Amz-Meta-Duration)
- const response = await axios({
- method: "HEAD",
- url: track.source,
- }).catch((err) => {
- return {
- data: null,
- headers: null,
- }
- })
-
- if (response.headers) {
- return {
- duration: response.headers["x-amz-meta-duration"],
- size: response.headers["content-length"],
- bitrate: response.headers["x-amz-meta-bitrate"],
- }
- }
-
- return null
-}
-
-async function createOrUpdateTrack(payload) {
- if (!payload.title || !payload.source || !payload.publisher) {
- throw new Error("title and source and publisher are required")
- }
-
- let track = null
-
- if (payload._id) {
- track = await Track.findById(payload._id)
-
- if (!track) {
- throw new Error("track not found")
- }
-
- TrackAllowedUpdateFields.forEach((field) => {
- if (typeof payload[field] !== "undefined") {
- track[field] = payload[field]
- }
- })
-
- track = await Track.findByIdAndUpdate(payload._id, track)
-
- if (!track) {
- throw new Error("Failed to update track")
- }
- } else {
- track = new Track(payload)
-
- await track.save()
- }
-
- if (!track.metadata) {
- track.metadata = await fetchTrackSourceMetadata(track)
-
- await track.save()
- }
-
- return track
-}
-
-export default async (req, res) => {
- if (!req.session) {
- return new AuthorizationError(req, res)
- }
-
- if (!req.body.title || !req.body.list) {
- return new BadRequestError(req, res, "title and list are required")
- }
-
- if (!Array.isArray(req.body.list)) {
- return new BadRequestError(req, res, "list must be an array")
- }
-
- const userData = await global.comty.rest.user.data({
- user_id: req.session.user_id.toString(),
- })
- .catch((err) => {
- console.log("err", err)
- return false
- })
-
- if (!userData) {
- return new AuthorizationError(req, res)
- }
-
- let release = null
-
- if (!req.body._id) {
- release = new Release({
- user_id: req.session.user_id.toString(),
- created_at: Date.now(),
- title: req.body.title ?? "Untitled",
- cover: req.body.cover,
- explicit: req.body.explicit,
- type: req.body.type,
- public: req.body.public,
- list: req.body.list,
- public: req.body.public,
- })
-
- await release.save()
- } else {
- release = await Release.findById(req.body._id)
- }
-
- if (!release) {
- return new NotFoundError(req, res, "Release not found")
- }
-
- if (release.user_id !== req.session.user_id.toString()) {
- return new PermissionError(req, res, "You don't have permission to edit this release")
- }
-
- release = release.toObject()
-
- release.publisher = {
- user_id: req.session.user_id.toString(),
- fullName: userData.fullName,
- username: userData.username,
- avatar: userData.avatar,
- }
-
- release.list = await Promise.all(req.body.list.map(async (track, index) => {
- if (typeof track !== "object") {
- return track
- }
-
- track.publisher = {
- user_id: req.session.user_id.toString(),
- username: userData.username,
- avatar: userData.avatar,
- ...track.publisher ?? {},
- }
-
- const result = await createOrUpdateTrack(track)
-
- if (result) {
- return result._id.toString()
- }
- }))
-
- AllowedUpdateFields.forEach((field) => {
- if (typeof req.body[field] !== "undefined") {
- release[field] = req.body[field]
- }
- })
-
- release = await Release.findByIdAndUpdate(release._id.toString(), release)
-
- if (!release) {
- return new NotFoundError(req, res, "Release not updated")
- }
-
- global.eventBus.emit(`release.${release._id}.updated`, release)
-
- return res.json(release)
-}
\ No newline at end of file
diff --git a/packages/server/services/music/controllers/search/index.js b/packages/server/services/music/controllers/search/index.js
deleted file mode 100755
index 78f97c69..00000000
--- a/packages/server/services/music/controllers/search/index.js
+++ /dev/null
@@ -1,86 +0,0 @@
-import { Release, Playlist, Track } from "@db_models"
-import TidalAPI from "@shared-classes/TidalAPI"
-
-async function searchRoute(req, res) {
- try {
- const {
- keywords,
- limit = 5,
- offset = 0,
- useTidal = false
- } = req.query
-
- let results = {
- playlists: [],
- artists: [],
- tracks: [],
- album: [],
- ep: [],
- single: [],
- }
-
- let searchQuery = {
- public: true,
- }
-
- if (keywords) {
- searchQuery = {
- ...searchQuery,
- title: {
- $regex: keywords,
- $options: "i",
- },
- // TODO: Improve searching by album or artist
- }
- }
-
- let releases = await Release.find(searchQuery)
- .limit(limit)
- .skip(offset)
-
- if (releases && releases.length > 0) {
- releases.forEach((release) => {
- results[release.type].push(release)
- })
- }
-
- let playlists = await Playlist.find(searchQuery)
- .limit(limit)
- .skip(offset)
-
- if (playlists) {
- results.playlists = playlists
- }
-
- let tracks = await Track.find(searchQuery)
- .limit(limit)
- .skip(offset)
-
- if (tracks) {
- results.tracks = tracks
- }
-
- if (toBoolean(useTidal)) {
- const tidalResult = await TidalAPI.search({
- query: keywords
- })
-
- results.tracks = [...results.tracks, ...tidalResult]
- }
-
- return res.json(results)
- } catch (error) {
- return res.status(500).json({
- error: error.message,
- })
- }
-}
-
-export default (router) => {
- router.get("/", searchRoute)
-
- return {
- path: "/search",
- router,
- }
-}
\ No newline at end of file
diff --git a/packages/server/services/music/controllers/tracks/index.js b/packages/server/services/music/controllers/tracks/index.js
deleted file mode 100755
index bbbbbf47..00000000
--- a/packages/server/services/music/controllers/tracks/index.js
+++ /dev/null
@@ -1,21 +0,0 @@
-import path from "path"
-import createRoutesFromDirectory from "@utils/createRoutesFromDirectory"
-import getMiddlewares from "@utils/getMiddlewares"
-
-export default async (router) => {
- // create a file based router
- const routesPath = path.resolve(__dirname, "routes")
-
- const middlewares = await getMiddlewares(["withOptionalAuth"])
-
- for (const middleware of middlewares) {
- router.use(middleware)
- }
-
- router = createRoutesFromDirectory("routes", routesPath, router)
-
- return {
- path: "/tracks",
- router,
- }
-}
\ No newline at end of file
diff --git a/packages/server/services/music/controllers/tracks/routes/delete/[track_id]/like.js b/packages/server/services/music/controllers/tracks/routes/delete/[track_id]/like.js
deleted file mode 100755
index 8d8e6dc6..00000000
--- a/packages/server/services/music/controllers/tracks/routes/delete/[track_id]/like.js
+++ /dev/null
@@ -1,36 +0,0 @@
-import { TrackLike, Track } from "@db_models"
-import { AuthorizationError, NotFoundError } from "@shared-classes/Errors"
-
-export default async (req, res) => {
- if (!req.session) {
- return new AuthorizationError(req, res)
- }
-
- const { track_id } = req.params
-
- const track = await Track.findById(track_id).catch((err) => {
- return null
- })
-
- if (!track) {
- return new NotFoundError(req, res, "Track not found")
- }
-
- let like = await TrackLike.findOne({
- track_id: track_id,
- user_id: req.session.user_id,
- })
-
- await like.delete()
-
- global.ws.io.emit("music:self:track:toggle:like", {
- track_id: track_id,
- user_id: req.session.user_id,
- action: "unliked",
- })
-
- return res.status(200).json({
- message: "ok",
- action: "unliked",
- })
-}
\ No newline at end of file
diff --git a/packages/server/services/music/controllers/tracks/routes/get/[track_id]/data.js b/packages/server/services/music/controllers/tracks/routes/get/[track_id]/data.js
deleted file mode 100755
index 2351fe5a..00000000
--- a/packages/server/services/music/controllers/tracks/routes/get/[track_id]/data.js
+++ /dev/null
@@ -1,19 +0,0 @@
-import { Track } from "@db_models"
-import { NotFoundError } from "@shared-classes/Errors"
-
-export default async (req, res) => {
- const { track_id } = req.params
-
- let track = await Track.findOne({
- _id: track_id,
- public: true,
- }).catch((err) => {
- return null
- })
-
- if (!track) {
- return new NotFoundError(req, res, "Track not found")
- }
-
- return res.json(track)
-}
\ No newline at end of file
diff --git a/packages/server/services/music/controllers/tracks/routes/get/[track_id]/stream.js b/packages/server/services/music/controllers/tracks/routes/get/[track_id]/stream.js
deleted file mode 100755
index 2a1eaa7c..00000000
--- a/packages/server/services/music/controllers/tracks/routes/get/[track_id]/stream.js
+++ /dev/null
@@ -1,43 +0,0 @@
-import { Track } from "@db_models"
-import { NotFoundError, InternalServerError } from "@shared-classes/Errors"
-
-import mimetypes from "mime-types"
-
-export default async (req, res) => {
- const { track_id } = req.params
-
- let track = await Track.findOne({
- _id: track_id,
- public: true,
- }).catch((err) => {
- return null
- })
-
- if (!track) {
- return new NotFoundError(req, res, "Track not found")
- }
-
- track = track.toObject()
-
- if (typeof track.stream_source === "undefined") {
- return new NotFoundError(req, res, "Track doesn't have stream source")
- }
-
- global.storage.getObject(process.env.S3_BUCKET, `tracks/${track.stream_source}`, (err, dataStream) => {
- if (err) {
- console.error(err)
- return new InternalServerError(req, res, "Error while getting file from storage")
- }
-
- const extname = mimetypes.lookup(track.stream_source)
-
- // send chunked response
- res.status(200)
-
- // set headers
- res.setHeader("Content-Type", extname)
- res.setHeader("Accept-Ranges", "bytes")
-
- return dataStream.pipe(res)
- })
-}
\ No newline at end of file
diff --git a/packages/server/services/music/controllers/tracks/routes/get/liked.js b/packages/server/services/music/controllers/tracks/routes/get/liked.js
deleted file mode 100755
index 6413e760..00000000
--- a/packages/server/services/music/controllers/tracks/routes/get/liked.js
+++ /dev/null
@@ -1,60 +0,0 @@
-import { Track, TrackLike } from "@db_models"
-import { AuthorizationError } from "@shared-classes/Errors"
-
-// TODO: Fetch from external linked services (like tidal, spotify, ...)
-export default async (req, res) => {
- if (!req.session) {
- return new AuthorizationError(req, res)
- }
-
- const { limit = 100, offset = 0 } = req.query
-
- let totalLikedTracks = await TrackLike.countDocuments({
- user_id: req.session.user_id,
- })
-
- let likedTracks = await TrackLike.find({
- user_id: req.session.user_id,
- })
- .limit(Number(limit))
- .skip(Number(offset))
- .sort({ created_at: -1 })
-
- const likedTracksIds = likedTracks.map((item) => {
- return item.track_id
- })
-
- let tracks = await Track.find({
- _id: likedTracksIds,
- //public: true,
- })
- .catch((err) => {
- return []
- })
-
- tracks = tracks.map((item) => {
- item = item.toObject()
-
- const likeIndex = likedTracksIds.indexOf(item._id.toString())
-
- if (likeIndex !== -1) {
- item.liked_at = new Date(likedTracks[likeIndex].created_at).getTime()
- }
-
- item.liked = true
-
- return item
- })
-
- tracks.sort((a, b) => {
- const indexA = likedTracksIds.indexOf(a._id.toString())
- const indexB = likedTracksIds.indexOf(b._id.toString())
-
- return indexA - indexB
- })
-
- return res.json({
- total_length: totalLikedTracks,
- tracks,
- })
-}
\ No newline at end of file
diff --git a/packages/server/services/music/controllers/tracks/routes/get/many.js b/packages/server/services/music/controllers/tracks/routes/get/many.js
deleted file mode 100755
index 677af431..00000000
--- a/packages/server/services/music/controllers/tracks/routes/get/many.js
+++ /dev/null
@@ -1,23 +0,0 @@
-import { Track } from "@db_models"
-
-export default async (req, res) => {
- const { ids, limit = 20, offset = 0 } = req.query
-
- if (!ids) {
- return res.status(400).json({
- message: "IDs is required",
- })
- }
-
- let tracks = await Track.find({
- _id: [...ids],
- public: true,
- })
- .limit(limit)
- .skip(offset)
- .catch((err) => {
- return []
- })
-
- return res.json(tracks)
-}
\ No newline at end of file
diff --git a/packages/server/services/music/controllers/tracks/routes/post/[track_id]/like.js b/packages/server/services/music/controllers/tracks/routes/post/[track_id]/like.js
deleted file mode 100755
index 89266099..00000000
--- a/packages/server/services/music/controllers/tracks/routes/post/[track_id]/like.js
+++ /dev/null
@@ -1,42 +0,0 @@
-import { TrackLike, Track } from "@db_models"
-import { AuthorizationError, NotFoundError } from "@shared-classes/Errors"
-
-export default async (req, res) => {
- if (!req.session) {
- return new AuthorizationError(req, res)
- }
-
- const { track_id } = req.params
-
- const track = await Track.findById(track_id).catch((err) => {
- return null
- })
-
- if (!track) {
- return new NotFoundError(req, res, "Track not found")
- }
-
- let like = await TrackLike.findOne({
- track_id: track_id,
- user_id: req.session.user_id,
- })
-
- like = new TrackLike({
- track_id: track_id,
- user_id: req.session.user_id,
- created_at: new Date().getTime(),
- })
-
- await like.save()
-
- global.ws.io.emit("music:self:track:toggle:like", {
- track_id: track_id,
- user_id: req.session.user_id,
- action: "liked" ,
- })
-
- return res.status(200).json({
- message: "ok",
- action: "liked",
- })
-}
\ No newline at end of file
diff --git a/packages/server/services/music/controllers/tracks/routes/post/[track_id]/refresh-cache.js b/packages/server/services/music/controllers/tracks/routes/post/[track_id]/refresh-cache.js
deleted file mode 100755
index 43bf4c4c..00000000
--- a/packages/server/services/music/controllers/tracks/routes/post/[track_id]/refresh-cache.js
+++ /dev/null
@@ -1,36 +0,0 @@
-import { Track } from "@db_models"
-import { NotFoundError, AuthorizationError } from "@shared-classes/Errors"
-import getEnhancedLyricsFromTrack from "@services/getEnhancedLyricsFromTrack"
-
-export default async (req, res) => {
- if (!req.session) {
- return new AuthorizationError(req, res)
- }
-
- const { track_id } = req.params
-
- let track = await Track.findOne({
- _id: track_id,
- public: true,
- }).catch((err) => {
- return null
- })
-
- if (!track) {
- return new NotFoundError(req, res, "Track not found")
- }
-
- if (track.lyricsEnabled) {
- const enhancedLyrics = await getEnhancedLyricsFromTrack(track, { req }).catch((err) => {
- return false
- })
-
- if (enhancedLyrics) {
- await global.redis.set(`lyrics:${track._id.toString()}`, JSON.stringify(enhancedLyrics), "EX", 60 * 60 * 24 * 30)
- }
- }
-
- return res.json({
- success: true,
- })
-}
\ 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 33b70587..03e7afa0 100755
--- a/packages/server/services/music/music.service.js
+++ b/packages/server/services/music/music.service.js
@@ -1,4 +1,4 @@
-import { Server } from "linebridge/dist/server"
+import { Server } from "linebridge"
import DbManager from "@shared-classes/DbManager"
@@ -7,7 +7,6 @@ import LimitsClass from "@shared-classes/Limits"
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
diff --git a/packages/server/services/music/routes/music/lyrics/[track_id]/get.js b/packages/server/services/music/routes/music/lyrics/[track_id]/get.js
index cdf9b040..241af038 100644
--- a/packages/server/services/music/routes/music/lyrics/[track_id]/get.js
+++ b/packages/server/services/music/routes/music/lyrics/[track_id]/get.js
@@ -7,83 +7,70 @@ function parseTimeToMs(timeStr) {
return Number(minutes) * 60 * 1000 + Number(seconds) * 1000 + Number(milliseconds)
}
+async function remoteLcrToSyncedLyrics(lrcUrl) {
+ const { data } = await axios.get(lrcUrl)
+
+ let syncedLyrics = data
+
+ syncedLyrics = syncedLyrics.split("\n")
+
+ syncedLyrics = syncedLyrics.map((line) => {
+ const syncedLine = {}
+
+ //syncedLine.time = line.match(/\[.*\]/)[0]
+ syncedLine.time = line.split(" ")[0]
+ syncedLine.text = line.replace(syncedLine.time, "").trim()
+
+ if (syncedLine.text === "") {
+ delete syncedLine.text
+ syncedLine.break = true
+ }
+
+ syncedLine.time = syncedLine.time.replace(/\[|\]/g, "")
+ syncedLine.time = syncedLine.time.replace(".", ":")
+
+ return syncedLine
+ })
+
+ syncedLyrics = syncedLyrics.map((syncedLine, index) => {
+ const nextLine = syncedLyrics[index + 1]
+
+ syncedLine.startTimeMs = parseTimeToMs(syncedLine.time)
+ syncedLine.endTimeMs = nextLine ? parseTimeToMs(nextLine.time) : parseTimeToMs(syncedLyrics[syncedLyrics.length - 1].time)
+
+ return syncedLine
+ })
+
+ return syncedLyrics
+}
+
export default async (req) => {
const { track_id } = req.params
let { translate_lang = "original" } = req.query
- let trackLyric = await TrackLyric.findOne({
+ let trackLyrics = await TrackLyric.findOne({
track_id
})
- if (!trackLyric) {
+ if (!trackLyrics) {
throw new OperationError(404, "Track lyric not found")
}
- trackLyric = trackLyric.toObject()
+ trackLyrics = trackLyrics.toObject()
- if (typeof trackLyric.lrc === "object") {
- trackLyric.available_langs = Object.entries(trackLyric.lrc).map(([key, value]) => {
- return {
- id: key,
- name: key,
- value: value
- }
- })
+ if (typeof trackLyrics.lrc === "object") {
+ trackLyrics.translated_lang = translate_lang
- if (typeof trackLyric.lrc[translate_lang] === "undefined") {
- translate_lang = "original"
+ if (trackLyrics.lrc[translate_lang]) {
+ trackLyrics.synced_lyrics = await remoteLcrToSyncedLyrics(trackLyrics.lrc[translate_lang])
}
- trackLyric.lang = translate_lang
-
- trackLyric.lrc = trackLyric.lrc[translate_lang]
-
- const { data } = await axios.get(trackLyric.lrc).catch((err) => {
- return false
- })
-
- trackLyric.lrc = data
- }else {
- trackLyric.available_langs = [{
- id: "original",
- name: "Original",
- value: null
- }]
+ trackLyrics.available_langs = Object.keys(trackLyrics.lrc)
}
- if (trackLyric.lrc) {
- trackLyric.lrc = trackLyric.lrc.split("\n")
- trackLyric.lrc = trackLyric.lrc.map((line) => {
- const syncedLine = {}
-
- //syncedLine.time = line.match(/\[.*\]/)[0]
- syncedLine.time = line.split(" ")[0]
- syncedLine.text = line.replace(syncedLine.time, "").trim()
-
- if (syncedLine.text === "") {
- delete syncedLine.text
- syncedLine.break = true
- }
-
- syncedLine.time = syncedLine.time.replace(/\[|\]/g, "")
- syncedLine.time = syncedLine.time.replace(".", ":")
-
- return syncedLine
- })
-
- trackLyric.lrc = trackLyric.lrc.map((syncedLine, index) => {
- const nextLine = trackLyric.lrc[index + 1]
-
- syncedLine.startTimeMs = parseTimeToMs(syncedLine.time)
- syncedLine.endTimeMs = nextLine ? parseTimeToMs(nextLine.time) : parseTimeToMs(trackLyric.lrc[trackLyric.lrc.length - 1].time)
-
- return syncedLine
- })
+ if (trackLyrics.sync_audio_at) {
+ trackLyrics.sync_audio_at_ms = parseTimeToMs(trackLyrics.sync_audio_at)
}
- if (trackLyric.sync_audio_at) {
- trackLyric.sync_audio_at_ms = parseTimeToMs(trackLyric.sync_audio_at)
- }
-
- return trackLyric
+ return trackLyrics
}
\ No newline at end of file
diff --git a/packages/server/services/music/routes/music/lyrics/[track_id]/put.js b/packages/server/services/music/routes/music/lyrics/[track_id]/put.js
new file mode 100644
index 00000000..58298384
--- /dev/null
+++ b/packages/server/services/music/routes/music/lyrics/[track_id]/put.js
@@ -0,0 +1,57 @@
+import { TrackLyric, Track } from "@db_models"
+
+export default {
+ middlewares: ["withAuthentication"],
+ fn: async (req) => {
+ const { track_id } = req.params
+ const { video_source, lrc, sync_audio_at } = req.body
+
+ let track = await Track.findOne({
+ _id: track_id,
+ })
+
+ if (!track) {
+ throw new OperationError(404, "Track not found")
+ }
+
+ if (track.publisher.user_id !== req.auth.session.user_id) {
+ throw new OperationError(403, "Unauthorized")
+ }
+
+ let trackLyric = await TrackLyric.findOne({
+ track_id: track_id
+ }).lean()
+
+ if (trackLyric) {
+ if (video_source) {
+ trackLyric.video_source = video_source
+ }
+
+ if (lrc) {
+ trackLyric.lrc = lrc
+ }
+
+ if (sync_audio_at) {
+ trackLyric.sync_audio_at = sync_audio_at
+ }
+
+ trackLyric = await TrackLyric.findOneAndUpdate(
+ {
+ _id: trackLyric._id
+ },
+ trackLyric
+ )
+ } else {
+ trackLyric = new TrackLyric({
+ track_id: track_id,
+ video_source: video_source,
+ lrc: lrc,
+ sync_audio_at: sync_audio_at,
+ })
+
+ await trackLyric.save()
+ }
+
+ return trackLyric
+ }
+}
\ No newline at end of file
diff --git a/packages/server/services/notifications/notifications.service.js b/packages/server/services/notifications/notifications.service.js
index f62aeb40..42dbb10e 100644
--- a/packages/server/services/notifications/notifications.service.js
+++ b/packages/server/services/notifications/notifications.service.js
@@ -1,4 +1,4 @@
-import { Server } from "linebridge/dist/server"
+import { Server } from "linebridge"
import DbManager from "@shared-classes/DbManager"
import RedisClient from "@shared-classes/RedisClient"
@@ -7,7 +7,7 @@ import SharedMiddlewares from "@shared-middlewares"
class API extends Server {
static refName = "notifications"
- static useEngine = "hyper-express"
+ static enableWebsockets = true
static wsRoutesPath = `${__dirname}/ws_routes`
static routesPath = `${__dirname}/routes`
static listen_port = process.env.HTTP_LISTEN_PORT ?? 3009
diff --git a/packages/server/services/posts/classes/posts/index.js b/packages/server/services/posts/classes/posts/index.js
index 6b5946e2..1acfa5a9 100644
--- a/packages/server/services/posts/classes/posts/index.js
+++ b/packages/server/services/posts/classes/posts/index.js
@@ -14,4 +14,6 @@ export default class Posts {
static delete = require("./methods/delete").default
static update = require("./methods/update").default
static replies = require("./methods/replies").default
+ static votePoll = require("./methods/votePoll").default
+ static deleteVotePoll = require("./methods/deletePollVote").default
}
\ No newline at end of file
diff --git a/packages/server/services/posts/classes/posts/methods/create.js b/packages/server/services/posts/classes/posts/methods/create.js
index 373bea3b..1337c3bd 100644
--- a/packages/server/services/posts/classes/posts/methods/create.js
+++ b/packages/server/services/posts/classes/posts/methods/create.js
@@ -7,7 +7,7 @@ import fullfill from "./fullfill"
export default async (payload = {}) => {
await requiredFields(["user_id"], payload)
- let { user_id, message, attachments, timestamp, reply_to } = payload
+ let { user_id, message, attachments, timestamp, reply_to, poll_options } = payload
// check if is a Array and have at least one element
const isAttachmentsValid = Array.isArray(attachments) && attachments.length > 0
@@ -20,6 +20,16 @@ export default async (payload = {}) => {
timestamp = DateTime.local().toISO()
}
+ if (Array.isArray(poll_options)) {
+ poll_options = poll_options.map((option) => {
+ if (!option.id) {
+ option.id = nanoid()
+ }
+
+ return option
+ })
+ }
+
let post = new Post({
created_at: timestamp,
user_id: typeof user_id === "object" ? user_id.toString() : user_id,
@@ -27,6 +37,7 @@ export default async (payload = {}) => {
attachments: attachments ?? [],
reply_to: reply_to,
flags: [],
+ poll_options: poll_options,
})
await post.save()
diff --git a/packages/server/services/posts/classes/posts/methods/deletePollVote.js b/packages/server/services/posts/classes/posts/methods/deletePollVote.js
new file mode 100644
index 00000000..05125a2c
--- /dev/null
+++ b/packages/server/services/posts/classes/posts/methods/deletePollVote.js
@@ -0,0 +1,33 @@
+import { VotePoll } from "@db_models"
+
+export default async (payload = {}) => {
+ if (!payload.user_id) {
+ throw new OperationError(400, "Missing user_id")
+ }
+
+ if (!payload.post_id) {
+ throw new OperationError(400, "Missing post_id")
+ }
+
+ if (!payload.option_id) {
+ throw new OperationError(400, "Missing option_id")
+ }
+
+ let vote = await VotePoll.find({
+ user_id: payload.user_id,
+ post_id: payload.post_id,
+ option_id: payload.option_id,
+ })
+
+ if (!vote) {
+ throw new OperationError(404, "Poll vote not found")
+ }
+
+ await VotePoll.deleteOne({
+ _id: vote._id
+ })
+
+ global.websocket.io.of("/").emit(`post.poll.vote.deleted`, vote)
+
+ return vote
+}
\ No newline at end of file
diff --git a/packages/server/services/posts/classes/posts/methods/fullfill.js b/packages/server/services/posts/classes/posts/methods/fullfill.js
index a75532ac..b84f593e 100644
--- a/packages/server/services/posts/classes/posts/methods/fullfill.js
+++ b/packages/server/services/posts/classes/posts/methods/fullfill.js
@@ -1,4 +1,4 @@
-import { User, PostLike, PostSave, Post } from "@db_models"
+import { User, PostLike, PostSave, Post, VotePoll } from "@db_models"
export default async (payload = {}) => {
let {
@@ -23,17 +23,25 @@ export default async (payload = {}) => {
postsSavesIds = postsSaves.map((postSave) => postSave.post_id)
}
- let [usersData, likesData] = await Promise.all([
+ const postsIds = posts.map((post) => post._id)
+ const usersIds = posts.map((post) => post.user_id)
+
+ let [usersData, likesData, pollVotes] = await Promise.all([
User.find({
_id: {
- $in: posts.map((post) => post.user_id)
+ $in: usersIds
}
}).catch(() => { }),
PostLike.find({
post_id: {
- $in: posts.map((post) => post._id)
+ $in: postsIds
}
}).catch(() => []),
+ VotePoll.find({
+ post_id: {
+ $in: postsIds
+ }
+ }).catch(() => [])
])
// wrap likesData by post_id
@@ -79,9 +87,43 @@ export default async (payload = {}) => {
post.countLikes = likes.length
+ const postPollVotes = pollVotes.filter((vote) => {
+ if (vote.post_id !== post._id.toString()) {
+ return false
+ }
+
+ return true
+ })
+
if (for_user_id) {
post.isLiked = likes.some((like) => like.user_id.toString() === for_user_id)
post.isSaved = postsSavesIds.includes(post._id.toString())
+
+ if (Array.isArray(post.poll_options)) {
+ post.poll_options = post.poll_options.map((option) => {
+ option.voted = !!postPollVotes.find((vote) => {
+ if (vote.user_id !== for_user_id) {
+ return false
+ }
+
+ if (vote.option_id !== option.id) {
+ return false
+ }
+
+ return true
+ })
+
+ option.count = postPollVotes.filter((vote) => {
+ if (vote.option_id !== option.id) {
+ return false
+ }
+
+ return true
+ }).length
+
+ return option
+ })
+ }
}
return {
diff --git a/packages/server/services/posts/classes/posts/methods/update.js b/packages/server/services/posts/classes/posts/methods/update.js
index 8e40a883..f80b1fac 100644
--- a/packages/server/services/posts/classes/posts/methods/update.js
+++ b/packages/server/services/posts/classes/posts/methods/update.js
@@ -17,6 +17,16 @@ export default async (post_id, update) => {
post.updated_at = DateTime.local().toISO()
+ if (Array.isArray(update.poll_options)) {
+ post.poll_options = update.poll_options.map((option) => {
+ if (!option.id) {
+ option.id = nanoid()
+ }
+
+ return option
+ })
+ }
+
await post.save()
post = post.toObject()
diff --git a/packages/server/services/posts/classes/posts/methods/votePoll.js b/packages/server/services/posts/classes/posts/methods/votePoll.js
new file mode 100644
index 00000000..181e0fd6
--- /dev/null
+++ b/packages/server/services/posts/classes/posts/methods/votePoll.js
@@ -0,0 +1,46 @@
+import { VotePoll } from "@db_models"
+
+export default async (payload = {}) => {
+ if (!payload.user_id) {
+ throw new OperationError(400, "Missing user_id")
+ }
+
+ if (!payload.post_id) {
+ throw new OperationError(400, "Missing post_id")
+ }
+
+ if (!payload.option_id) {
+ throw new OperationError(400, "Missing option_id")
+ }
+
+ let vote = await VotePoll.findOne({
+ user_id: payload.user_id,
+ post_id: payload.post_id,
+ })
+
+ let previousOptionId = null
+
+ if (vote) {
+ previousOptionId = vote.option_id
+
+ await VotePoll.deleteOne({
+ _id: vote._id.toString()
+ })
+ }
+
+ vote = new VotePoll({
+ user_id: payload.user_id,
+ post_id: payload.post_id,
+ option_id: payload.option_id,
+ })
+
+ await vote.save()
+
+ vote = vote.toObject()
+
+ vote.previous_option_id = previousOptionId
+
+ global.websocket.io.of("/").emit(`post.poll.vote`, vote)
+
+ return vote
+}
\ No newline at end of file
diff --git a/packages/server/services/posts/posts.service.js b/packages/server/services/posts/posts.service.js
index 7db00306..9291e4e4 100644
--- a/packages/server/services/posts/posts.service.js
+++ b/packages/server/services/posts/posts.service.js
@@ -1,4 +1,4 @@
-import { Server } from "linebridge/dist/server"
+import { Server } from "linebridge"
import DbManager from "@shared-classes/DbManager"
import RedisClient from "@shared-classes/RedisClient"
@@ -7,7 +7,7 @@ import SharedMiddlewares from "@shared-middlewares"
export default class API extends Server {
static refName = "posts"
- static useEngine = "hyper-express"
+ static enableWebsockets = true
static routesPath = `${__dirname}/routes`
static listen_port = process.env.HTTP_LISTEN_PORT ?? 3001
diff --git a/packages/server/services/posts/routes/posts/[post_id]/update/put.js b/packages/server/services/posts/routes/posts/[post_id]/update/put.js
index 8f2007e8..d48c340d 100644
--- a/packages/server/services/posts/routes/posts/[post_id]/update/put.js
+++ b/packages/server/services/posts/routes/posts/[post_id]/update/put.js
@@ -1,7 +1,7 @@
import PostClass from "@classes/posts"
import { Post } from "@db_models"
-const AllowedFields = ["message", "tags", "attachments"]
+const AllowedFields = ["message", "tags", "attachments", "poll_options"]
// TODO: Get limits from LimitsAPI
const MaxStringsLengths = {
diff --git a/packages/server/services/posts/routes/posts/[post_id]/vote_poll/[option_id]/delete.js b/packages/server/services/posts/routes/posts/[post_id]/vote_poll/[option_id]/delete.js
new file mode 100644
index 00000000..0f70d3a6
--- /dev/null
+++ b/packages/server/services/posts/routes/posts/[post_id]/vote_poll/[option_id]/delete.js
@@ -0,0 +1,14 @@
+import Posts from "@classes/posts"
+
+export default {
+ middlewares: ["withAuthentication"],
+ fn: async (req) => {
+ const result = await Posts.deleteVotePoll({
+ user_id: req.auth.session.user_id,
+ post_id: req.params.post_id,
+ option_id: req.params.option_id,
+ })
+
+ return result
+ }
+}
\ No newline at end of file
diff --git a/packages/server/services/posts/routes/posts/[post_id]/vote_poll/[option_id]/post.js b/packages/server/services/posts/routes/posts/[post_id]/vote_poll/[option_id]/post.js
new file mode 100644
index 00000000..f4fe61ac
--- /dev/null
+++ b/packages/server/services/posts/routes/posts/[post_id]/vote_poll/[option_id]/post.js
@@ -0,0 +1,14 @@
+import Posts from "@classes/posts"
+
+export default {
+ middlewares: ["withAuthentication"],
+ fn: async (req) => {
+ const result = await Posts.votePoll({
+ user_id: req.auth.session.user_id,
+ post_id: req.params.post_id,
+ option_id: req.params.option_id,
+ })
+
+ return result
+ }
+}
\ No newline at end of file
diff --git a/packages/server/services/users/classes/users/method/update.js b/packages/server/services/users/classes/users/method/update.js
index e5277bab..370cb48e 100644
--- a/packages/server/services/users/classes/users/method/update.js
+++ b/packages/server/services/users/classes/users/method/update.js
@@ -25,7 +25,7 @@ export default async (user_id, update) => {
user = user.toObject()
- global.websocket.io.of("/").emit(`user.update.${update}`, user)
+ //global.websocket.io.of("/").emit(`user.update.${update}`, user)
return user
}
\ No newline at end of file
diff --git a/packages/server/services/users/routes/users/self/update/post.js b/packages/server/services/users/routes/users/self/update/post.js
index a8e50587..4188455a 100644
--- a/packages/server/services/users/routes/users/self/update/post.js
+++ b/packages/server/services/users/routes/users/self/update/post.js
@@ -21,15 +21,7 @@ const MaxStringsLengths = {
export default {
middlewares: ["withAuthentication"],
fn: async (req) => {
- let update = {}
-
- if (!update) {
- throw new OperationError(400, "Missing update")
- }
-
- if (typeof update === "string") {
- update = JSON.parse(update)
- }
+ const update = {}
// sanitize update
AllowedPublicUpdateFields.forEach((key) => {
diff --git a/packages/server/services/users/users.service.js b/packages/server/services/users/users.service.js
index 851448de..38acde4f 100644
--- a/packages/server/services/users/users.service.js
+++ b/packages/server/services/users/users.service.js
@@ -1,4 +1,4 @@
-import { Server } from "linebridge/dist/server"
+import { Server } from "linebridge"
import DbManager from "@shared-classes/DbManager"
import RedisClient from "@shared-classes/RedisClient"