mirror of
https://github.com/ragestudio/comty.git
synced 2025-06-09 10:34:17 +00:00
support for post visibility
This commit is contained in:
parent
21441ef11a
commit
db14fd0c94
File diff suppressed because it is too large
Load Diff
@ -1,380 +1,387 @@
|
|||||||
@file_preview_borderRadius: 7px;
|
@file_preview_borderRadius: 7px;
|
||||||
|
|
||||||
.postCreator {
|
.postCreator {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
z-index: 55;
|
z-index: 55;
|
||||||
|
|
||||||
max-width: 600px;
|
max-width: 600px;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
|
|
||||||
background-color: var(--background-color-accent);
|
background-color: var(--background-color-accent);
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
|
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
|
|
||||||
.postCreator-header {
|
.postCreator-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
p {
|
p {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.postCreator-header-indicator {
|
.postCreator-header-indicator {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
|
||||||
gap: 7px;
|
gap: 7px;
|
||||||
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.actions {
|
.actions {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
|
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
|
|
||||||
transition: all 150ms ease-in-out;
|
transition: all 150ms ease-in-out;
|
||||||
|
|
||||||
.ant-btn {
|
.ant-btn {
|
||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
|
|
||||||
color: var(--text-color);
|
color: var(--text-color);
|
||||||
|
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.uploader {
|
.uploader {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
overflow-x: scroll;
|
overflow-x: scroll;
|
||||||
|
|
||||||
transition: all 150ms ease-in-out;
|
transition: all 150ms ease-in-out;
|
||||||
|
|
||||||
&.visible {
|
&.visible {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
z-index: 150;
|
z-index: 150;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
||||||
border: 0;
|
border: 0;
|
||||||
|
|
||||||
.ant-upload-drag {
|
.ant-upload-drag {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-upload-list {
|
.ant-upload-list {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-upload-drag {
|
.ant-upload-drag {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
width: 0;
|
width: 0;
|
||||||
height: 0;
|
height: 0;
|
||||||
|
|
||||||
background-color: transparent !important;
|
background-color: transparent !important;
|
||||||
border: 0;
|
border: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-upload-wrapper {
|
.ant-upload-wrapper {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
backdrop-filter: blur(8px);
|
backdrop-filter: blur(8px);
|
||||||
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
.ant-upload-list {
|
.ant-upload-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
|
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
|
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
|
|
||||||
border-radius: @file_preview_borderRadius;
|
border-radius: @file_preview_borderRadius;
|
||||||
|
|
||||||
.ant-upload-list-item-container {
|
.ant-upload-list-item-container {
|
||||||
background-color: rgba(var(--bg_color_5), 1);
|
background-color: rgba(var(--bg_color_5), 1);
|
||||||
|
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
height: fit-content;
|
height: fit-content;
|
||||||
|
|
||||||
border-radius: @file_preview_borderRadius;
|
border-radius: @file_preview_borderRadius;
|
||||||
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-upload-list-item-actions {
|
.ant-upload-list-item-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
font-size: 2rem;
|
font-size: 2rem;
|
||||||
|
|
||||||
border-radius: @file_preview_borderRadius;
|
border-radius: @file_preview_borderRadius;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hint {
|
.hint {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-self: center;
|
align-self: center;
|
||||||
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
|
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
|
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
margin-bottom: 6px;
|
margin-bottom: 6px;
|
||||||
margin-right: 0 !important;
|
margin-right: 0 !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.file {
|
.file {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
width: 10vw;
|
width: 10vw;
|
||||||
height: 10vw;
|
height: 10vw;
|
||||||
|
|
||||||
max-width: 150px;
|
max-width: 150px;
|
||||||
max-height: 150px;
|
max-height: 150px;
|
||||||
|
|
||||||
font-size: 2rem;
|
font-size: 2rem;
|
||||||
|
|
||||||
transition: all 150ms ease-in-out;
|
transition: all 150ms ease-in-out;
|
||||||
|
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
|
|
||||||
&.uploading {
|
&.uploading {
|
||||||
img {
|
img {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.actions {
|
.actions {
|
||||||
backdrop-filter: blur(5px);
|
backdrop-filter: blur(5px);
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
.actions {
|
.actions {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
backdrop-filter: blur(2px);
|
backdrop-filter: blur(2px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.preview {
|
.preview {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.preview {
|
.preview {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
img {
|
img {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
transition: all 150ms ease-in-out;
|
transition: all 150ms ease-in-out;
|
||||||
border-radius: @file_preview_borderRadius;
|
border-radius: @file_preview_borderRadius;
|
||||||
|
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: inline-flex;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
color: var(--text-color);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.actions {
|
.ant-select {
|
||||||
display: flex;
|
.ant-select-selector {
|
||||||
flex-direction: column;
|
border: 0;
|
||||||
|
padding: 4px 10px !important;
|
||||||
align-items: center;
|
background-color: rgba(var(--bg_color_1), 0.8);
|
||||||
justify-content: center;
|
|
||||||
|
.ant-select-selection-item {
|
||||||
width: 100%;
|
display: flex;
|
||||||
height: 100%;
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
transition: all 150ms ease-in-out;
|
color: var(--text-color);
|
||||||
|
}
|
||||||
opacity: 0;
|
}
|
||||||
color: rgba(var(--bg_color_1), 1);
|
|
||||||
|
.ant-select-arrow {
|
||||||
border-radius: @file_preview_borderRadius;
|
font-size: 0.6rem;
|
||||||
|
right: 7px;
|
||||||
svg {
|
}
|
||||||
color: rgba(var(--bg_color_1), 1);
|
}
|
||||||
margin: 0;
|
}
|
||||||
}
|
|
||||||
}
|
.textInput {
|
||||||
}
|
display: flex;
|
||||||
}
|
|
||||||
|
width: 100%;
|
||||||
.textInput {
|
|
||||||
display: flex;
|
transition: all 150ms ease-in-out;
|
||||||
|
|
||||||
width: 100%;
|
background-color: transparent;
|
||||||
|
|
||||||
transition: all 150ms ease-in-out;
|
svg {
|
||||||
|
margin: 0 !important;
|
||||||
background-color: transparent;
|
}
|
||||||
|
|
||||||
svg {
|
.avatar {
|
||||||
margin: 0 !important;
|
width: fit-content;
|
||||||
}
|
height: 45px;
|
||||||
|
|
||||||
.avatar {
|
display: flex;
|
||||||
width: fit-content;
|
|
||||||
height: 45px;
|
img {
|
||||||
|
width: 45px;
|
||||||
display: flex;
|
height: 45px;
|
||||||
|
border-radius: 12px;
|
||||||
img {
|
}
|
||||||
width: 45px;
|
}
|
||||||
height: 45px;
|
|
||||||
border-radius: 12px;
|
textarea {
|
||||||
}
|
color: var(--text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
textarea {
|
textarea::placeholder {
|
||||||
color: var(--text-color);
|
color: rgb(var(--bg_color_4));
|
||||||
}
|
}
|
||||||
|
|
||||||
textarea::placeholder {
|
.textArea {
|
||||||
color: rgb(var(--bg_color_4));
|
border-radius: 8px !important;
|
||||||
}
|
transition: all 150ms ease-in-out !important;
|
||||||
|
|
||||||
.textArea {
|
&.active {
|
||||||
border-radius: 8px !important;
|
background-color: var(--background-color-primary);
|
||||||
transition: all 150ms ease-in-out !important;
|
}
|
||||||
|
}
|
||||||
&.active {
|
|
||||||
background-color: var(--background-color-primary);
|
.ant-btn-primary {
|
||||||
}
|
z-index: 10;
|
||||||
}
|
position: relative;
|
||||||
|
border-radius: 0 10px 10px 0;
|
||||||
.ant-btn-primary {
|
height: 100%;
|
||||||
z-index: 10;
|
vertical-align: bottom;
|
||||||
position: relative;
|
border: none;
|
||||||
border-radius: 0 10px 10px 0;
|
box-shadow: none;
|
||||||
height: 100%;
|
|
||||||
vertical-align: bottom;
|
svg {
|
||||||
border: none;
|
color: var(--text-color) !important;
|
||||||
box-shadow: none;
|
}
|
||||||
|
}
|
||||||
svg {
|
|
||||||
color: var(--text-color) !important;
|
.ant-input {
|
||||||
}
|
background-color: transparent;
|
||||||
}
|
|
||||||
|
z-index: 10;
|
||||||
.ant-input {
|
position: relative;
|
||||||
background-color: transparent;
|
border-color: transparent !important;
|
||||||
|
box-shadow: none;
|
||||||
z-index: 10;
|
border-radius: 3px 0 0;
|
||||||
position: relative;
|
height: 100%;
|
||||||
border-color: transparent !important;
|
padding: 5px 10px;
|
||||||
box-shadow: none;
|
transition: height 150ms linear;
|
||||||
border-radius: 3px 0 0;
|
width: 100%;
|
||||||
height: 100%;
|
}
|
||||||
padding: 5px 10px;
|
|
||||||
transition: height 150ms linear;
|
.ant-btn-primary[disabled] {
|
||||||
width: 100%;
|
background-color: var(--background-color-accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-btn-primary[disabled] {
|
.ant-input-affix-wrapper {
|
||||||
background-color: var(--background-color-accent);
|
height: 100%;
|
||||||
}
|
|
||||||
|
border: 0;
|
||||||
.ant-input-affix-wrapper {
|
outline: 0;
|
||||||
height: 100%;
|
box-shadow: none;
|
||||||
|
|
||||||
border: 0;
|
background-color: transparent;
|
||||||
outline: 0;
|
}
|
||||||
box-shadow: none;
|
}
|
||||||
|
}
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -334,6 +334,8 @@ export class PostsListsComponent extends React.Component {
|
|||||||
"posts",
|
"posts",
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
app.cores.api.client().sockets.posts.emit("connect_realtime")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -362,6 +364,8 @@ export class PostsListsComponent extends React.Component {
|
|||||||
"posts",
|
"posts",
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
app.cores.api.client().sockets.posts.emit("disconnect_realtime")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,37 +1,41 @@
|
|||||||
export default {
|
export default {
|
||||||
name: "Post",
|
name: "Post",
|
||||||
collection: "posts",
|
collection: "posts",
|
||||||
schema: {
|
schema: {
|
||||||
user_id: {
|
user_id: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: true,
|
||||||
},
|
},
|
||||||
created_at: {
|
created_at: {
|
||||||
type: Date,
|
type: Date,
|
||||||
required: true
|
required: true,
|
||||||
},
|
},
|
||||||
message: {
|
message: {
|
||||||
type: String
|
type: String,
|
||||||
},
|
},
|
||||||
attachments: {
|
attachments: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: []
|
default: [],
|
||||||
},
|
},
|
||||||
flags: {
|
flags: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: []
|
default: [],
|
||||||
},
|
},
|
||||||
reply_to: {
|
reply_to: {
|
||||||
type: String,
|
type: String,
|
||||||
default: null
|
default: null,
|
||||||
},
|
},
|
||||||
updated_at: {
|
updated_at: {
|
||||||
type: String,
|
type: String,
|
||||||
default: null
|
default: null,
|
||||||
},
|
},
|
||||||
poll_options: {
|
poll_options: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: null
|
default: null,
|
||||||
}
|
},
|
||||||
}
|
visibility: {
|
||||||
}
|
type: String,
|
||||||
|
default: "public",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
@ -1,19 +1,19 @@
|
|||||||
export default class Posts {
|
export default class Posts {
|
||||||
static timeline = require("./methods/timeline").default
|
static timeline = require("./methods/timeline").default
|
||||||
static globalTimeline = require("./methods/globalTimeline").default
|
static globalTimeline = require("./methods/globalTimeline").default
|
||||||
static data = require("./methods/data").default
|
static data = require("./methods/data").default
|
||||||
static getLiked = require("./methods/getLiked").default
|
static getLiked = require("./methods/getLiked").default
|
||||||
static getSaved = require("./methods/getSaved").default
|
static getSaved = require("./methods/getSaved").default
|
||||||
static fromUserId = require("./methods/fromUserId").default
|
static fromUserId = require("./methods/fromUserId").default
|
||||||
static create = require("./methods/create").default
|
static create = require("./methods/create").default
|
||||||
static fullfillPost = require("./methods/fullfill").default
|
static stage = require("./methods/stage").default
|
||||||
static toggleSave = require("./methods/toggleSave").default
|
static toggleSave = require("./methods/toggleSave").default
|
||||||
static toggleLike = require("./methods/toggleLike").default
|
static toggleLike = require("./methods/toggleLike").default
|
||||||
static report = require("./methods/report").default
|
static report = require("./methods/report").default
|
||||||
static flag = require("./methods/flag").default
|
static flag = require("./methods/flag").default
|
||||||
static delete = require("./methods/delete").default
|
static delete = require("./methods/delete").default
|
||||||
static update = require("./methods/update").default
|
static update = require("./methods/update").default
|
||||||
static replies = require("./methods/replies").default
|
static replies = require("./methods/replies").default
|
||||||
static votePoll = require("./methods/votePoll").default
|
static votePoll = require("./methods/votePoll").default
|
||||||
static deleteVotePoll = require("./methods/deletePollVote").default
|
static deleteVotePoll = require("./methods/deletePollVote").default
|
||||||
}
|
}
|
||||||
|
@ -2,73 +2,113 @@ import requiredFields from "@shared-utils/requiredFields"
|
|||||||
import { DateTime } from "luxon"
|
import { DateTime } from "luxon"
|
||||||
|
|
||||||
import { Post } from "@db_models"
|
import { Post } from "@db_models"
|
||||||
import fullfill from "./fullfill"
|
import stage from "./stage"
|
||||||
|
|
||||||
export default async (payload = {}) => {
|
const visibilityOptions = ["public", "private", "only_mutuals"]
|
||||||
await requiredFields(["user_id"], payload)
|
|
||||||
|
|
||||||
let { user_id, message, attachments, timestamp, reply_to, poll_options } = payload
|
export default async (payload = {}, req) => {
|
||||||
|
await requiredFields(["user_id"], payload)
|
||||||
|
|
||||||
// check if is a Array and have at least one element
|
let {
|
||||||
const isAttachmentArray = Array.isArray(attachments) && attachments.length > 0
|
user_id,
|
||||||
|
message,
|
||||||
|
attachments,
|
||||||
|
timestamp,
|
||||||
|
reply_to,
|
||||||
|
poll_options,
|
||||||
|
visibility = "public",
|
||||||
|
} = payload
|
||||||
|
|
||||||
if (!isAttachmentArray && !message) {
|
// check if visibility is valid
|
||||||
throw new OperationError(400, "Cannot create a post without message or attachments")
|
if (!visibilityOptions.includes(visibility)) {
|
||||||
}
|
throw new OperationError(400, "Invalid visibility option")
|
||||||
|
}
|
||||||
|
|
||||||
if (isAttachmentArray) {
|
// check if is a Array and have at least one element
|
||||||
// clean empty attachments
|
const isAttachmentArray =
|
||||||
attachments = attachments.filter((attachment) => attachment)
|
Array.isArray(attachments) && attachments.length > 0
|
||||||
|
|
||||||
// fix attachments with url strings if needed
|
if (!isAttachmentArray && !message) {
|
||||||
attachments = attachments.map((attachment) => {
|
throw new OperationError(
|
||||||
if (typeof attachment === "string") {
|
400,
|
||||||
attachment = {
|
"Cannot create a post without message or attachments",
|
||||||
url: attachment,
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return attachment
|
if (isAttachmentArray) {
|
||||||
})
|
// clean empty attachments
|
||||||
}
|
attachments = attachments.filter((attachment) => attachment)
|
||||||
|
|
||||||
if (!timestamp) {
|
// fix attachments with url strings if needed
|
||||||
timestamp = DateTime.local().toISO()
|
attachments = attachments.map((attachment) => {
|
||||||
} else {
|
if (typeof attachment === "string") {
|
||||||
timestamp = DateTime.fromISO(timestamp).toISO()
|
attachment = {
|
||||||
}
|
url: attachment,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (Array.isArray(poll_options)) {
|
return attachment
|
||||||
poll_options = poll_options.map((option) => {
|
})
|
||||||
if (!option.id) {
|
}
|
||||||
option.id = nanoid()
|
|
||||||
}
|
|
||||||
|
|
||||||
return option
|
if (!timestamp) {
|
||||||
})
|
timestamp = DateTime.local().toISO()
|
||||||
}
|
} else {
|
||||||
|
timestamp = DateTime.fromISO(timestamp).toISO()
|
||||||
|
}
|
||||||
|
|
||||||
let post = new Post({
|
if (Array.isArray(poll_options)) {
|
||||||
created_at: timestamp,
|
poll_options = poll_options.map((option) => {
|
||||||
user_id: typeof user_id === "object" ? user_id.toString() : user_id,
|
if (!option.id) {
|
||||||
message: message,
|
option.id = nanoid()
|
||||||
attachments: attachments ?? [],
|
}
|
||||||
reply_to: reply_to,
|
|
||||||
flags: [],
|
|
||||||
poll_options: poll_options,
|
|
||||||
})
|
|
||||||
|
|
||||||
await post.save()
|
return option
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
post = post.toObject()
|
let post = new Post({
|
||||||
|
created_at: timestamp,
|
||||||
|
user_id: typeof user_id === "object" ? user_id.toString() : user_id,
|
||||||
|
message: message,
|
||||||
|
attachments: attachments ?? [],
|
||||||
|
reply_to: reply_to,
|
||||||
|
flags: [],
|
||||||
|
poll_options: poll_options,
|
||||||
|
visibility: visibility.toLocaleLowerCase(),
|
||||||
|
})
|
||||||
|
|
||||||
const result = await fullfill({
|
await post.save()
|
||||||
posts: post,
|
|
||||||
for_user_id: user_id
|
|
||||||
})
|
|
||||||
|
|
||||||
// TODO: create background jobs (nsfw dectection)
|
post = post.toObject()
|
||||||
global.websocket.io.of("/").emit(`post.new`, result[0])
|
|
||||||
|
|
||||||
return post
|
const result = await stage({
|
||||||
}
|
posts: post,
|
||||||
|
for_user_id: user_id,
|
||||||
|
})
|
||||||
|
|
||||||
|
// broadcast post to all users
|
||||||
|
if (visibility === "public") {
|
||||||
|
global.websocket.io
|
||||||
|
.to("global:posts:realtime")
|
||||||
|
.emit(`post.new`, result[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
if (visibility === "private") {
|
||||||
|
const userSocket = await global.websocket.find.socketByUserId(
|
||||||
|
post.user_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (userSocket) {
|
||||||
|
userSocket.emit(`post.new`, result[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: create background jobs (nsfw dectection)
|
||||||
|
global.queues.createJob("classify_post_attachments", {
|
||||||
|
post_id: post._id.toString(),
|
||||||
|
auth_token: req.headers.authorization,
|
||||||
|
})
|
||||||
|
|
||||||
|
return post
|
||||||
|
}
|
||||||
|
@ -1,54 +1,54 @@
|
|||||||
import { Post } from "@db_models"
|
import { Post } from "@db_models"
|
||||||
import fullfillPostsData from "./fullfill"
|
import stage from "./stage"
|
||||||
|
|
||||||
const maxLimit = 300
|
const maxLimit = 300
|
||||||
|
|
||||||
export default async (payload = {}) => {
|
export default async (payload = {}) => {
|
||||||
let {
|
let {
|
||||||
for_user_id,
|
for_user_id,
|
||||||
post_id,
|
post_id,
|
||||||
query = {},
|
query = {},
|
||||||
trim = 0,
|
trim = 0,
|
||||||
limit = 20,
|
limit = 20,
|
||||||
sort = { created_at: -1 },
|
sort = { created_at: -1 },
|
||||||
} = payload
|
} = payload
|
||||||
|
|
||||||
// set a hard limit on the number of posts to retrieve, used for pagination
|
// set a hard limit on the number of posts to retrieve, used for pagination
|
||||||
if (limit > maxLimit) {
|
if (limit > maxLimit) {
|
||||||
limit = maxLimit
|
limit = maxLimit
|
||||||
}
|
}
|
||||||
|
|
||||||
let posts = []
|
let posts = []
|
||||||
|
|
||||||
if (post_id) {
|
if (post_id) {
|
||||||
try {
|
try {
|
||||||
const post = await Post.findById(post_id)
|
const post = await Post.findById(post_id)
|
||||||
|
|
||||||
posts = [post]
|
posts = [post]
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new OperationError(404, "Post not found")
|
throw new OperationError(404, "Post not found")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
posts = await Post.find({ ...query })
|
posts = await Post.find({ ...query })
|
||||||
.sort(sort)
|
.sort(sort)
|
||||||
.skip(trim)
|
.skip(trim)
|
||||||
.limit(limit)
|
.limit(limit)
|
||||||
}
|
}
|
||||||
|
|
||||||
// fullfill data
|
// fullfill data
|
||||||
posts = await fullfillPostsData({
|
posts = await stage({
|
||||||
posts,
|
posts,
|
||||||
for_user_id,
|
for_user_id,
|
||||||
})
|
})
|
||||||
|
|
||||||
// if post_id is specified, return only one post
|
// if post_id is specified, return only one post
|
||||||
if (post_id) {
|
if (post_id) {
|
||||||
if (posts.length === 0) {
|
if (posts.length === 0) {
|
||||||
throw new OperationError(404, "Post not found")
|
throw new OperationError(404, "Post not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
return posts[0]
|
return posts[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
return posts
|
return posts
|
||||||
}
|
}
|
||||||
|
@ -1,45 +1,65 @@
|
|||||||
import { Post, PostLike, PostSave } from "@db_models"
|
import { Post, PostLike, PostSave } from "@db_models"
|
||||||
|
|
||||||
export default async (payload = {}) => {
|
export default async (payload = {}) => {
|
||||||
let {
|
let { post_id } = payload
|
||||||
post_id
|
|
||||||
} = payload
|
|
||||||
|
|
||||||
if (!post_id) {
|
if (!post_id) {
|
||||||
throw new OperationError(400, "Missing post_id")
|
throw new OperationError(400, "Missing post_id")
|
||||||
}
|
}
|
||||||
|
|
||||||
await Post.deleteOne({
|
const post = await Post.findById(post_id)
|
||||||
_id: post_id,
|
|
||||||
}).catch((err) => {
|
|
||||||
throw new OperationError(500, `An error has occurred: ${err.message}`)
|
|
||||||
})
|
|
||||||
|
|
||||||
// search for likes
|
if (!post) {
|
||||||
await PostLike.deleteMany({
|
throw new OperationError(404, "Post not found")
|
||||||
post_id: post_id,
|
}
|
||||||
}).catch((err) => {
|
|
||||||
throw new OperationError(500, `An error has occurred: ${err.message}`)
|
|
||||||
})
|
|
||||||
|
|
||||||
// deleted from saved
|
await Post.deleteOne({
|
||||||
await PostSave.deleteMany({
|
_id: post_id,
|
||||||
post_id: post_id,
|
}).catch((err) => {
|
||||||
}).catch((err) => {
|
throw new OperationError(500, `An error has occurred: ${err.message}`)
|
||||||
throw new OperationError(500, `An error has occurred: ${err.message}`)
|
})
|
||||||
})
|
|
||||||
|
|
||||||
// delete replies
|
// search for likes
|
||||||
await Post.deleteMany({
|
await PostLike.deleteMany({
|
||||||
reply_to: post_id,
|
post_id: post_id,
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
throw new OperationError(500, `An error has occurred: ${err.message}`)
|
throw new OperationError(500, `An error has occurred: ${err.message}`)
|
||||||
})
|
})
|
||||||
|
|
||||||
global.websocket.io.of("/").emit(`post.delete`, post_id)
|
// deleted from saved
|
||||||
global.websocket.io.of("/").emit(`post.delete.${post_id}`, post_id)
|
await PostSave.deleteMany({
|
||||||
|
post_id: post_id,
|
||||||
|
}).catch((err) => {
|
||||||
|
throw new OperationError(500, `An error has occurred: ${err.message}`)
|
||||||
|
})
|
||||||
|
|
||||||
return {
|
// delete replies
|
||||||
deleted: true,
|
await Post.deleteMany({
|
||||||
}
|
reply_to: post_id,
|
||||||
}
|
}).catch((err) => {
|
||||||
|
throw new OperationError(500, `An error has occurred: ${err.message}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
if (post.visibility === "public") {
|
||||||
|
global.websocket.io
|
||||||
|
.to("global:posts:realtime")
|
||||||
|
.emit(`post.delete`, post)
|
||||||
|
global.websocket.io
|
||||||
|
.to("global:posts:realtime")
|
||||||
|
.emit(`post.delete.${post_id}`, post)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (post.visibility === "private") {
|
||||||
|
const userSocket = await global.websocket.find.socketByUserId(
|
||||||
|
post.user_id,
|
||||||
|
)
|
||||||
|
if (userSocket) {
|
||||||
|
userSocket.emit(`post.delete`, post_id)
|
||||||
|
userSocket.emit(`post.delete.${post_id}`, post_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
deleted: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,140 +0,0 @@
|
|||||||
import { User, PostLike, PostSave, Post, VotePoll } from "@db_models"
|
|
||||||
|
|
||||||
export default async (payload = {}) => {
|
|
||||||
let {
|
|
||||||
posts,
|
|
||||||
for_user_id,
|
|
||||||
} = payload
|
|
||||||
|
|
||||||
if (!Array.isArray(posts)) {
|
|
||||||
posts = [posts]
|
|
||||||
}
|
|
||||||
|
|
||||||
if (posts.every((post) => !post)) {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
let postsSavesIds = []
|
|
||||||
|
|
||||||
if (for_user_id) {
|
|
||||||
const postsSaves = await PostSave.find({ user_id: for_user_id })
|
|
||||||
.sort({ saved_at: -1 })
|
|
||||||
|
|
||||||
postsSavesIds = postsSaves.map((postSave) => postSave.post_id)
|
|
||||||
}
|
|
||||||
|
|
||||||
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: usersIds
|
|
||||||
}
|
|
||||||
}).catch(() => { }),
|
|
||||||
PostLike.find({
|
|
||||||
post_id: {
|
|
||||||
$in: postsIds
|
|
||||||
}
|
|
||||||
}).catch(() => []),
|
|
||||||
VotePoll.find({
|
|
||||||
post_id: {
|
|
||||||
$in: postsIds
|
|
||||||
}
|
|
||||||
}).catch(() => [])
|
|
||||||
])
|
|
||||||
|
|
||||||
// wrap likesData by post_id
|
|
||||||
likesData = likesData.reduce((acc, like) => {
|
|
||||||
if (!acc[like.post_id]) {
|
|
||||||
acc[like.post_id] = []
|
|
||||||
}
|
|
||||||
|
|
||||||
acc[like.post_id].push(like)
|
|
||||||
|
|
||||||
return acc
|
|
||||||
}, {})
|
|
||||||
|
|
||||||
posts = await Promise.all(posts.map(async (post, index) => {
|
|
||||||
if (typeof post.toObject === "function") {
|
|
||||||
post = post.toObject()
|
|
||||||
}
|
|
||||||
|
|
||||||
let user = usersData.find((user) => user._id.toString() === post.user_id.toString())
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
user = {
|
|
||||||
_deleted: true,
|
|
||||||
username: "Deleted user",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (post.reply_to) {
|
|
||||||
post.reply_to_data = await Post.findById(post.reply_to)
|
|
||||||
|
|
||||||
if (post.reply_to_data) {
|
|
||||||
post.reply_to_data = post.reply_to_data.toObject()
|
|
||||||
|
|
||||||
const replyUserData = await User.findById(post.reply_to_data.user_id)
|
|
||||||
|
|
||||||
if (replyUserData) {
|
|
||||||
post.reply_to_data.user = replyUserData.toObject()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
post.hasReplies = await Post.countDocuments({ reply_to: post._id })
|
|
||||||
|
|
||||||
let likes = likesData[post._id.toString()] ?? []
|
|
||||||
|
|
||||||
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
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
post.share_url = `${process.env.APP_URL}/post/${post._id}`
|
|
||||||
|
|
||||||
return {
|
|
||||||
...post,
|
|
||||||
user,
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
|
|
||||||
return posts
|
|
||||||
}
|
|
@ -1,29 +1,24 @@
|
|||||||
import { Post } from "@db_models"
|
import { Post } from "@db_models"
|
||||||
import fullfillPostsData from "./fullfill"
|
import stage from "./stage"
|
||||||
|
|
||||||
export default async (payload = {}) => {
|
export default async (payload = {}) => {
|
||||||
const {
|
const { post_id, for_user_id, trim = 0, limit = 50 } = payload
|
||||||
post_id,
|
|
||||||
for_user_id,
|
|
||||||
trim = 0,
|
|
||||||
limit = 50,
|
|
||||||
} = payload
|
|
||||||
|
|
||||||
if (!post_id) {
|
if (!post_id) {
|
||||||
throw new OperationError(400, "Post ID is required")
|
throw new OperationError(400, "Post ID is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
let posts = await Post.find({
|
let posts = await Post.find({
|
||||||
reply_to: post_id,
|
reply_to: post_id,
|
||||||
})
|
})
|
||||||
.limit(limit)
|
.limit(limit)
|
||||||
.skip(trim)
|
.skip(trim)
|
||||||
.sort({ created_at: -1 })
|
.sort({ created_at: -1 })
|
||||||
|
|
||||||
posts = await fullfillPostsData({
|
posts = await stage({
|
||||||
posts,
|
posts,
|
||||||
for_user_id,
|
for_user_id,
|
||||||
})
|
})
|
||||||
|
|
||||||
return posts
|
return posts
|
||||||
}
|
}
|
||||||
|
161
packages/server/services/posts/classes/posts/methods/stage.js
Normal file
161
packages/server/services/posts/classes/posts/methods/stage.js
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
import { User, PostLike, PostSave, Post, VotePoll } from "@db_models"
|
||||||
|
|
||||||
|
export default async (payload = {}) => {
|
||||||
|
let { posts, for_user_id } = payload
|
||||||
|
|
||||||
|
if (!Array.isArray(posts)) {
|
||||||
|
posts = [posts]
|
||||||
|
}
|
||||||
|
|
||||||
|
if (posts.every((post) => !post)) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
let postsSavesIds = []
|
||||||
|
|
||||||
|
if (for_user_id) {
|
||||||
|
const postsSaves = await PostSave.find({ user_id: for_user_id }).sort({
|
||||||
|
saved_at: -1,
|
||||||
|
})
|
||||||
|
|
||||||
|
postsSavesIds = postsSaves.map((postSave) => postSave.post_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
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: usersIds,
|
||||||
|
},
|
||||||
|
}).catch(() => {}),
|
||||||
|
PostLike.find({
|
||||||
|
post_id: {
|
||||||
|
$in: postsIds,
|
||||||
|
},
|
||||||
|
}).catch(() => []),
|
||||||
|
VotePoll.find({
|
||||||
|
post_id: {
|
||||||
|
$in: postsIds,
|
||||||
|
},
|
||||||
|
}).catch(() => []),
|
||||||
|
])
|
||||||
|
|
||||||
|
// wrap likesData by post_id
|
||||||
|
likesData = likesData.reduce((acc, like) => {
|
||||||
|
if (!acc[like.post_id]) {
|
||||||
|
acc[like.post_id] = []
|
||||||
|
}
|
||||||
|
|
||||||
|
acc[like.post_id].push(like)
|
||||||
|
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
|
||||||
|
posts = await Promise.all(
|
||||||
|
posts.map(async (post, index) => {
|
||||||
|
if (typeof post.toObject === "function") {
|
||||||
|
post = post.toObject()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (post.visibility === "private" && post.user_id !== for_user_id) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
post.visibility === "only_mutuals" &&
|
||||||
|
post.user_id !== for_user_id
|
||||||
|
) {
|
||||||
|
// TODO
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
let user = usersData.find(
|
||||||
|
(user) => user._id.toString() === post.user_id.toString(),
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
user = {
|
||||||
|
_deleted: true,
|
||||||
|
username: "Deleted user",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (post.reply_to) {
|
||||||
|
post.reply_to_data = await Post.findById(post.reply_to)
|
||||||
|
|
||||||
|
if (post.reply_to_data) {
|
||||||
|
post.reply_to_data = post.reply_to_data.toObject()
|
||||||
|
|
||||||
|
const replyUserData = await User.findById(
|
||||||
|
post.reply_to_data.user_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (replyUserData) {
|
||||||
|
post.reply_to_data.user = replyUserData.toObject()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
post.hasReplies = await Post.countDocuments({ reply_to: post._id })
|
||||||
|
|
||||||
|
let likes = likesData[post._id.toString()] ?? []
|
||||||
|
|
||||||
|
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
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
post.share_url = `${process.env.APP_URL}/post/${post._id}`
|
||||||
|
|
||||||
|
return {
|
||||||
|
...post,
|
||||||
|
user,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
// clear undefined and null
|
||||||
|
posts = posts.filter((post) => post !== undefined && post !== null)
|
||||||
|
|
||||||
|
return posts
|
||||||
|
}
|
@ -1,42 +1,60 @@
|
|||||||
import { Post } from "@db_models"
|
import { Post } from "@db_models"
|
||||||
import { DateTime } from "luxon"
|
import { DateTime } from "luxon"
|
||||||
import fullfill from "./fullfill"
|
import stage from "./stage"
|
||||||
|
|
||||||
export default async (post_id, update) => {
|
export default async (post_id, update) => {
|
||||||
let post = await Post.findById(post_id)
|
let post = await Post.findById(post_id)
|
||||||
|
|
||||||
if (!post) {
|
if (!post) {
|
||||||
throw new OperationError(404, "Post not found")
|
throw new OperationError(404, "Post not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateKeys = Object.keys(update)
|
const updateKeys = Object.keys(update)
|
||||||
|
|
||||||
updateKeys.forEach((key) => {
|
updateKeys.forEach((key) => {
|
||||||
post[key] = update[key]
|
post[key] = update[key]
|
||||||
})
|
})
|
||||||
|
|
||||||
post.updated_at = DateTime.local().toISO()
|
post.updated_at = DateTime.local().toISO()
|
||||||
|
|
||||||
if (Array.isArray(update.poll_options)) {
|
if (Array.isArray(update.poll_options)) {
|
||||||
post.poll_options = update.poll_options.map((option) => {
|
post.poll_options = update.poll_options.map((option) => {
|
||||||
if (!option.id) {
|
if (!option.id) {
|
||||||
option.id = nanoid()
|
option.id = nanoid()
|
||||||
}
|
}
|
||||||
|
|
||||||
return option
|
return option
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
await post.save()
|
await post.save()
|
||||||
|
|
||||||
post = post.toObject()
|
post = post.toObject()
|
||||||
|
|
||||||
const result = await fullfill({
|
const result = await stage({
|
||||||
posts: post,
|
posts: post,
|
||||||
})
|
for_user_id: post.user_id,
|
||||||
|
})
|
||||||
|
|
||||||
global.websocket.io.of("/").emit(`post.update`, result[0])
|
if (post.visibility === "public") {
|
||||||
global.websocket.io.of("/").emit(`post.update.${post_id}`, result[0])
|
global.websocket.io
|
||||||
|
.to("global:posts:realtime")
|
||||||
|
.emit(`post.update`, result[0])
|
||||||
|
global.websocket.io
|
||||||
|
.to("global:posts:realtime")
|
||||||
|
.emit(`post.update.${post_id}`, result[0])
|
||||||
|
}
|
||||||
|
|
||||||
return result[0]
|
if (post.visibility === "private") {
|
||||||
}
|
const userSocket = await global.websocket.find.socketByUserId(
|
||||||
|
post.user_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (userSocket) {
|
||||||
|
userSocket.emit(`post.update`, result[0])
|
||||||
|
userSocket.emit(`post.update.${post_id}`, result[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result[0]
|
||||||
|
}
|
||||||
|
@ -2,30 +2,72 @@ import { Server } from "linebridge"
|
|||||||
|
|
||||||
import DbManager from "@shared-classes/DbManager"
|
import DbManager from "@shared-classes/DbManager"
|
||||||
import RedisClient from "@shared-classes/RedisClient"
|
import RedisClient from "@shared-classes/RedisClient"
|
||||||
|
import TaskQueueManager from "@shared-classes/TaskQueueManager"
|
||||||
|
|
||||||
import SharedMiddlewares from "@shared-middlewares"
|
import SharedMiddlewares from "@shared-middlewares"
|
||||||
|
|
||||||
export default class API extends Server {
|
// wsfast
|
||||||
static refName = "posts"
|
import HyperExpress from "hyper-express"
|
||||||
static enableWebsockets = true
|
|
||||||
static routesPath = `${__dirname}/routes`
|
|
||||||
static listen_port = process.env.HTTP_LISTEN_PORT ?? 3001
|
|
||||||
|
|
||||||
middlewares = {
|
class WSFastServer {
|
||||||
...SharedMiddlewares
|
router = new HyperExpress.Router()
|
||||||
}
|
|
||||||
|
|
||||||
contexts = {
|
clients = new Set()
|
||||||
db: new DbManager(),
|
|
||||||
redis: RedisClient(),
|
|
||||||
}
|
|
||||||
|
|
||||||
async onInitialize() {
|
routes = {
|
||||||
await this.contexts.db.initialize()
|
connect: async (socket) => {
|
||||||
await this.contexts.redis.initialize()
|
console.log("Client connected", socket)
|
||||||
}
|
},
|
||||||
|
}
|
||||||
|
|
||||||
handleWsAuth = require("@shared-lib/handleWsAuth").default
|
async initialize(engine) {
|
||||||
|
this.engine = engine
|
||||||
|
|
||||||
|
Object.keys(this.routes).forEach((route) => {
|
||||||
|
this.router.ws(`/${route}`, this.routes[route])
|
||||||
|
})
|
||||||
|
|
||||||
|
this.engine.app.use(this.router)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Boot(API)
|
export default class API extends Server {
|
||||||
|
static refName = "posts"
|
||||||
|
static enableWebsockets = true
|
||||||
|
static routesPath = `${__dirname}/routes`
|
||||||
|
static wsRoutesPath = `${__dirname}/routes_ws`
|
||||||
|
|
||||||
|
static listen_port = process.env.HTTP_LISTEN_PORT ?? 3001
|
||||||
|
|
||||||
|
middlewares = {
|
||||||
|
...SharedMiddlewares,
|
||||||
|
}
|
||||||
|
|
||||||
|
contexts = {
|
||||||
|
db: new DbManager(),
|
||||||
|
redis: RedisClient(),
|
||||||
|
ws: new WSFastServer(this.engine),
|
||||||
|
}
|
||||||
|
|
||||||
|
queuesManager = new TaskQueueManager(
|
||||||
|
{
|
||||||
|
workersPath: `${__dirname}/queues`,
|
||||||
|
},
|
||||||
|
this,
|
||||||
|
)
|
||||||
|
|
||||||
|
async onInitialize() {
|
||||||
|
await this.contexts.db.initialize()
|
||||||
|
await this.contexts.redis.initialize()
|
||||||
|
await this.queuesManager.initialize({
|
||||||
|
redisOptions: this.engine.ws.redis.options,
|
||||||
|
})
|
||||||
|
await this.contexts.ws.initialize(this.engine)
|
||||||
|
|
||||||
|
global.queues = this.queuesManager
|
||||||
|
}
|
||||||
|
|
||||||
|
handleWsAuth = require("@shared-lib/handleWsAuth").default
|
||||||
|
}
|
||||||
|
|
||||||
|
Boot(API)
|
||||||
|
@ -0,0 +1,67 @@
|
|||||||
|
import { Post } from "@db_models"
|
||||||
|
import axios from "axios"
|
||||||
|
|
||||||
|
const classifyAPI = "https://vision-service.ragestudio.net"
|
||||||
|
|
||||||
|
const adultLevels = [
|
||||||
|
"VERY_UNLIKELY",
|
||||||
|
"UNLIKELY",
|
||||||
|
"POSSIBLE",
|
||||||
|
"LIKELY",
|
||||||
|
"VERY_LIKELY",
|
||||||
|
]
|
||||||
|
|
||||||
|
export default {
|
||||||
|
id: "classify_post_attachments",
|
||||||
|
maxJobs: 100,
|
||||||
|
process: async (job) => {
|
||||||
|
const { post_id, auth_token } = job.data
|
||||||
|
|
||||||
|
let post = await Post.findById(post_id).lean()
|
||||||
|
|
||||||
|
console.log(`[CLASSIFY] Checking post ${post_id}`)
|
||||||
|
|
||||||
|
if (!post) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Array.isArray(post.attachments)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for await (const attachment of post.attachments) {
|
||||||
|
if (!attachment.url) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await axios({
|
||||||
|
method: "GET",
|
||||||
|
url: `${classifyAPI}/safe_detect`,
|
||||||
|
headers: {
|
||||||
|
Authorization: auth_token,
|
||||||
|
},
|
||||||
|
params: {
|
||||||
|
url: attachment.url,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`[CLASSIFY] Attachment [${attachment.url}] classified as ${response.data.detections.adult}`,
|
||||||
|
)
|
||||||
|
|
||||||
|
const adultLevel = adultLevels.indexOf(
|
||||||
|
response.data.detections.adult,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!Array.isArray(attachment.flags)) {
|
||||||
|
attachment.flags = []
|
||||||
|
}
|
||||||
|
|
||||||
|
if (adultLevel > 2) {
|
||||||
|
attachment.flags.push("nsfw")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await Post.findByIdAndUpdate(post._id, post)
|
||||||
|
},
|
||||||
|
}
|
@ -1,44 +1,56 @@
|
|||||||
import PostClass from "@classes/posts"
|
import PostClass from "@classes/posts"
|
||||||
import { Post } from "@db_models"
|
import { Post } from "@db_models"
|
||||||
|
|
||||||
const AllowedFields = ["message", "tags", "attachments", "poll_options"]
|
const AllowedFields = [
|
||||||
|
"message",
|
||||||
|
"tags",
|
||||||
|
"attachments",
|
||||||
|
"poll_options",
|
||||||
|
"visibility",
|
||||||
|
]
|
||||||
|
|
||||||
// TODO: Get limits from LimitsAPI
|
// TODO: Get limits from LimitsAPI
|
||||||
const MaxStringsLengths = {
|
const MaxStringsLengths = {
|
||||||
message: 2000
|
message: 2000,
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
middlewares: ["withAuthentication"],
|
middlewares: ["withAuthentication"],
|
||||||
fn: async (req) => {
|
fn: async (req) => {
|
||||||
let update = {}
|
let update = {}
|
||||||
|
|
||||||
const post = await Post.findById(req.params.post_id)
|
const post = await Post.findById(req.params.post_id)
|
||||||
|
|
||||||
if (!post) {
|
if (!post) {
|
||||||
throw new OperationError(404, "Post not found")
|
throw new OperationError(404, "Post not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
if (post.user_id !== req.auth.session.user_id) {
|
if (post.user_id !== req.auth.session.user_id) {
|
||||||
throw new OperationError(403, "You cannot edit this post")
|
throw new OperationError(403, "You cannot edit this post")
|
||||||
}
|
}
|
||||||
|
|
||||||
AllowedFields.forEach((key) => {
|
AllowedFields.forEach((key) => {
|
||||||
if (typeof req.body[key] !== "undefined") {
|
if (typeof req.body[key] !== "undefined") {
|
||||||
// check maximung strings length
|
// check maximung strings length
|
||||||
if (typeof req.body[key] === "string" && MaxStringsLengths[key]) {
|
if (
|
||||||
if (req.body[key].length > MaxStringsLengths[key]) {
|
typeof req.body[key] === "string" &&
|
||||||
// create a substring
|
MaxStringsLengths[key]
|
||||||
update[key] = req.body[key].substring(0, MaxStringsLengths[key])
|
) {
|
||||||
} else {
|
if (req.body[key].length > MaxStringsLengths[key]) {
|
||||||
update[key] = req.body[key]
|
// create a substring
|
||||||
}
|
update[key] = req.body[key].substring(
|
||||||
} else {
|
0,
|
||||||
update[key] = req.body[key]
|
MaxStringsLengths[key],
|
||||||
}
|
)
|
||||||
}
|
} else {
|
||||||
})
|
update[key] = req.body[key]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
update[key] = req.body[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
return await PostClass.update(req.params.post_id, update)
|
return await PostClass.update(req.params.post_id, update)
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,16 @@
|
|||||||
import Posts from "@classes/posts"
|
import Posts from "@classes/posts"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
middlewares: ["withAuthentication"],
|
middlewares: ["withAuthentication"],
|
||||||
fn: async (req, res) => {
|
fn: async (req, res) => {
|
||||||
const result = await Posts.create({
|
const result = await Posts.create(
|
||||||
...req.body,
|
{
|
||||||
user_id: req.auth.session.user_id,
|
...req.body,
|
||||||
})
|
user_id: req.auth.session.user_id,
|
||||||
|
},
|
||||||
|
req,
|
||||||
|
)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
@ -1,25 +1,25 @@
|
|||||||
import { Post } from "@db_models"
|
import { Post } from "@db_models"
|
||||||
import fullfill from "@classes/posts/methods/fullfill"
|
import stage from "@classes/posts/methods/stage"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
middlewares: ["withOptionalAuthentication"],
|
middlewares: ["withOptionalAuthentication"],
|
||||||
fn: async (req) => {
|
fn: async (req) => {
|
||||||
const { limit, trim } = req.query
|
const { limit, trim } = req.query
|
||||||
|
|
||||||
let result = await Post.find({
|
let result = await Post.find({
|
||||||
message: {
|
message: {
|
||||||
$regex: new RegExp(`#${req.params.trending}`, "gi")
|
$regex: new RegExp(`#${req.params.trending}`, "gi"),
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
.sort({ created_at: -1 })
|
.sort({ created_at: -1 })
|
||||||
.skip(trim ?? 0)
|
.skip(trim ?? 0)
|
||||||
.limit(limit ?? 20)
|
.limit(limit ?? 20)
|
||||||
|
|
||||||
result = await fullfill({
|
result = await stage({
|
||||||
posts: result,
|
posts: result,
|
||||||
for_user_id: req.auth.session.user_id,
|
for_user_id: req.auth.session.user_id,
|
||||||
})
|
})
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,4 @@
|
|||||||
|
export default async function (socket) {
|
||||||
|
console.log(`Socket ${socket.id} connected to realtime posts`)
|
||||||
|
socket.join("global:posts:realtime")
|
||||||
|
}
|
@ -0,0 +1,4 @@
|
|||||||
|
export default async function (socket) {
|
||||||
|
console.log(`Socket ${socket.id} disconnected from realtime posts`)
|
||||||
|
socket.leave("global:posts:realtime")
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user