merge from local

This commit is contained in:
SrGooglo 2024-11-04 13:02:37 +00:00
parent 75f1dddf48
commit dfcafc6b18
29 changed files with 843 additions and 579 deletions

View File

@ -336,6 +336,10 @@ class ComtyApp extends React.Component {
await this.flushState()
},
"auth:disabled_account": async () => {
await SessionModel.removeToken()
app.navigation.goAuth()
}
}
flushState = async () => {

View File

@ -240,8 +240,8 @@ const PlaylistView = (props) => {
playlistType,
)}
>
<div className="play_info_wrapper">
{
!props.noHeader && <div className="play_info_wrapper">
<div className="play_info">
<div className="play_info_cover">
<ImageViewer src={playlist.cover ?? playlist?.thumbnail ?? "/assets/no_song.png"} />
@ -298,12 +298,6 @@ const PlaylistView = (props) => {
Play
</antd.Button>
{
!props.favorite && <antd.Button
icon={<Icons.MdFavorite />}
/>
}
{
playlist.description && <antd.Button
icon={<Icons.MdInfo />}
@ -331,6 +325,7 @@ const PlaylistView = (props) => {
</div>
</div>
</div>
}
<div className="list">
{

View File

@ -159,7 +159,9 @@ export default React.memo((props) => {
props.attachments?.length > 0 && <BearCarousel
data={props.attachments.map((attachment, index) => {
if (typeof attachment !== "object") {
return null
attachment = {
url: attachment,
}
}
return {

View File

@ -0,0 +1,45 @@
import React from "react"
import { Skeleton } from "antd"
import "./index.less"
const SponsorsList = () => {
const fetchAPI = "https://raw.githubusercontent.com/ragestudio/comty/refs/heads/master/sponsors.json"
const [loading, setLoading] = React.useState(true)
const [sponsors, setSponsors] = React.useState(null)
React.useEffect(() => {
fetch(fetchAPI)
.then((response) => response.json())
.then((data) => {
setLoading(false)
setSponsors(data)
})
}, [])
if (loading) {
return <Skeleton active />
}
return <div className="sponsors_list">
{
sponsors && sponsors.map((sponsor, index) => {
return <a
key={index}
href={sponsor.url}
target="_blank"
rel="noreferrer"
className="sponsor"
>
<img
src={sponsor.promo_badge}
alt={sponsor.name}
/>
</a>
})
}
</div>
}
export default SponsorsList

View File

@ -0,0 +1,23 @@
.sponsors_list {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 20px;
.sponsor {
display: flex;
flex-direction: column;
gap: 5px;
img {
max-width: 250px;
max-height: 100px;
max-width: 250px;
min-height: 100px;
}
}
}

View File

@ -72,6 +72,10 @@ export default class APICore extends Core {
app.eventBus.emit("session.invalid", error)
})
this.client.eventBus.on("auth:disabled_account", () => {
app.eventBus.emit("auth:disabled_account")
})
// make a basic request to check if the API is available
await this.client.baseRequest({
method: "head",

View File

@ -0,0 +1,4 @@
export default () => {
app.location.push("/settings?tab=about")
return null
}

View File

@ -0,0 +1,108 @@
import React from "react"
import { Alert, Divider, Checkbox, Button } from "antd"
import AuthModel from "@models/auth"
const DisableAccountPage = () => {
const [confirm, setConfirm] = React.useState(false)
async function submit() {
if (!confirm) {
return null
}
AuthModel.disableAccount({ confirm })
.then(() => {
app.message.success("Your account has been disabled. More information will be sent to your email.")
})
.catch(() => {
app.message.error("Failed to disable your account.")
})
}
return <div className="flex-column gap-20 align-start w-100">
<div className="flex-column align-start">
<h1>Account Disablement</h1>
<p>You are about to disable your account.</p>
</div>
<div className="flex-column gap-10 align-start">
<Divider
style={{
margin: "5px 0"
}}
/>
<p>
Due to our security policy, your data is retained when your account is disabled, this retention period is 2 months, but can be extended if appropriate.
</p>
<p>
Once the account hold expires, all of your data will be permanently deleted.
</p>
<Divider
style={{
margin: "5px 0"
}}
/>
<p>
This action cannot be stopped directly, you must contact support to stop this process.
</p>
<p>
<strong>
In case an imminent deletion is necessary, you should contact support.
</strong>
</p>
<Divider
style={{
margin: "5px 0"
}} />
</div>
<div className="flex-column gap-10 align-start w-100">
<p>
These are all the data that are deleted after the retention time:
</p>
<ul>
<li>Your account data</li>
<li>Your profile</li>
<li>Sessions logs</li>
<li>All content you have created; tracks, videos, posts, images, products, events, etc...</li>
<li>All information related to your account in our Databases</li>
</ul>
<p>
While your account is on hold, all of your information will remain visible, but your account will not be usable by any services or log in.
</p>
<Divider
style={{
margin: "10px 0"
}}
/>
</div>
<Alert
type="warning"
message="Some features like API keys keeps working until your account is fully deleted."
/>
<div className="flex-column align-start justify-space-between gap-10 w-100">
<Checkbox
checked={confirm}
onChange={(e) => setConfirm(e.target.checked)}
>
Im aware of this action and I want to continue.
</Checkbox>
<Button
type="primary"
onClick={submit}
disabled={!confirm}
>
Disable Account
</Button>
</div>
</div>
}
export default DisableAccountPage

View File

@ -1,143 +0,0 @@
import React from "react"
import * as antd from "antd"
import PlaylistView from "@components/Music/PlaylistView"
import MusicModel from "@models/music"
export default class FavoriteTracks extends React.Component {
state = {
error: null,
initialLoading: true,
loading: false,
list: [],
total_length: 0,
empty: false,
hasMore: true,
offset: 0,
}
static loadLimit = 50
componentDidMount = async () => {
await this.loadItems()
}
onLoadMore = async () => {
console.log(`Loading more items...`, this.state.offset)
const newOffset = this.state.offset + FavoriteTracks.loadLimit
await this.setState({
offset: newOffset,
})
await this.loadItems({
offset: newOffset,
})
}
loadItems = async ({
replace = false,
offset = 0,
limit = FavoriteTracks.loadLimit,
} = {}) => {
this.setState({
loading: true,
})
const result = await MusicModel.getFavouriteFolder({
offset: offset,
limit: limit,
}).catch((error) => {
this.setState({
error: error.message,
})
return null
})
console.log("Loaded favorites => ", result)
if (result) {
const {
tracks,
releases,
playlists,
total_length,
} = result
const data = [
...tracks.list,
...releases.list,
...playlists.list,
]
if (total_length === 0) {
this.setState({
empty: true,
hasMore: false,
initialLoading: false,
})
}
if (data.length === 0) {
return this.setState({
empty: false,
hasMore: false,
initialLoading: false,
})
}
if (replace) {
this.setState({
list: data,
})
} else {
this.setState({
list: [...this.state.list, ...data],
})
}
this.setState({
total_length
})
}
this.setState({
loading: false,
initialLoading: false,
})
}
render() {
if (this.state.error) {
return <antd.Result
status="error"
title="Error"
subTitle={this.state.error}
/>
}
if (this.state.initialLoading) {
return <antd.Skeleton active />
}
return <PlaylistView
favorite
type="vertical"
playlist={{
title: "Your favorites",
cover: "https://storage.ragestudio.net/comty-static-assets/favorite_song.png",
list: this.state.list
}}
centered={app.isMobile}
onLoadMore={this.onLoadMore}
hasMore={this.state.hasMore}
length={this.state.total_length}
/>
}
}

View File

@ -1,5 +1,4 @@
import LibraryTab from "./library"
import FavoritesTab from "./favorites"
import ExploreTab from "./explore"
export default [
@ -15,12 +14,6 @@ export default [
icon: "MdLibraryMusic",
component: LibraryTab,
},
{
key: "favorites",
label: "Favorites",
icon: "MdFavoriteBorder",
component: FavoritesTab,
},
{
key: "radio",
label: "Radio",

View File

@ -1,189 +1,40 @@
import React from "react"
import * as antd from "antd"
import classnames from "classnames"
import Image from "@components/Image"
import { Icons } from "@components/Icons"
import OpenPlaylistCreator from "@components/Music/PlaylistCreator"
import MusicModel from "@models/music"
import TracksLibraryView from "./views/tracks"
import PlaylistLibraryView from "./views/playlists"
import "./index.less"
const ReleaseTypeDecorators = {
"user": () => <p >
<Icons.MdPlaylistAdd />
Playlist
</p>,
"playlist": () => <p >
<Icons.MdPlaylistAdd />
Playlist
</p>,
"editorial": () => <p >
<Icons.MdPlaylistAdd />
Official Playlist
</p>,
"single": () => <p >
<Icons.MdMusicNote />
Single
</p>,
"album": () => <p >
<Icons.MdAlbum />
Album
</p>,
"ep": () => <p >
<Icons.MdAlbum />
EP
</p>,
"mix": () => <p >
<Icons.MdMusicNote />
Mix
</p>,
const TabToView = {
tracks: TracksLibraryView,
playlist: PlaylistLibraryView,
releases: PlaylistLibraryView,
}
function isNotAPlaylist(type) {
return type === "album" || type === "ep" || type === "mix" || type === "single"
}
const PlaylistItem = (props) => {
const data = props.data ?? {}
const handleOnClick = () => {
if (typeof props.onClick === "function") {
props.onClick(data)
}
if (props.type !== "action") {
if (data.service) {
return app.navigation.goToPlaylist(`${data._id}?service=${data.service}`)
}
return app.navigation.goToPlaylist(data._id)
}
}
return <div
className={classnames(
"playlist_item",
{
["action"]: props.type === "action",
["release"]: isNotAPlaylist(data.type),
}
)}
onClick={handleOnClick}
>
<div className="playlist_item_icon">
{
React.isValidElement(data.icon)
? <div className="playlist_item_icon_svg">
{data.icon}
</div>
: <Image
src={data.icon}
alt="playlist icon"
/>
}
</div>
<div className="playlist_item_info">
<div className="playlist_item_info_title">
<h1>
{
data.service === "tidal" && <Icons.SiTidal />
}
{
data.title ?? "Unnamed playlist"
}
</h1>
</div>
{
data.owner && <div className="playlist_item_info_owner">
<h4>
{
data.owner
}
</h4>
</div>
}
{
data.description && <div className="playlist_item_info_description">
<p>
{
data.description
}
</p>
{
ReleaseTypeDecorators[String(data.type).toLowerCase()] && ReleaseTypeDecorators[String(data.type).toLowerCase()](props)
}
{
data.public
? <p>
<Icons.MdVisibility />
Public
</p>
: <p>
<Icons.MdVisibilityOff />
Private
</p>
}
</div>
}
</div>
</div>
}
const OwnPlaylists = (props) => {
const [L_Playlists, R_Playlists, E_Playlists, M_Playlists] = app.cores.api.useRequest(MusicModel.getFavoritePlaylists)
if (E_Playlists) {
console.error(E_Playlists)
return <antd.Result
status="warning"
title="Failed to load"
subTitle="We are sorry, but we could not load your playlists. Please try again later."
/>
}
if (L_Playlists) {
return <antd.Skeleton />
}
return <div className="own_playlists">
<PlaylistItem
type="action"
data={{
icon: <Icons.MdPlaylistAdd />,
title: "Create new",
}}
onClick={OpenPlaylistCreator}
/>
{
R_Playlists.items.map((playlist) => {
playlist.icon = playlist.cover ?? playlist.thumbnail
playlist.description = `${playlist.numberOfTracks ?? playlist.list.length} tracks`
return <PlaylistItem
key={playlist.id}
data={playlist}
/>
})
}
</div>
const TabToHeader = {
tracks: {
icon: <Icons.MdMusicNote />,
label: "Tracks",
},
playlist: {
icon: <Icons.MdPlaylistPlay />,
label: "Playlists",
},
}
const Library = (props) => {
const [selectedTab, setSelectedTab] = React.useState("tracks")
return <div className="music-library">
<div className="music-library_header">
<h1>Library</h1>
<antd.Segmented
value={selectedTab}
onChange={setSelectedTab}
options={[
{
value: "tracks",
@ -195,18 +46,18 @@ const Library = (props) => {
label: "Playlists",
icon: <Icons.MdPlaylistPlay />
},
{
value: "releases",
label: "Releases",
icon: <Icons.MdPlaylistPlay />
}
]}
/>
</div>
<PlaylistItem
type="action"
data={{
icon: <Icons.MdPlaylistAdd />,
title: "Create new",
}}
onClick={OpenPlaylistCreator}
/>
<OwnPlaylists />
{
selectedTab && TabToView[selectedTab] && React.createElement(TabToView[selectedTab])
}
</div>
}

View File

@ -1,170 +1,3 @@
@playlist_item_icon_size: 50px;
.playlist_item {
display: flex;
flex-direction: row;
width: 100%;
height: 70px;
gap: 10px;
padding: 10px;
border: 1px solid var(--border-color);
border-radius: 12px;
cursor: pointer;
overflow: hidden;
&.release {
.playlist_item_icon {
img {
border-radius: 50%;
}
}
}
&.action {
.playlist_item_icon {
color: var(--colorPrimary);
}
}
.playlist_item_icon {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: @playlist_item_icon_size;
height: @playlist_item_icon_size;
min-width: @playlist_item_icon_size;
min-height: @playlist_item_icon_size;
overflow: hidden;
border-radius: 12px;
img {
width: @playlist_item_icon_size;
height: @playlist_item_icon_size;
min-width: @playlist_item_icon_size;
min-height: @playlist_item_icon_size;
object-fit: cover;
}
.playlist_item_icon_svg {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
background-color: var(--background-color-accent);
svg {
margin: 0;
font-size: 2rem;
}
}
}
.playlist_item_info {
display: flex;
flex-direction: column;
//align-items: center;
justify-content: center;
height: 100%;
width: 90%;
text-overflow: ellipsis;
.playlist_item_info_title {
display: inline;
font-size: 0.8rem;
text-overflow: ellipsis;
h1 {
font-weight: 700;
white-space: nowrap;
overflow: hidden;
}
}
.playlist_item_info_owner {
display: inline;
overflow: hidden;
font-size: 0.7rem;
h4 {
font-weight: 400;
}
}
.playlist_item_info_description {
display: inline-flex;
flex-direction: row;
gap: 10px;
font-size: 0.7rem;
p {
font-weight: 500;
white-space: nowrap;
overflow: hidden;
text-transform: uppercase;
svg {
margin-right: 0.4rem;
}
}
}
h1,
h2,
h3,
h4,
h5,
h6,
p,
span {
margin: 0;
}
}
}
.own_playlists {
display: flex;
flex-direction: column;
width: 100%;
gap: 10px;
}
.music-library {
display: flex;
flex-direction: column;
@ -184,4 +17,11 @@
margin: 0;
}
}
.music-library-content {
display: flex;
flex-direction: column;
gap: 15px;
}
}

View File

@ -0,0 +1,181 @@
import React from "react"
import * as antd from "antd"
import classnames from "classnames"
import Image from "@components/Image"
import { Icons } from "@components/Icons"
import OpenPlaylistCreator from "@components/Music/PlaylistCreator"
import MusicModel from "@models/music"
import "./index.less"
const ReleaseTypeDecorators = {
"user": () => <p >
<Icons.MdPlaylistAdd />
Playlist
</p>,
"playlist": () => <p >
<Icons.MdPlaylistAdd />
Playlist
</p>,
"editorial": () => <p >
<Icons.MdPlaylistAdd />
Official Playlist
</p>,
"single": () => <p >
<Icons.MdMusicNote />
Single
</p>,
"album": () => <p >
<Icons.MdAlbum />
Album
</p>,
"ep": () => <p >
<Icons.MdAlbum />
EP
</p>,
"mix": () => <p >
<Icons.MdMusicNote />
Mix
</p>,
}
function isNotAPlaylist(type) {
return type === "album" || type === "ep" || type === "mix" || type === "single"
}
const PlaylistItem = (props) => {
const data = props.data ?? {}
const handleOnClick = () => {
if (typeof props.onClick === "function") {
props.onClick(data)
}
if (props.type !== "action") {
if (data.service) {
return app.navigation.goToPlaylist(`${data._id}?service=${data.service}`)
}
return app.navigation.goToPlaylist(data._id)
}
}
return <div
className={classnames(
"playlist_item",
{
["action"]: props.type === "action",
["release"]: isNotAPlaylist(data.type),
}
)}
onClick={handleOnClick}
>
<div className="playlist_item_icon">
{
React.isValidElement(data.icon)
? <div className="playlist_item_icon_svg">
{data.icon}
</div>
: <Image
src={data.icon}
alt="playlist icon"
/>
}
</div>
<div className="playlist_item_info">
<div className="playlist_item_info_title">
<h1>
{
data.service === "tidal" && <Icons.SiTidal />
}
{
data.title ?? "Unnamed playlist"
}
</h1>
</div>
{
data.owner && <div className="playlist_item_info_owner">
<h4>
{
data.owner
}
</h4>
</div>
}
{
data.description && <div className="playlist_item_info_description">
<p>
{
data.description
}
</p>
{
ReleaseTypeDecorators[String(data.type).toLowerCase()] && ReleaseTypeDecorators[String(data.type).toLowerCase()](props)
}
{
data.public
? <p>
<Icons.MdVisibility />
Public
</p>
: <p>
<Icons.MdVisibilityOff />
Private
</p>
}
</div>
}
</div>
</div>
}
const PlaylistLibraryView = (props) => {
const [L_Playlists, R_Playlists, E_Playlists, M_Playlists] = app.cores.api.useRequest(MusicModel.getFavoritePlaylists)
if (E_Playlists) {
console.error(E_Playlists)
return <antd.Result
status="warning"
title="Failed to load"
subTitle="We are sorry, but we could not load your playlists. Please try again later."
/>
}
if (L_Playlists) {
return <antd.Skeleton />
}
return <div className="own_playlists">
<PlaylistItem
type="action"
data={{
icon: <Icons.MdPlaylistAdd />,
title: "Create new",
}}
onClick={OpenPlaylistCreator}
/>
{
R_Playlists.items.map((playlist) => {
playlist.icon = playlist.cover ?? playlist.thumbnail
playlist.description = `${playlist.numberOfTracks ?? playlist.list.length} tracks`
return <PlaylistItem
key={playlist.id}
data={playlist}
/>
})
}
</div>
}
export default PlaylistLibraryView

View File

@ -0,0 +1,166 @@
@playlist_item_icon_size: 50px;
.playlist_item {
display: flex;
flex-direction: row;
width: 100%;
height: 70px;
gap: 10px;
padding: 10px;
border: 1px solid var(--border-color);
border-radius: 12px;
cursor: pointer;
overflow: hidden;
&.release {
.playlist_item_icon {
img {
border-radius: 50%;
}
}
}
&.action {
.playlist_item_icon {
color: var(--colorPrimary);
}
}
.playlist_item_icon {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: @playlist_item_icon_size;
height: @playlist_item_icon_size;
min-width: @playlist_item_icon_size;
min-height: @playlist_item_icon_size;
overflow: hidden;
border-radius: 12px;
img {
width: @playlist_item_icon_size;
height: @playlist_item_icon_size;
min-width: @playlist_item_icon_size;
min-height: @playlist_item_icon_size;
object-fit: cover;
}
.playlist_item_icon_svg {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
background-color: var(--background-color-accent);
svg {
margin: 0;
font-size: 2rem;
}
}
}
.playlist_item_info {
display: flex;
flex-direction: column;
//align-items: center;
justify-content: center;
height: 100%;
width: 90%;
text-overflow: ellipsis;
.playlist_item_info_title {
display: inline;
font-size: 0.8rem;
text-overflow: ellipsis;
h1 {
font-weight: 700;
white-space: nowrap;
overflow: hidden;
}
}
.playlist_item_info_owner {
display: inline;
overflow: hidden;
font-size: 0.7rem;
h4 {
font-weight: 400;
}
}
.playlist_item_info_description {
display: inline-flex;
flex-direction: row;
gap: 10px;
font-size: 0.7rem;
p {
font-weight: 500;
white-space: nowrap;
overflow: hidden;
text-transform: uppercase;
svg {
margin-right: 0.4rem;
}
}
}
h1,
h2,
h3,
h4,
h5,
h6,
p,
span {
margin: 0;
}
}
}
.own_playlists {
display: flex;
flex-direction: column;
width: 100%;
gap: 10px;
}

View File

@ -0,0 +1,78 @@
import React from "react"
import * as antd from "antd"
import PlaylistView from "@components/Music/PlaylistView"
import MusicModel from "@models/music"
const loadLimit = 50
const TracksLibraryView = () => {
const [offset, setOffset] = React.useState(0)
const [list, setList] = React.useState([])
const [hasMore, setHasMore] = React.useState(true)
const [initialLoading, setInitialLoading] = React.useState(true)
const [L_Favourites, R_Favourites, E_Favourites, M_Favourites] = app.cores.api.useRequest(MusicModel.getFavouriteFolder, {
offset: offset,
limit: loadLimit,
})
async function onLoadMore() {
const newOffset = offset + loadLimit
setOffset(newOffset)
M_Favourites({
offset: newOffset,
limit: loadLimit,
})
}
React.useEffect(() => {
if (R_Favourites && R_Favourites.tracks) {
if (initialLoading === true) {
setInitialLoading(false)
}
if (R_Favourites.tracks.list.length === 0) {
setHasMore(false)
} else {
setList((prev) => {
prev = [
...prev,
...R_Favourites.tracks.list,
]
return prev
})
}
}
}, [R_Favourites])
if (E_Favourites) {
return <antd.Result
status="warning"
title="Failed to load"
subTitle={E_Favourites}
/>
}
if (initialLoading) {
return <antd.Skeleton active />
}
return <PlaylistView
noHeader
loading={L_Favourites}
type="vertical"
playlist={{
list: list
}}
onLoadMore={onLoadMore}
hasMore={hasMore}
length={R_Favourites.tracks.total_length}
/>
}
export default TracksLibraryView

View File

@ -7,6 +7,12 @@ import MyReleasesList from "@components/MusicStudio/MyReleasesList"
import "./index.less"
const ReleasesAnalytics = () => {
return <div>
<h1>Analytics</h1>
</div>
}
const MusicStudioPage = (props) => {
return <div
className="music-studio-page"
@ -25,6 +31,8 @@ const MusicStudioPage = (props) => {
</antd.Button>
</div>
<ReleasesAnalytics />
<MyReleasesList />
</div>
}

View File

@ -3,6 +3,7 @@ import * as antd from "antd"
import { Icons } from "@components/Icons"
import LatencyIndicator from "@components/PerformanceIndicators/latency"
import SponsorsList from "@components/SponsorsList"
import config from "@config"
@ -157,6 +158,11 @@ export default {
</div>
</div>
<div className="group">
<h3>Thanks to our sponsors</h3>
<SponsorsList />
</div>
<div className="group">
<div className="inline_field">
<div className="field_header">

View File

@ -32,6 +32,21 @@ export default {
description: "Manage your active sessions",
icon: "FiMonitor",
component: loadable(() => import("../components/sessions")),
},
{
id: "disable-account",
group: "security.account",
title: "Disable Account",
description: "Disable your account",
icon: "FiUserX",
component: "Button",
props: {
danger: true,
children: "Disable",
onClick: () => {
app.location.push("/disable-account")
}
}
}
]
}

View File

@ -1,3 +1,15 @@
// DIMENSIONS
// WIDTH
.w-100 {
width: 100%;
}
// HEIGHT
.h-100 {
height: 100%;
}
// FLEX
.flex-row {
display: flex;
@ -50,6 +62,11 @@
}
// GAPS
.gap-20,
.gap20 {
gap: 20px;
}
.gap-10,
.gap10 {
gap: 10px;

View File

@ -68,6 +68,10 @@ export default {
activated: {
type: Boolean,
default: false,
}
},
disabled: {
type: Boolean,
default: false
},
}
}

View File

@ -389,8 +389,8 @@ export default class Gateway {
async initialize() {
onExit(this.onGatewayExit)
process.stdout.setMaxListeners(50)
process.stderr.setMaxListeners(50)
process.stdout.setMaxListeners(150)
process.stderr.setMaxListeners(150)
this.services = await scanServices()
this.proxy = new Proxy()

View File

@ -8,4 +8,5 @@ export default class Account {
static deleteSession = require("./methods/deleteSession").default
static sendActivationCode = require("./methods/sendActivationCode").default
static activateAccount = require("./methods/activateAccount").default
static disableAccount = require("./methods/disableAccount").default
}

View File

@ -0,0 +1,28 @@
import { User } from "@db_models"
export default async (payload) => {
const { user_id } = payload
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")
}
user = await User.findOneAndUpdate(
{
_id: user._id.toString(),
},
{
disabled: true
},
)
return user.toObject()
}

View File

@ -14,6 +14,10 @@ export default async ({ username, password, hash }, user) => {
throw new OperationError(401, "User not found")
}
if (user.disabled == true) {
throw new OperationError(401, "User is disabled")
}
if (typeof hash !== "undefined") {
if (user.password !== hash) {
throw new OperationError(401, "Invalid credentials")

View File

@ -0,0 +1,17 @@
import AccountClass from "@classes/account"
import { OperationLog } from "@db_models"
export default {
middlewares: ["withAuthentication"],
fn: async (req) => {
const user_id = req.auth.session.user_id
await OperationLog.create({
user_id: user_id,
type: "disable_account",
date: Date.now()
})
return await AccountClass.disableAccount({ user_id })
}
}

View File

@ -31,6 +31,7 @@ export default async (req, res) => {
if (user.activated === false) {
return res.status(401).json({
code: user.email,
user_id: user._id.toString(),
activation_required: true,
})

View File

@ -38,7 +38,7 @@ export default {
parentDir: req.auth.session.user_id,
source: localFilepath,
service: providerType,
useCompression: req.headers["use-compression"] ?? true,
useCompression: ToBoolean(req.headers["use-compression"]) ?? true,
})
fs.promises.rm(tmpPath, { recursive: true, force: true })

View File

@ -47,6 +47,7 @@ export default (input, params = defaultParams) => {
const commands = {
input: input,
...params,
preset: "ultrafast",
output: outputFilepath,
}

View File

@ -10,12 +10,23 @@ export default async (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
const isAttachmentArray = Array.isArray(attachments) && attachments.length > 0
if (!isAttachmentsValid && !message) {
if (!isAttachmentArray && !message) {
throw new OperationError(400, "Cannot create a post without message or attachments")
}
// fix attachments with url strings
attachments = attachments.map((attachment) => {
if (typeof attachment === "string") {
attachment = {
url: attachment,
}
}
return attachment
})
if (!timestamp) {
timestamp = DateTime.local().toISO()
}