huhuih
hzgjuigik
This commit is contained in:
@@ -30,6 +30,10 @@ export const Collections = {
|
||||
EMAIL_DIGESTS: 'email_digests',
|
||||
SUBSCRIPTIONS: 'subscriptions',
|
||||
USER_PREFERENCES: 'user_preferences',
|
||||
ONBOARDING_STATE: 'onboarding_state',
|
||||
EMAIL_USAGE: 'email_usage',
|
||||
REFERRALS: 'referrals',
|
||||
ANALYTICS_EVENTS: 'analytics_events',
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -251,12 +255,86 @@ export const emailStats = {
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Email usage operations
|
||||
*/
|
||||
export const emailUsage = {
|
||||
async getCurrentMonth(userId) {
|
||||
const month = new Date().toISOString().slice(0, 7) // "2026-01"
|
||||
return db.findOne(Collections.EMAIL_USAGE, [
|
||||
Query.equal('userId', userId),
|
||||
Query.equal('month', month),
|
||||
])
|
||||
},
|
||||
|
||||
async increment(userId, count) {
|
||||
const month = new Date().toISOString().slice(0, 7)
|
||||
const existing = await this.getCurrentMonth(userId)
|
||||
|
||||
if (existing) {
|
||||
return db.update(Collections.EMAIL_USAGE, existing.$id, {
|
||||
emailsProcessed: (existing.emailsProcessed || 0) + count,
|
||||
lastReset: new Date().toISOString(),
|
||||
})
|
||||
}
|
||||
|
||||
return db.create(Collections.EMAIL_USAGE, {
|
||||
userId,
|
||||
month,
|
||||
emailsProcessed: count,
|
||||
lastReset: new Date().toISOString(),
|
||||
})
|
||||
},
|
||||
|
||||
async getUsage(userId) {
|
||||
const usage = await this.getCurrentMonth(userId)
|
||||
return {
|
||||
emailsProcessed: usage?.emailsProcessed || 0,
|
||||
month: new Date().toISOString().slice(0, 7),
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscriptions operations
|
||||
*/
|
||||
export const subscriptions = {
|
||||
async getByUser(userId) {
|
||||
return db.findOne(Collections.SUBSCRIPTIONS, [Query.equal('userId', userId)])
|
||||
const subscription = await db.findOne(Collections.SUBSCRIPTIONS, [Query.equal('userId', userId)])
|
||||
|
||||
// If no subscription, user is on free tier
|
||||
if (!subscription) {
|
||||
const usage = await emailUsage.getUsage(userId)
|
||||
return {
|
||||
plan: 'free',
|
||||
status: 'active',
|
||||
isFreeTier: true,
|
||||
emailsUsedThisMonth: usage.emailsProcessed,
|
||||
emailsLimit: 500, // From config
|
||||
}
|
||||
}
|
||||
|
||||
// Check if subscription is active
|
||||
const isActive = subscription.status === 'active'
|
||||
const isFreeTier = !isActive || subscription.plan === 'free'
|
||||
|
||||
// Get usage for free tier users
|
||||
let emailsUsedThisMonth = 0
|
||||
let emailsLimit = -1 // Unlimited for paid
|
||||
|
||||
if (isFreeTier) {
|
||||
const usage = await emailUsage.getUsage(userId)
|
||||
emailsUsedThisMonth = usage.emailsProcessed
|
||||
emailsLimit = 500 // From config
|
||||
}
|
||||
|
||||
return {
|
||||
...subscription,
|
||||
plan: subscription.plan || 'free',
|
||||
isFreeTier,
|
||||
emailsUsedThisMonth,
|
||||
emailsLimit,
|
||||
}
|
||||
},
|
||||
|
||||
async getByStripeId(stripeSubscriptionId) {
|
||||
@@ -296,6 +374,27 @@ export const userPreferences = {
|
||||
categoryActions: {},
|
||||
companyLabels: [],
|
||||
autoDetectCompanies: true,
|
||||
version: 1,
|
||||
categoryAdvanced: {},
|
||||
cleanup: {
|
||||
enabled: false,
|
||||
readItems: {
|
||||
enabled: false,
|
||||
action: 'archive_read',
|
||||
gracePeriodDays: 7,
|
||||
},
|
||||
promotions: {
|
||||
enabled: false,
|
||||
matchCategoriesOrLabels: ['promotions', 'newsletters'],
|
||||
action: 'archive_read',
|
||||
deleteAfterDays: 30,
|
||||
},
|
||||
safety: {
|
||||
requireConfirmForDelete: true,
|
||||
dryRun: false,
|
||||
maxDeletesPerRun: 100,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
@@ -347,6 +446,170 @@ export const userPreferences = {
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Onboarding state operations
|
||||
*/
|
||||
export const onboardingState = {
|
||||
async getByUser(userId) {
|
||||
const state = await db.findOne(Collections.ONBOARDING_STATE, [
|
||||
Query.equal('userId', userId),
|
||||
])
|
||||
if (state?.completed_steps_json) {
|
||||
return {
|
||||
...state,
|
||||
completedSteps: JSON.parse(state.completed_steps_json),
|
||||
}
|
||||
}
|
||||
return {
|
||||
...state,
|
||||
completedSteps: [],
|
||||
onboarding_step: state?.onboarding_step || 'not_started',
|
||||
}
|
||||
},
|
||||
|
||||
async updateStep(userId, step, completedSteps = []) {
|
||||
const existing = await db.findOne(Collections.ONBOARDING_STATE, [
|
||||
Query.equal('userId', userId),
|
||||
])
|
||||
|
||||
const data = {
|
||||
onboarding_step: step,
|
||||
completed_steps_json: JSON.stringify(completedSteps),
|
||||
last_updated: new Date().toISOString(),
|
||||
}
|
||||
|
||||
if (existing) {
|
||||
return db.update(Collections.ONBOARDING_STATE, existing.$id, data)
|
||||
}
|
||||
return db.create(Collections.ONBOARDING_STATE, { userId, ...data })
|
||||
},
|
||||
|
||||
async markValueSeen(userId) {
|
||||
const existing = await db.findOne(Collections.ONBOARDING_STATE, [
|
||||
Query.equal('userId', userId),
|
||||
])
|
||||
|
||||
const data = {
|
||||
first_value_seen_at: new Date().toISOString(),
|
||||
last_updated: new Date().toISOString(),
|
||||
}
|
||||
|
||||
if (existing) {
|
||||
return db.update(Collections.ONBOARDING_STATE, existing.$id, data)
|
||||
}
|
||||
return db.create(Collections.ONBOARDING_STATE, {
|
||||
userId,
|
||||
onboarding_step: 'see_results',
|
||||
completed_steps_json: JSON.stringify(['connect', 'first_rule', 'see_results']),
|
||||
...data,
|
||||
})
|
||||
},
|
||||
|
||||
async skip(userId) {
|
||||
const existing = await db.findOne(Collections.ONBOARDING_STATE, [
|
||||
Query.equal('userId', userId),
|
||||
])
|
||||
|
||||
const data = {
|
||||
skipped_at: new Date().toISOString(),
|
||||
last_updated: new Date().toISOString(),
|
||||
}
|
||||
|
||||
if (existing) {
|
||||
return db.update(Collections.ONBOARDING_STATE, existing.$id, data)
|
||||
}
|
||||
return db.create(Collections.ONBOARDING_STATE, {
|
||||
userId,
|
||||
onboarding_step: 'not_started',
|
||||
completed_steps_json: JSON.stringify([]),
|
||||
...data,
|
||||
})
|
||||
},
|
||||
|
||||
async resume(userId) {
|
||||
const existing = await db.findOne(Collections.ONBOARDING_STATE, [
|
||||
Query.equal('userId', userId),
|
||||
])
|
||||
|
||||
if (existing) {
|
||||
return db.update(Collections.ONBOARDING_STATE, existing.$id, {
|
||||
skipped_at: null,
|
||||
last_updated: new Date().toISOString(),
|
||||
})
|
||||
}
|
||||
// If no state exists, create initial state
|
||||
return db.create(Collections.ONBOARDING_STATE, {
|
||||
userId,
|
||||
onboarding_step: 'connect',
|
||||
completed_steps_json: JSON.stringify([]),
|
||||
last_updated: new Date().toISOString(),
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Referrals operations
|
||||
*/
|
||||
export const referrals = {
|
||||
async getOrCreateCode(userId) {
|
||||
const existing = await db.findOne(Collections.REFERRALS, [
|
||||
Query.equal('userId', userId),
|
||||
])
|
||||
|
||||
if (existing) {
|
||||
return existing
|
||||
}
|
||||
|
||||
// Generate unique code: USER-ABC123
|
||||
const randomPart = Math.random().toString(36).substring(2, 8).toUpperCase()
|
||||
const code = `USER-${randomPart}`
|
||||
|
||||
// Ensure uniqueness
|
||||
let uniqueCode = code
|
||||
let attempts = 0
|
||||
while (attempts < 10) {
|
||||
const existingCode = await db.findOne(Collections.REFERRALS, [
|
||||
Query.equal('referralCode', uniqueCode),
|
||||
])
|
||||
if (!existingCode) break
|
||||
uniqueCode = `USER-${Math.random().toString(36).substring(2, 8).toUpperCase()}`
|
||||
attempts++
|
||||
}
|
||||
|
||||
return db.create(Collections.REFERRALS, {
|
||||
userId,
|
||||
referralCode: uniqueCode,
|
||||
referralCount: 0,
|
||||
createdAt: new Date().toISOString(),
|
||||
})
|
||||
},
|
||||
|
||||
async getByCode(code) {
|
||||
return db.findOne(Collections.REFERRALS, [
|
||||
Query.equal('referralCode', code),
|
||||
])
|
||||
},
|
||||
|
||||
async incrementCount(userId) {
|
||||
const referral = await db.findOne(Collections.REFERRALS, [
|
||||
Query.equal('userId', userId),
|
||||
])
|
||||
|
||||
if (referral) {
|
||||
return db.update(Collections.REFERRALS, referral.$id, {
|
||||
referralCount: (referral.referralCount || 0) + 1,
|
||||
})
|
||||
}
|
||||
return null
|
||||
},
|
||||
|
||||
async getReferrals(userId) {
|
||||
return db.list(Collections.REFERRALS, [
|
||||
Query.equal('referredBy', userId),
|
||||
])
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Orders operations
|
||||
*/
|
||||
@@ -450,6 +713,8 @@ export const emailDigests = {
|
||||
},
|
||||
}
|
||||
|
||||
export { Query }
|
||||
|
||||
export default {
|
||||
db,
|
||||
products,
|
||||
|
||||
Reference in New Issue
Block a user