dfssdfsfdsf
This commit is contained in:
2026-04-09 21:00:04 +02:00
parent 983b67e6fc
commit 89bc86b615
27 changed files with 2921 additions and 408 deletions

View File

@@ -4,13 +4,34 @@
*/
import express from 'express'
import { asyncHandler, NotFoundError, ValidationError } from '../middleware/errorHandler.mjs'
import { Client, Users, Query as AppwriteQuery } from 'node-appwrite'
import { asyncHandler, NotFoundError, ValidationError, AuthorizationError } from '../middleware/errorHandler.mjs'
import { validate, schemas, rules } from '../middleware/validate.mjs'
import { respond } from '../utils/response.mjs'
import { products, questions, submissions, orders, onboardingState, emailAccounts, emailStats, emailDigests, userPreferences, subscriptions, emailUsage, referrals, db, Collections, Query } from '../services/database.mjs'
import {
products,
questions,
submissions,
orders,
onboardingState,
emailAccounts,
emailStats,
emailDigests,
userPreferences,
subscriptions,
emailUsage,
referrals,
db,
Collections,
Query,
deleteAllDocumentsForUser,
} from '../services/database.mjs'
import Stripe from 'stripe'
import { config } from '../config/index.mjs'
import { config, isAdmin } from '../config/index.mjs'
import { log } from '../middleware/logger.mjs'
import { parseImapAccountAccess } from './email.mjs'
import { ImapService } from '../services/imap.mjs'
import { isAppwriteCollectionMissing } from '../utils/appwriteErrors.mjs'
import { requireAuth } from '../middleware/auth.mjs'
const router = express.Router()
@@ -328,18 +349,31 @@ router.delete('/account/delete',
/**
* GET /api/referrals/code
* Get or create referral code for user
* (Also registered on app in index.mjs before router mount.)
*/
router.get('/referrals/code',
requireAuth,
asyncHandler(async (req, res) => {
const userId = req.appwriteUser.id
const referral = await referrals.getOrCreateCode(userId)
respond.success(res, {
referralCode: referral.referralCode,
referralCount: referral.referralCount || 0,
export async function handleGetReferralCode(req, res) {
const userId = req.appwriteUser.id
try {
const result = await referrals.getOrCreateCode(userId)
if (!result) {
return respond.success(res, { referralCode: null, referralCount: 0 })
}
return respond.success(res, {
referralCode: result.referralCode,
referralCount: result.referralCount || 0,
})
})
)
} catch (err) {
if (isAppwriteCollectionMissing(err)) {
return respond.success(res, {
referralCode: null,
referralCount: 0,
})
}
throw err
}
}
router.get('/referrals/code', requireAuth, asyncHandler(handleGetReferralCode))
/**
* POST /api/referrals/track
@@ -383,4 +417,105 @@ router.post('/referrals/track',
})
)
async function resolveUserIdByEmail(email) {
const normalized = String(email).trim().toLowerCase()
const c = new Client()
.setEndpoint(config.appwrite.endpoint)
.setProject(config.appwrite.projectId)
.setKey(config.appwrite.apiKey)
const users = new Users(c)
try {
const res = await users.list([AppwriteQuery.equal('email', normalized)])
const uid = res.users?.[0]?.$id
if (uid) return uid
} catch (e) {
log.warn('resolveUserIdByEmail: Users.list failed', { email: normalized, error: e.message })
}
const acc = await db.findOne(Collections.EMAIL_ACCOUNTS, [Query.equal('email', normalized)])
return acc?.userId || null
}
/**
* POST /api/admin/reset-user-sort-data
* Admin only: clear sort-related data for a user (by email).
*/
router.post(
'/admin/reset-user-sort-data',
requireAuth,
validate({
body: {
email: [rules.required('email'), rules.email()],
},
}),
asyncHandler(async (req, res) => {
if (!isAdmin(req.appwriteUser?.email)) {
throw new AuthorizationError('Admin access required')
}
const targetEmail = String(req.body.email).trim().toLowerCase()
const targetUserId = await resolveUserIdByEmail(targetEmail)
if (!targetUserId) {
throw new NotFoundError('User for this email')
}
const statsDoc = await emailStats.getByUser(targetUserId)
let statsDeleted = 0
if (statsDoc?.$id) {
await db.delete(Collections.EMAIL_STATS, statsDoc.$id)
statsDeleted = 1
}
const digestsDeleted = await deleteAllDocumentsForUser(Collections.EMAIL_DIGESTS, targetUserId)
const usageDeleted = await deleteAllDocumentsForUser(Collections.EMAIL_USAGE, targetUserId)
await onboardingState.resetToInitial(targetUserId)
let imapCleared = 0
const imapAccounts = await db.list(Collections.EMAIL_ACCOUNTS, [
Query.equal('userId', targetUserId),
Query.equal('provider', 'imap'),
])
for (const acc of imapAccounts) {
if (!acc.accessToken) continue
try {
const cfg = parseImapAccountAccess(acc)
if (!cfg.password) continue
const imap = new ImapService({
host: cfg.host,
port: cfg.port,
secure: cfg.secure,
user: acc.email,
password: cfg.password,
})
const n = await imap.removeAllSortedFlags()
imapCleared += n
} catch (e) {
log.warn('reset-user-sort-data: IMAP flags failed for account', {
accountId: acc.$id,
error: e.message,
})
}
}
log.info('Admin reset-user-sort-data', {
admin: req.appwriteUser.email,
targetEmail,
targetUserId,
statsDeleted,
digestsDeleted,
usageDeleted,
imapCleared,
})
respond.success(res, {
reset: true,
deleted: {
stats: statsDeleted,
digests: digestsDeleted,
usage: usageDeleted,
},
imapCleared,
})
})
)
export default router