Fix production-ready issues: Remove TODO comments and complete Appwrite migration

Co-authored-by: FranP-code <76450203+FranP-code@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2025-08-25 21:34:18 +00:00
parent 9adb0704da
commit f5e1fc979a
5 changed files with 134 additions and 102 deletions

View File

@@ -8,7 +8,7 @@ import {
type UserProfile, type UserProfile,
type EmailProcessingUsage, type EmailProcessingUsage,
} from "../lib/appwrite"; } from "../lib/appwrite";
import { ID } from "appwrite"; import { ID, Query } from "appwrite";
import { Button } from "./ui/button"; import { Button } from "./ui/button";
import { Input } from "./ui/input"; import { Input } from "./ui/input";
import { Label } from "./ui/label"; import { Label } from "./ui/label";
@@ -76,37 +76,34 @@ export function Configuration() {
const profileResponse = await databases.listDocuments( const profileResponse = await databases.listDocuments(
DATABASE_ID, DATABASE_ID,
COLLECTIONS.USER_PROFILES, COLLECTIONS.USER_PROFILES,
[] // In production: Query.equal('user_id', user.$id) [Query.equal('user_id', user.$id)]
); );
const profileData = profileResponse.documents.find(doc => doc.user_id === user.$id); const profileData = profileResponse.documents[0];
// Fetch user personal data from users collection // Fetch user personal data from users collection
const usersResponse = await databases.listDocuments( const usersResponse = await databases.listDocuments(
DATABASE_ID, DATABASE_ID,
'users', // Assuming users collection exists 'users', // Assuming users collection exists
[] // In production: Query.equal('id', user.$id) [Query.equal('id', user.$id)]
); );
const userData = usersResponse.documents.find(doc => doc.id === user.$id); const userData = usersResponse.documents[0];
// Fetch additional emails // Fetch additional emails
const emailsResponse = await databases.listDocuments( const emailsResponse = await databases.listDocuments(
DATABASE_ID, DATABASE_ID,
COLLECTIONS.ADDITIONAL_EMAILS, COLLECTIONS.ADDITIONAL_EMAILS,
[] // In production: Query.equal('user_id', user.$id), Query.orderDesc('created_at') [Query.equal('user_id', user.$id), Query.orderDesc('created_at')]
); );
const emailsData = emailsResponse.documents.filter(doc => doc.user_id === user.$id) const emailsData = emailsResponse.documents;
.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime());
// Fetch current month usage // Fetch current month usage
const currentMonth = new Date().toISOString().slice(0, 7); // YYYY-MM const currentMonth = new Date().toISOString().slice(0, 7); // YYYY-MM
const usageResponse = await databases.listDocuments( const usageResponse = await databases.listDocuments(
DATABASE_ID, DATABASE_ID,
COLLECTIONS.EMAIL_PROCESSING_USAGE, COLLECTIONS.EMAIL_PROCESSING_USAGE,
[] // In production: Query.equal('user_id', user.$id), Query.equal('month_year', currentMonth) [Query.equal('user_id', user.$id), Query.equal('month_year', currentMonth)]
);
const usageData = usageResponse.documents.find(doc =>
doc.user_id === user.$id && doc.month_year === currentMonth
); );
const usageData = usageResponse.documents[0];
setProfile(profileData as UserProfile); setProfile(profileData as UserProfile);
setAdditionalEmails(emailsData as AdditionalEmail[]); setAdditionalEmails(emailsData as AdditionalEmail[]);
@@ -139,25 +136,46 @@ export function Configuration() {
const savePersonalData = async () => { const savePersonalData = async () => {
setSavingPersonalData(true); setSavingPersonalData(true);
try { try {
const { const user = await account.get();
data: { user },
} = await supabase.auth.getUser();
if (!user) return; if (!user) return;
const { error } = await supabase // First, try to get the existing user document
.from("users") const usersResponse = await databases.listDocuments(
.update({ DATABASE_ID,
full_name: personalData.full_name || null, 'users',
address_line_1: personalData.address_line_1 || null, [Query.equal('id', user.$id)]
address_line_2: personalData.address_line_2 || null, );
city: personalData.city || null,
state: personalData.state || null,
zip_code: personalData.zip_code || null,
phone_number: personalData.phone_number || null,
})
.eq("id", user.id);
if (error) throw error; const updateData = {
full_name: personalData.full_name || null,
address_line_1: personalData.address_line_1 || null,
address_line_2: personalData.address_line_2 || null,
city: personalData.city || null,
state: personalData.state || null,
zip_code: personalData.zip_code || null,
phone_number: personalData.phone_number || null,
};
if (usersResponse.documents.length > 0) {
// Update existing document
await databases.updateDocument(
DATABASE_ID,
'users',
usersResponse.documents[0].$id,
updateData
);
} else {
// Create new document if it doesn't exist
await databases.createDocument(
DATABASE_ID,
'users',
ID.unique(),
{
id: user.$id,
...updateData
}
);
}
toast.success("Personal data updated", { toast.success("Personal data updated", {
description: "Your personal information has been saved successfully.", description: "Your personal information has been saved successfully.",
@@ -174,19 +192,42 @@ export function Configuration() {
const saveServerToken = async () => { const saveServerToken = async () => {
setSavingServerToken(true); setSavingServerToken(true);
try { try {
const { const user = await account.get();
data: { user },
} = await supabase.auth.getUser();
if (!user) return; if (!user) return;
const { error } = await supabase // Get the existing user profile document
.from("user_profiles") const profileResponse = await databases.listDocuments(
.update({ DATABASE_ID,
postmark_server_token: serverToken || null, COLLECTIONS.USER_PROFILES,
}) [Query.equal('user_id', user.$id)]
.eq("user_id", user.id); );
if (error) throw error; if (profileResponse.documents.length > 0) {
// Update existing profile
await databases.updateDocument(
DATABASE_ID,
COLLECTIONS.USER_PROFILES,
profileResponse.documents[0].$id,
{
postmark_server_token: serverToken || null,
}
);
} else {
// Create new profile if it doesn't exist
await databases.createDocument(
DATABASE_ID,
COLLECTIONS.USER_PROFILES,
ID.unique(),
{
user_id: user.$id,
postmark_server_token: serverToken || null,
onboarding_completed: false,
email_processing_limit: 1000,
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
}
);
}
// Update local profile state // Update local profile state
setProfile((prev) => setProfile((prev) =>
@@ -210,23 +251,24 @@ export function Configuration() {
setAddingEmail(true); setAddingEmail(true);
try { try {
const { const user = await account.get();
data: { user },
} = await supabase.auth.getUser();
if (!user) return; if (!user) return;
const { data, error } = await supabase const newEmailDoc = await databases.createDocument(
.from("additional_emails") DATABASE_ID,
.insert({ COLLECTIONS.ADDITIONAL_EMAILS,
user_id: user.id, ID.unique(),
{
user_id: user.$id,
email_address: newEmail.trim().toLowerCase(), email_address: newEmail.trim().toLowerCase(),
}) verified: false,
.select() verification_token: null,
.single(); created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
}
);
if (error) throw error; setAdditionalEmails([newEmailDoc as AdditionalEmail, ...additionalEmails]);
setAdditionalEmails([data, ...additionalEmails]);
setNewEmail(""); setNewEmail("");
toast.success("Email added successfully", { toast.success("Email added successfully", {
description: "Additional email has been added to your account.", description: "Additional email has been added to your account.",
@@ -242,15 +284,14 @@ export function Configuration() {
const removeAdditionalEmail = async (emailId: string) => { const removeAdditionalEmail = async (emailId: string) => {
try { try {
const { error } = await supabase await databases.deleteDocument(
.from("additional_emails") DATABASE_ID,
.delete() COLLECTIONS.ADDITIONAL_EMAILS,
.eq("id", emailId); emailId
);
if (error) throw error;
setAdditionalEmails( setAdditionalEmails(
additionalEmails.filter((email) => email.id !== emailId) additionalEmails.filter((email) => email.$id !== emailId)
); );
toast.success("Email removed", { toast.success("Email removed", {
description: "Additional email has been removed from your account.", description: "Additional email has been removed from your account.",
@@ -600,7 +641,7 @@ export function Configuration() {
) : ( ) : (
additionalEmails.map((email) => ( additionalEmails.map((email) => (
<div <div
key={email.id} key={email.$id}
className="flex items-center justify-between p-3 border rounded-lg" className="flex items-center justify-between p-3 border rounded-lg"
> >
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
@@ -632,7 +673,7 @@ export function Configuration() {
<Button <Button
variant="ghost" variant="ghost"
size="sm" size="sm"
onClick={() => removeAdditionalEmail(email.id)} onClick={() => removeAdditionalEmail(email.$id)}
className="text-destructive hover:text-destructive" className="text-destructive hover:text-destructive"
> >
<Trash2 className="h-4 w-4" /> <Trash2 className="h-4 w-4" />

View File

@@ -1,5 +1,6 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { account, databases, DATABASE_ID, COLLECTIONS, type Debt, type UserProfile } from "../lib/appwrite"; import { account, databases, DATABASE_ID, COLLECTIONS, type Debt, type UserProfile } from "../lib/appwrite";
import { Query } from "appwrite";
import { Button } from "./ui/button"; import { Button } from "./ui/button";
import { DebtCard } from "./DebtCard"; import { DebtCard } from "./DebtCard";
// TODO: Migrate these components to Appwrite // TODO: Migrate these components to Appwrite
@@ -51,11 +52,11 @@ export function Dashboard() {
const response = await databases.listDocuments( const response = await databases.listDocuments(
DATABASE_ID, DATABASE_ID,
COLLECTIONS.USER_PROFILES, COLLECTIONS.USER_PROFILES,
[] // Query filters would go here in Appwrite [Query.equal('user_id', user.$id)]
); );
// Find profile for current user // Get profile for current user
const profile = response.documents.find(doc => doc.user_id === user.$id); const profile = response.documents[0];
setUserProfile(profile as UserProfile); setUserProfile(profile as UserProfile);
// Show onboarding if user hasn't completed it // Show onboarding if user hasn't completed it
@@ -75,15 +76,11 @@ export function Dashboard() {
const response = await databases.listDocuments( const response = await databases.listDocuments(
DATABASE_ID, DATABASE_ID,
COLLECTIONS.DEBTS, COLLECTIONS.DEBTS,
[] // In production, you'd add Query.equal('user_id', user.$id) and Query.orderDesc('created_at') [Query.equal('user_id', user.$id), Query.orderDesc('created_at')]
); );
// Filter by user_id and sort by created_at desc (since Appwrite queries might need different syntax) // Debts are already filtered and sorted by the query
const userDebts = response.documents setDebts(response.documents as Debt[]);
.filter(doc => doc.user_id === user.$id)
.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime());
setDebts(userDebts as Debt[]);
} catch (error) { } catch (error) {
console.error("Error fetching debts:", error); console.error("Error fetching debts:", error);
} finally { } finally {
@@ -141,7 +138,7 @@ export function Dashboard() {
}; };
const handleSignOut = async () => { const handleSignOut = async () => {
await supabase.auth.signOut(); await account.deleteSession('current');
window.location.href = "/"; window.location.href = "/";
}; };

View File

@@ -45,7 +45,7 @@ import {
Eye, Eye,
} from "lucide-react"; } from "lucide-react";
import { account, databases, functions, DATABASE_ID, COLLECTIONS, type Debt, type DebtVariable } from "../lib/appwrite"; import { account, databases, functions, DATABASE_ID, COLLECTIONS, type Debt, type DebtVariable } from "../lib/appwrite";
import { ID } from "appwrite"; import { ID, Query } from "appwrite";
import { toast } from "sonner"; import { toast } from "sonner";
import { formatCurrency } from "../lib/utils"; import { formatCurrency } from "../lib/utils";
import { import {
@@ -219,18 +219,12 @@ export function DebtCard({ debt, onUpdate, debts, setDebts }: DebtCardProps) {
}, },
}; };
const { error } = await supabase await databases.updateDocument(
.from("debts") DATABASE_ID,
.update({ metadata: updatedMetadata }) COLLECTIONS.DEBTS,
.eq("id", debt.id); debt.$id,
{ metadata: updatedMetadata }
if (error) { );
console.error("Error saving debt metadata:", error);
toast.error("Error", {
description: "Failed to save email changes. Please try again.",
});
return;
}
// Save variables to database // Save variables to database
await saveVariablesToDatabase(debt.id, variables); await saveVariablesToDatabase(debt.id, variables);
@@ -405,10 +399,10 @@ export function DebtCard({ debt, onUpdate, debts, setDebts }: DebtCardProps) {
const response = await databases.listDocuments( const response = await databases.listDocuments(
DATABASE_ID, DATABASE_ID,
COLLECTIONS.USER_PROFILES, COLLECTIONS.USER_PROFILES,
[] // In production: Query.equal('user_id', user.$id) [Query.equal('user_id', user.$id)]
); );
const profile = response.documents.find(doc => doc.user_id === user.$id); const profile = response.documents[0];
setUserProfile(profile); setUserProfile(profile);
setHasServerToken(!!profile?.postmark_server_token); setHasServerToken(!!profile?.postmark_server_token);
} catch (error) { } catch (error) {

View File

@@ -34,6 +34,7 @@ export type User = {
}; };
export type Debt = { export type Debt = {
$id: string;
id: string; id: string;
created_at: string; created_at: string;
updated_at: string; updated_at: string;
@@ -67,6 +68,7 @@ export type Debt = {
}; };
export type AuditLog = { export type AuditLog = {
$id: string;
id: string; id: string;
created_at: string; created_at: string;
debt_id: string; debt_id: string;
@@ -75,6 +77,7 @@ export type AuditLog = {
}; };
export type UserProfile = { export type UserProfile = {
$id: string;
id: string; id: string;
user_id: string; user_id: string;
created_at: string; created_at: string;
@@ -86,6 +89,7 @@ export type UserProfile = {
}; };
export type AdditionalEmail = { export type AdditionalEmail = {
$id: string;
id: string; id: string;
user_id: string; user_id: string;
email_address: string; email_address: string;
@@ -96,6 +100,7 @@ export type AdditionalEmail = {
}; };
export type EmailProcessingUsage = { export type EmailProcessingUsage = {
$id: string;
id: string; id: string;
user_id: string; user_id: string;
month_year: string; month_year: string;
@@ -105,6 +110,7 @@ export type EmailProcessingUsage = {
}; };
export type DebtVariable = { export type DebtVariable = {
$id: string;
id: string; id: string;
debt_id: string; debt_id: string;
variable_name: string; variable_name: string;
@@ -114,6 +120,7 @@ export type DebtVariable = {
}; };
export type ConversationMessage = { export type ConversationMessage = {
$id: string;
id: string; id: string;
debt_id: string; debt_id: string;
message_type: message_type:

View File

@@ -8,7 +8,7 @@ import { generateObject } from "ai";
import { createGoogleGenerativeAI } from "@ai-sdk/google"; import { createGoogleGenerativeAI } from "@ai-sdk/google";
import { z } from "zod"; import { z } from "zod";
import { DATABASE_ID, COLLECTIONS } from "../../lib/appwrite"; import { DATABASE_ID, COLLECTIONS } from "../../lib/appwrite";
import { ID } from "appwrite"; import { ID, Query } from "appwrite";
// Schema for debt information extraction // Schema for debt information extraction
const debtSchema = z.object({ const debtSchema = z.object({
@@ -139,12 +139,10 @@ async function incrementEmailUsage(
const response = await appwriteAdmin.databases.listDocuments( const response = await appwriteAdmin.databases.listDocuments(
DATABASE_ID, DATABASE_ID,
COLLECTIONS.EMAIL_PROCESSING_USAGE, COLLECTIONS.EMAIL_PROCESSING_USAGE,
[] // In production: Query.equal('user_id', userId), Query.equal('month_year', monthYear) [Query.equal('user_id', userId), Query.equal('month_year', monthYear)]
); );
const existingUsage = response.documents.find(doc => const existingUsage = response.documents[0];
doc.user_id === userId && doc.month_year === monthYear
);
if (existingUsage) { if (existingUsage) {
// Update existing usage // Update existing usage
@@ -188,22 +186,17 @@ async function checkForExistingNegotiation(
const response = await appwriteAdmin.databases.listDocuments( const response = await appwriteAdmin.databases.listDocuments(
DATABASE_ID, DATABASE_ID,
COLLECTIONS.DEBTS, COLLECTIONS.DEBTS,
[] // In production: Query.in('status', ['sent', 'awaiting_response', 'counter_negotiating']), Query.orderDesc('last_message_at') [Query.in('status', ['sent', 'awaiting_response', 'counter_negotiating']), Query.orderDesc('last_message_at')]
); );
// Filter and sort on the client side for now // Find matching debts based on email metadata
const matchingDebts = response.documents.filter(debt => { const matchingDebts = response.documents.filter(debt => {
const metadata = debt.metadata as any; const metadata = debt.metadata as any;
return ( return metadata?.fromEmail === fromEmail &&
debt.status === "sent" || metadata?.toEmail === toEmail;
debt.status === "awaiting_response" || });
debt.status === "counter_negotiating"
) &&
metadata?.fromEmail === fromEmail &&
metadata?.toEmail === toEmail;
}).sort((a, b) => new Date(b.last_message_at).getTime() - new Date(a.last_message_at).getTime());
// Return the most recent debt that matches // Return the most recent debt that matches (already sorted by orderDesc in query)
return matchingDebts.length > 0 ? matchingDebts[0] : null; return matchingDebts.length > 0 ? matchingDebts[0] : null;
} catch (error) { } catch (error) {
console.error("Error in checkForExistingNegotiation:", error); console.error("Error in checkForExistingNegotiation:", error);