chat pic
This commit is contained in:
@@ -21,6 +21,29 @@ function storyMediaUrl (storagePath) {
|
||||
return normalized.replace('/root/modstagram-back/storage', '/storage')
|
||||
}
|
||||
|
||||
function clamp01 (value, fallback) {
|
||||
const num = Number(value)
|
||||
if (Number.isNaN(num)) return fallback
|
||||
return Math.min(1, Math.max(0, num))
|
||||
}
|
||||
|
||||
function sanitizeOverlays (raw) {
|
||||
if (!Array.isArray(raw)) return []
|
||||
return raw
|
||||
.filter((o) => o && typeof o.text === 'string' && o.text.trim().length)
|
||||
.slice(0, 20)
|
||||
.map((o) => ({
|
||||
text: String(o.text).slice(0, 300),
|
||||
x: clamp01(o.x, 0.5),
|
||||
y: clamp01(o.y, 0.5),
|
||||
font_size: clamp01(o.font_size, 0.06) || 0.06,
|
||||
color: typeof o.color === 'string' ? o.color.slice(0, 32) : '#ffffff',
|
||||
background: typeof o.background === 'string' ? o.background.slice(0, 32) : '',
|
||||
rotation: Number.isFinite(Number(o.rotation)) ? Number(o.rotation) : 0,
|
||||
align: ['left', 'center', 'right'].includes(o.align) ? o.align : 'center'
|
||||
}))
|
||||
}
|
||||
|
||||
async function resolveViewerId (req) {
|
||||
const token = req.header('Authorization')?.split(' ')[1]
|
||||
if (!token) return null
|
||||
@@ -35,7 +58,7 @@ async function resolveViewerId (req) {
|
||||
const createStoryBase64 = async (req, res) => {
|
||||
try {
|
||||
const userId = req.user._id
|
||||
const { file } = req.body
|
||||
const { file, overlays } = req.body
|
||||
|
||||
if (!file?.data || !file?.type) {
|
||||
return res.status(400).json({ error: true, message: 'فایل استوری الزامی است' })
|
||||
@@ -52,6 +75,7 @@ const createStoryBase64 = async (req, res) => {
|
||||
user_id: userId,
|
||||
media_path: filePath,
|
||||
media_type: mediaType,
|
||||
overlays: sanitizeOverlays(overlays),
|
||||
expires_at: new Date(Date.now() + STORY_TTL_MS),
|
||||
viewers: []
|
||||
})
|
||||
@@ -96,6 +120,7 @@ const getStoriesFeed = async (req, res, next) => {
|
||||
_id: story._id,
|
||||
media_path: storyMediaUrl(story.media_path),
|
||||
media_type: story.media_type,
|
||||
overlays: story.overlays || [],
|
||||
createdAt: story.createdAt,
|
||||
expires_at: story.expires_at,
|
||||
viewed: viewerId
|
||||
@@ -174,6 +199,30 @@ const markStoryViewed = async (req, res) => {
|
||||
}
|
||||
}
|
||||
|
||||
const updateStory = async (req, res) => {
|
||||
try {
|
||||
const userId = req.user._id
|
||||
const { storyId } = req.params
|
||||
const { overlays } = req.body
|
||||
|
||||
const story = await StoryModel.findOne({ _id: storyId, user_id: userId })
|
||||
if (!story || story.expires_at <= new Date()) {
|
||||
return res.status(404).json({ message: 'استوری یافت نشد' })
|
||||
}
|
||||
|
||||
story.overlays = sanitizeOverlays(overlays)
|
||||
await story.save()
|
||||
|
||||
return res.status(200).json({
|
||||
message: 'استوری ویرایش شد',
|
||||
overlays: story.overlays
|
||||
})
|
||||
} catch (err) {
|
||||
console.error('updateStory:', err)
|
||||
return res.status(500).json({ message: 'خطا در ویرایش استوری' })
|
||||
}
|
||||
}
|
||||
|
||||
const deleteStory = async (req, res) => {
|
||||
try {
|
||||
const userId = req.user._id
|
||||
@@ -193,5 +242,6 @@ module.exports = {
|
||||
createStoryBase64,
|
||||
getStoriesFeed,
|
||||
markStoryViewed,
|
||||
updateStory,
|
||||
deleteStory
|
||||
}
|
||||
|
||||
@@ -970,10 +970,13 @@ const getPostsWeb = async (req, res, next) => {
|
||||
lng,
|
||||
feedMode,
|
||||
seedPostId,
|
||||
sort,
|
||||
} = req.query;
|
||||
|
||||
const reelsMode = feedMode === "reels" || type === "video";
|
||||
const gridMode = feedMode === "grid";
|
||||
// فید اصلی صفحه پستها: فقط جدیدترینها (بدون شخصیسازی)
|
||||
const latestMode = sort === "latest" && !reelsMode;
|
||||
|
||||
const postFilter = { status: { $ne: null } };
|
||||
if (type === 'video' || type === 'image') {
|
||||
@@ -1090,6 +1093,24 @@ const getPostsWeb = async (req, res, next) => {
|
||||
|
||||
explorePosts = await mapPostsWithUsers(explorePosts, usersMap, decodedToken);
|
||||
|
||||
// فید اصلی: فقط جدیدترینها بر اساس تاریخ ساخت
|
||||
if (latestMode) {
|
||||
explorePosts.sort(
|
||||
(a, b) => new Date(b.createdAt) - new Date(a.createdAt)
|
||||
);
|
||||
const totalItems = explorePosts.length;
|
||||
const startIndex = (parseInt(page, 10) - 1) * parseInt(limit, 10);
|
||||
const paginatedPosts = explorePosts.slice(
|
||||
startIndex,
|
||||
startIndex + parseInt(limit, 10)
|
||||
);
|
||||
return res.status(200).json({
|
||||
posts: paginatedPosts,
|
||||
totalPages: Math.ceil(totalItems / limit) || 1,
|
||||
totalItems,
|
||||
});
|
||||
}
|
||||
|
||||
const { posts: paginatedPosts, totalItems } = await paginatePersonalizedExplore({
|
||||
posts: explorePosts,
|
||||
usersById,
|
||||
|
||||
10
index.js
10
index.js
@@ -1,3 +1,4 @@
|
||||
require('dotenv').config();
|
||||
const express = require('express');
|
||||
const app = express();
|
||||
const path = require('path');
|
||||
@@ -486,8 +487,7 @@ cron.schedule('0 0 1 * *', async () => {
|
||||
scheduled: true,
|
||||
timezone: 'Asia/Tehran'
|
||||
})
|
||||
module.exports = (port) => {
|
||||
http.listen(port, () => {
|
||||
console.log(`HTTP server is running on port ${port}`)
|
||||
})
|
||||
}
|
||||
const port = process.env.APP_PORT || 3002;
|
||||
http.listen(port, () => {
|
||||
console.log(`HTTP server is running on port ${port}`);
|
||||
});
|
||||
|
||||
@@ -1,6 +1,18 @@
|
||||
const mongoose = require('mongoose')
|
||||
const timestamp = require('mongoose-timestamp')
|
||||
|
||||
// متنهای روی استوری با مختصات نسبی (۰ تا ۱) تا در هر ابعادی درست رندر شوند
|
||||
const overlaySchema = new mongoose.Schema({
|
||||
text: { type: String, default: '' },
|
||||
x: { type: Number, default: 0.5 },
|
||||
y: { type: Number, default: 0.5 },
|
||||
font_size: { type: Number, default: 0.06 },
|
||||
color: { type: String, default: '#ffffff' },
|
||||
background: { type: String, default: '' },
|
||||
rotation: { type: Number, default: 0 },
|
||||
align: { type: String, default: 'center' }
|
||||
}, { _id: false })
|
||||
|
||||
const storySchema = new mongoose.Schema({
|
||||
user_id: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
@@ -17,6 +29,10 @@ const storySchema = new mongoose.Schema({
|
||||
enum: ['image', 'video'],
|
||||
required: true
|
||||
},
|
||||
overlays: {
|
||||
type: [overlaySchema],
|
||||
default: []
|
||||
},
|
||||
expires_at: {
|
||||
type: Date,
|
||||
required: true,
|
||||
|
||||
@@ -6,6 +6,7 @@ const {
|
||||
createStoryBase64,
|
||||
getStoriesFeed,
|
||||
markStoryViewed,
|
||||
updateStory,
|
||||
deleteStory
|
||||
} = require('../../../controllers/application/stories/storyController')
|
||||
|
||||
@@ -14,6 +15,7 @@ const router = express.Router()
|
||||
router.get('/feed', getStoriesFeed)
|
||||
router.post('/create-base64', auth, blockCheck, isHalfRegister, createStoryBase64)
|
||||
router.post('/view', auth, blockCheck, markStoryViewed)
|
||||
router.patch('/:storyId', auth, blockCheck, updateStory)
|
||||
router.delete('/:storyId', auth, blockCheck, deleteStory)
|
||||
|
||||
module.exports = router
|
||||
|
||||
12
server.js
12
server.js
@@ -1,9 +1,13 @@
|
||||
require('dotenv').config()
|
||||
console.log('APP_PORT:', process.env.APP_PORT)
|
||||
|
||||
const bootApplication = require('./index')
|
||||
// index.js خودش روی پورت listen میکند (اجرای مستقیم روی سرور: node index.js).
|
||||
// اینجا فقط require میکنیم تا سرور بالا بیاید. اگر نسخهای از index.js تابع
|
||||
// export کند، آن را هم صدا میزنیم تا با هر دو حالت سازگار باشد.
|
||||
const app = require('./index')
|
||||
console.log('Index loaded, booting...')
|
||||
|
||||
const port = process.env.APP_PORT || 3002
|
||||
bootApplication(port)
|
||||
console.log(`✅ Server should be running on port ${port}`)
|
||||
if (typeof app === 'function') {
|
||||
const port = process.env.APP_PORT || 3002
|
||||
app(port)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user