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

@@ -6,6 +6,7 @@
import { Client, Databases, Query, ID } from 'node-appwrite'
import { config, isAdmin } from '../config/index.mjs'
import { NotFoundError } from '../middleware/errorHandler.mjs'
import { log } from '../middleware/logger.mjs'
// Initialize Appwrite client
const client = new Client()
@@ -15,6 +16,19 @@ const client = new Client()
const databases = new Databases(client)
const DB_ID = config.appwrite.databaseId
const DATABASE_ID = DB_ID
/**
* Appwrite: database/collection missing (schema not provisioned yet)
*/
function isCollectionNotFound(err) {
if (!err) return false
const msg = typeof err.message === 'string' ? err.message : ''
return (
err.type === 'collection_not_found' ||
msg.includes('Collection with the requested ID')
)
}
/**
* Collection names
@@ -44,7 +58,12 @@ export const db = {
* Create a document
*/
async create(collection, data, id = ID.unique()) {
return await databases.createDocument(DB_ID, collection, id, data)
try {
return await databases.createDocument(DB_ID, collection, id, data)
} catch (err) {
if (isCollectionNotFound(err)) return null
throw err
}
},
/**
@@ -54,6 +73,9 @@ export const db = {
try {
return await databases.getDocument(DB_ID, collection, id)
} catch (error) {
if (isCollectionNotFound(error)) {
return null
}
if (error.code === 404) {
throw new NotFoundError(collection)
}
@@ -65,22 +87,43 @@ export const db = {
* Update a document
*/
async update(collection, id, data) {
return await databases.updateDocument(DB_ID, collection, id, data)
try {
return await databases.updateDocument(DB_ID, collection, id, data)
} catch (err) {
if (isCollectionNotFound(err)) return null
throw err
}
},
/**
* Delete a document
*/
async delete(collection, id) {
return await databases.deleteDocument(DB_ID, collection, id)
try {
return await databases.deleteDocument(DB_ID, collection, id)
} catch (err) {
if (isCollectionNotFound(err)) return null
throw err
}
},
/**
* List documents with optional queries
*/
async list(collection, queries = []) {
const response = await databases.listDocuments(DB_ID, collection, queries)
return response.documents
async list(collectionId, queries = []) {
try {
const response = await databases.listDocuments(
DATABASE_ID,
collectionId,
queries
)
return response.documents
} catch (err) {
if (isCollectionNotFound(err)) {
return []
}
throw err
}
},
/**
@@ -98,7 +141,8 @@ export const db = {
try {
await databases.getDocument(DB_ID, collection, id)
return true
} catch {
} catch (err) {
if (isCollectionNotFound(err)) return false
return false
}
},
@@ -107,11 +151,16 @@ export const db = {
* Count documents
*/
async count(collection, queries = []) {
const response = await databases.listDocuments(DB_ID, collection, [
...queries,
Query.limit(1),
])
return response.total
try {
const response = await databases.listDocuments(DB_ID, collection, [
...queries,
Query.limit(1),
])
return response.total
} catch (err) {
if (isCollectionNotFound(err)) return 0
throw err
}
},
}
@@ -208,21 +257,28 @@ export const emailStats = {
const stats = await this.getByUser(userId)
if (stats) {
return db.update(Collections.EMAIL_STATS, stats.$id, {
const updated = await db.update(Collections.EMAIL_STATS, stats.$id, {
totalSorted: (stats.totalSorted || 0) + (counts.total || 0),
todaySorted: (stats.todaySorted || 0) + (counts.today || 0),
weekSorted: (stats.weekSorted || 0) + (counts.week || 0),
timeSavedMinutes: (stats.timeSavedMinutes || 0) + (counts.timeSaved || 0),
})
} else {
return this.create(userId, {
totalSorted: counts.total || 0,
todaySorted: counts.today || 0,
weekSorted: counts.week || 0,
timeSavedMinutes: counts.timeSaved || 0,
categoriesJson: '{}',
})
if (updated == null && process.env.NODE_ENV === 'development') {
log.warn('emailStats.increment: update skipped (missing collection or document)', { userId })
}
return updated
}
const created = await this.create(userId, {
totalSorted: counts.total || 0,
todaySorted: counts.today || 0,
weekSorted: counts.week || 0,
timeSavedMinutes: counts.timeSaved || 0,
categoriesJson: '{}',
})
if (created == null && process.env.NODE_ENV === 'development') {
log.warn('emailStats.increment: create skipped (missing collection)', { userId })
}
return created
},
async updateCategories(userId, categories) {
@@ -276,18 +332,26 @@ export const emailUsage = {
const existing = await this.getCurrentMonth(userId)
if (existing) {
return db.update(Collections.EMAIL_USAGE, existing.$id, {
const updated = await db.update(Collections.EMAIL_USAGE, existing.$id, {
emailsProcessed: (existing.emailsProcessed || 0) + count,
lastReset: new Date().toISOString(),
})
if (updated == null && process.env.NODE_ENV === 'development') {
log.warn('emailUsage.increment: update skipped (missing collection or document)', { userId })
}
return updated
}
return db.create(Collections.EMAIL_USAGE, {
const created = await db.create(Collections.EMAIL_USAGE, {
userId,
month,
emailsProcessed: count,
lastReset: new Date().toISOString(),
})
if (created == null && process.env.NODE_ENV === 'development') {
log.warn('emailUsage.increment: create skipped (missing collection)', { userId })
}
return created
},
async getUsage(userId) {
@@ -578,6 +642,26 @@ export const onboardingState = {
last_updated: new Date().toISOString(),
})
},
/**
* Reset onboarding to initial state (does not delete the document).
*/
async resetToInitial(userId) {
const existing = await db.findOne(Collections.ONBOARDING_STATE, [
Query.equal('userId', userId),
])
const data = {
onboarding_step: 'not_started',
completed_steps_json: JSON.stringify([]),
skipped_at: null,
first_value_seen_at: null,
last_updated: new Date().toISOString(),
}
if (existing) {
return db.update(Collections.ONBOARDING_STATE, existing.$id, data)
}
return db.create(Collections.ONBOARDING_STATE, { userId, ...data })
},
}
/**
@@ -609,12 +693,17 @@ export const referrals = {
attempts++
}
return db.create(Collections.REFERRALS, {
const created = await db.create(Collections.REFERRALS, {
userId,
referralCode: uniqueCode,
referralCount: 0,
createdAt: new Date().toISOString(),
})
// Collection missing → return null safely
if (!created) return null
return created
},
async getByCode(code) {
@@ -746,6 +835,25 @@ export const emailDigests = {
},
}
/**
* Delete all documents in a collection where attribute `userId` matches (paginated batches of 100).
*/
export async function deleteAllDocumentsForUser(collection, userId) {
let deleted = 0
for (;;) {
const batch = await db.list(collection, [
Query.equal('userId', userId),
Query.limit(100),
])
if (!batch.length) break
for (const doc of batch) {
await db.delete(collection, doc.$id)
deleted++
}
}
return deleted
}
export { Query }
export default {