hzgjuigik
This commit is contained in:
2026-01-27 21:06:48 +01:00
parent 18c11d27bc
commit 6da8ce1cbd
51 changed files with 6208 additions and 974 deletions

View File

@@ -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,