huhuih
hzgjuigik
This commit is contained in:
@@ -7,9 +7,10 @@ import express from 'express'
|
||||
import { asyncHandler, NotFoundError, ValidationError } from '../middleware/errorHandler.mjs'
|
||||
import { validate, schemas, rules } from '../middleware/validate.mjs'
|
||||
import { respond } from '../utils/response.mjs'
|
||||
import { products, questions, submissions, orders } from '../services/database.mjs'
|
||||
import { products, questions, submissions, orders, onboardingState, emailAccounts, emailStats, emailDigests, userPreferences, subscriptions, emailUsage, referrals, db, Collections, Query } from '../services/database.mjs'
|
||||
import Stripe from 'stripe'
|
||||
import { config } from '../config/index.mjs'
|
||||
import { log } from '../middleware/logger.mjs'
|
||||
|
||||
const router = express.Router()
|
||||
const stripe = new Stripe(config.stripe.secretKey)
|
||||
@@ -171,4 +172,232 @@ router.get('/config', (req, res) => {
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* GET /api/onboarding/status
|
||||
* Get current onboarding state
|
||||
*/
|
||||
router.get('/onboarding/status',
|
||||
validate({
|
||||
query: {
|
||||
userId: [rules.required('userId')],
|
||||
},
|
||||
}),
|
||||
asyncHandler(async (req, res) => {
|
||||
const { userId } = req.query
|
||||
const state = await onboardingState.getByUser(userId)
|
||||
respond.success(res, state)
|
||||
})
|
||||
)
|
||||
|
||||
/**
|
||||
* POST /api/onboarding/step
|
||||
* Update onboarding step progress
|
||||
*/
|
||||
router.post('/onboarding/step',
|
||||
validate({
|
||||
body: {
|
||||
userId: [rules.required('userId')],
|
||||
step: [rules.required('step')],
|
||||
completedSteps: [rules.isArray('completedSteps')],
|
||||
},
|
||||
}),
|
||||
asyncHandler(async (req, res) => {
|
||||
const { userId, step, completedSteps = [] } = req.body
|
||||
await onboardingState.updateStep(userId, step, completedSteps)
|
||||
respond.success(res, { step, completedSteps })
|
||||
})
|
||||
)
|
||||
|
||||
/**
|
||||
* POST /api/onboarding/skip
|
||||
* Skip onboarding
|
||||
*/
|
||||
router.post('/onboarding/skip',
|
||||
validate({
|
||||
body: {
|
||||
userId: [rules.required('userId')],
|
||||
},
|
||||
}),
|
||||
asyncHandler(async (req, res) => {
|
||||
const { userId } = req.body
|
||||
await onboardingState.skip(userId)
|
||||
respond.success(res, { skipped: true })
|
||||
})
|
||||
)
|
||||
|
||||
/**
|
||||
* POST /api/onboarding/resume
|
||||
* Resume onboarding
|
||||
*/
|
||||
router.post('/onboarding/resume',
|
||||
validate({
|
||||
body: {
|
||||
userId: [rules.required('userId')],
|
||||
},
|
||||
}),
|
||||
asyncHandler(async (req, res) => {
|
||||
const { userId } = req.body
|
||||
await onboardingState.resume(userId)
|
||||
const state = await onboardingState.getByUser(userId)
|
||||
respond.success(res, state)
|
||||
})
|
||||
)
|
||||
|
||||
/**
|
||||
* DELETE /api/account/delete
|
||||
* Delete all user data and account
|
||||
*/
|
||||
router.delete('/account/delete',
|
||||
validate({
|
||||
body: {
|
||||
userId: [rules.required('userId')],
|
||||
},
|
||||
}),
|
||||
asyncHandler(async (req, res) => {
|
||||
const { userId } = req.body
|
||||
|
||||
log.info(`Account deletion requested for user ${userId}`)
|
||||
|
||||
// Delete all user data
|
||||
try {
|
||||
// Delete email accounts
|
||||
const accounts = await emailAccounts.getByUser(userId)
|
||||
for (const account of accounts) {
|
||||
try {
|
||||
await db.delete(Collections.EMAIL_ACCOUNTS, account.$id)
|
||||
} catch (err) {
|
||||
log.warn(`Failed to delete account ${account.$id}`, { error: err.message })
|
||||
}
|
||||
}
|
||||
|
||||
// Delete stats
|
||||
const stats = await emailStats.getByUser(userId)
|
||||
if (stats) {
|
||||
try {
|
||||
await db.delete(Collections.EMAIL_STATS, stats.$id)
|
||||
} catch (err) {
|
||||
log.warn(`Failed to delete stats`, { error: err.message })
|
||||
}
|
||||
}
|
||||
|
||||
// Delete digests
|
||||
const digests = await emailDigests.getByUser(userId)
|
||||
for (const digest of digests) {
|
||||
try {
|
||||
await db.delete(Collections.EMAIL_DIGESTS, digest.$id)
|
||||
} catch (err) {
|
||||
log.warn(`Failed to delete digest ${digest.$id}`, { error: err.message })
|
||||
}
|
||||
}
|
||||
|
||||
// Delete preferences
|
||||
const prefs = await userPreferences.getByUser(userId)
|
||||
if (prefs) {
|
||||
try {
|
||||
await db.delete(Collections.USER_PREFERENCES, prefs.$id)
|
||||
} catch (err) {
|
||||
log.warn(`Failed to delete preferences`, { error: err.message })
|
||||
}
|
||||
}
|
||||
|
||||
// Delete subscription
|
||||
const subscription = await subscriptions.getByUser(userId)
|
||||
if (subscription && subscription.$id) {
|
||||
try {
|
||||
await db.delete(Collections.SUBSCRIPTIONS, subscription.$id)
|
||||
} catch (err) {
|
||||
log.warn(`Failed to delete subscription`, { error: err.message })
|
||||
}
|
||||
}
|
||||
|
||||
// Delete email usage
|
||||
const usageRecords = await db.list(Collections.EMAIL_USAGE, [Query.equal('userId', userId)])
|
||||
for (const usage of usageRecords) {
|
||||
try {
|
||||
await db.delete(Collections.EMAIL_USAGE, usage.$id)
|
||||
} catch (err) {
|
||||
log.warn(`Failed to delete usage record`, { error: err.message })
|
||||
}
|
||||
}
|
||||
|
||||
// Delete onboarding state
|
||||
const onboarding = await onboardingState.getByUser(userId)
|
||||
if (onboarding && onboarding.$id) {
|
||||
try {
|
||||
await db.delete(Collections.ONBOARDING_STATE, onboarding.$id)
|
||||
} catch (err) {
|
||||
log.warn(`Failed to delete onboarding state`, { error: err.message })
|
||||
}
|
||||
}
|
||||
|
||||
log.success(`Account deletion completed for user ${userId}`)
|
||||
respond.success(res, { success: true, message: 'All data deleted successfully' })
|
||||
} catch (err) {
|
||||
log.error('Account deletion failed', { error: err.message, userId })
|
||||
throw new ValidationError('Failed to delete account data')
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
/**
|
||||
* GET /api/referrals/code
|
||||
* Get or create referral code for user
|
||||
*/
|
||||
router.get('/referrals/code',
|
||||
validate({
|
||||
query: {
|
||||
userId: [rules.required('userId')],
|
||||
},
|
||||
}),
|
||||
asyncHandler(async (req, res) => {
|
||||
const { userId } = req.query
|
||||
const referral = await referrals.getOrCreateCode(userId)
|
||||
respond.success(res, {
|
||||
referralCode: referral.referralCode,
|
||||
referralCount: referral.referralCount || 0,
|
||||
})
|
||||
})
|
||||
)
|
||||
|
||||
/**
|
||||
* POST /api/referrals/track
|
||||
* Track a referral (when new user signs up with referral code)
|
||||
*/
|
||||
router.post('/referrals/track',
|
||||
validate({
|
||||
body: {
|
||||
userId: [rules.required('userId')],
|
||||
referralCode: [rules.required('referralCode')],
|
||||
},
|
||||
}),
|
||||
asyncHandler(async (req, res) => {
|
||||
const { userId, referralCode } = req.body
|
||||
|
||||
// Find referrer by code
|
||||
const referrer = await referrals.getByCode(referralCode)
|
||||
if (!referrer) {
|
||||
throw new NotFoundError('Referral code')
|
||||
}
|
||||
|
||||
// Don't allow self-referral
|
||||
if (referrer.userId === userId) {
|
||||
throw new ValidationError('Cannot refer yourself')
|
||||
}
|
||||
|
||||
// Update referrer's count
|
||||
await referrals.incrementCount(referrer.userId)
|
||||
|
||||
// Store referral relationship
|
||||
await referrals.getOrCreateCode(userId)
|
||||
const userReferral = await referrals.getOrCreateCode(userId)
|
||||
await db.update(Collections.REFERRALS, userReferral.$id, {
|
||||
referredBy: referrer.userId,
|
||||
})
|
||||
|
||||
log.info(`Referral tracked: ${userId} referred by ${referrer.userId} (code: ${referralCode})`)
|
||||
|
||||
respond.success(res, { success: true })
|
||||
})
|
||||
)
|
||||
|
||||
export default router
|
||||
|
||||
Reference in New Issue
Block a user