mirror of
https://github.com/FranP-code/inbox-negotiator.git
synced 2025-10-13 00:42:26 +00:00
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:
@@ -8,7 +8,7 @@ import {
|
||||
type UserProfile,
|
||||
type EmailProcessingUsage,
|
||||
} from "../lib/appwrite";
|
||||
import { ID } from "appwrite";
|
||||
import { ID, Query } from "appwrite";
|
||||
import { Button } from "./ui/button";
|
||||
import { Input } from "./ui/input";
|
||||
import { Label } from "./ui/label";
|
||||
@@ -76,37 +76,34 @@ export function Configuration() {
|
||||
const profileResponse = await databases.listDocuments(
|
||||
DATABASE_ID,
|
||||
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
|
||||
const usersResponse = await databases.listDocuments(
|
||||
DATABASE_ID,
|
||||
'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
|
||||
const emailsResponse = await databases.listDocuments(
|
||||
DATABASE_ID,
|
||||
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)
|
||||
.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime());
|
||||
const emailsData = emailsResponse.documents;
|
||||
|
||||
// Fetch current month usage
|
||||
const currentMonth = new Date().toISOString().slice(0, 7); // YYYY-MM
|
||||
const usageResponse = await databases.listDocuments(
|
||||
DATABASE_ID,
|
||||
COLLECTIONS.EMAIL_PROCESSING_USAGE,
|
||||
[] // In production: 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
|
||||
[Query.equal('user_id', user.$id), Query.equal('month_year', currentMonth)]
|
||||
);
|
||||
const usageData = usageResponse.documents[0];
|
||||
|
||||
setProfile(profileData as UserProfile);
|
||||
setAdditionalEmails(emailsData as AdditionalEmail[]);
|
||||
@@ -139,25 +136,46 @@ export function Configuration() {
|
||||
const savePersonalData = async () => {
|
||||
setSavingPersonalData(true);
|
||||
try {
|
||||
const {
|
||||
data: { user },
|
||||
} = await supabase.auth.getUser();
|
||||
const user = await account.get();
|
||||
if (!user) return;
|
||||
|
||||
const { error } = await supabase
|
||||
.from("users")
|
||||
.update({
|
||||
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,
|
||||
})
|
||||
.eq("id", user.id);
|
||||
// First, try to get the existing user document
|
||||
const usersResponse = await databases.listDocuments(
|
||||
DATABASE_ID,
|
||||
'users',
|
||||
[Query.equal('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", {
|
||||
description: "Your personal information has been saved successfully.",
|
||||
@@ -174,19 +192,42 @@ export function Configuration() {
|
||||
const saveServerToken = async () => {
|
||||
setSavingServerToken(true);
|
||||
try {
|
||||
const {
|
||||
data: { user },
|
||||
} = await supabase.auth.getUser();
|
||||
const user = await account.get();
|
||||
if (!user) return;
|
||||
|
||||
const { error } = await supabase
|
||||
.from("user_profiles")
|
||||
.update({
|
||||
postmark_server_token: serverToken || null,
|
||||
})
|
||||
.eq("user_id", user.id);
|
||||
// Get the existing user profile document
|
||||
const profileResponse = await databases.listDocuments(
|
||||
DATABASE_ID,
|
||||
COLLECTIONS.USER_PROFILES,
|
||||
[Query.equal('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
|
||||
setProfile((prev) =>
|
||||
@@ -210,23 +251,24 @@ export function Configuration() {
|
||||
|
||||
setAddingEmail(true);
|
||||
try {
|
||||
const {
|
||||
data: { user },
|
||||
} = await supabase.auth.getUser();
|
||||
const user = await account.get();
|
||||
if (!user) return;
|
||||
|
||||
const { data, error } = await supabase
|
||||
.from("additional_emails")
|
||||
.insert({
|
||||
user_id: user.id,
|
||||
const newEmailDoc = await databases.createDocument(
|
||||
DATABASE_ID,
|
||||
COLLECTIONS.ADDITIONAL_EMAILS,
|
||||
ID.unique(),
|
||||
{
|
||||
user_id: user.$id,
|
||||
email_address: newEmail.trim().toLowerCase(),
|
||||
})
|
||||
.select()
|
||||
.single();
|
||||
verified: false,
|
||||
verification_token: null,
|
||||
created_at: new Date().toISOString(),
|
||||
updated_at: new Date().toISOString(),
|
||||
}
|
||||
);
|
||||
|
||||
if (error) throw error;
|
||||
|
||||
setAdditionalEmails([data, ...additionalEmails]);
|
||||
setAdditionalEmails([newEmailDoc as AdditionalEmail, ...additionalEmails]);
|
||||
setNewEmail("");
|
||||
toast.success("Email added successfully", {
|
||||
description: "Additional email has been added to your account.",
|
||||
@@ -242,15 +284,14 @@ export function Configuration() {
|
||||
|
||||
const removeAdditionalEmail = async (emailId: string) => {
|
||||
try {
|
||||
const { error } = await supabase
|
||||
.from("additional_emails")
|
||||
.delete()
|
||||
.eq("id", emailId);
|
||||
|
||||
if (error) throw error;
|
||||
await databases.deleteDocument(
|
||||
DATABASE_ID,
|
||||
COLLECTIONS.ADDITIONAL_EMAILS,
|
||||
emailId
|
||||
);
|
||||
|
||||
setAdditionalEmails(
|
||||
additionalEmails.filter((email) => email.id !== emailId)
|
||||
additionalEmails.filter((email) => email.$id !== emailId)
|
||||
);
|
||||
toast.success("Email removed", {
|
||||
description: "Additional email has been removed from your account.",
|
||||
@@ -600,7 +641,7 @@ export function Configuration() {
|
||||
) : (
|
||||
additionalEmails.map((email) => (
|
||||
<div
|
||||
key={email.id}
|
||||
key={email.$id}
|
||||
className="flex items-center justify-between p-3 border rounded-lg"
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
@@ -632,7 +673,7 @@ export function Configuration() {
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => removeAdditionalEmail(email.id)}
|
||||
onClick={() => removeAdditionalEmail(email.$id)}
|
||||
className="text-destructive hover:text-destructive"
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { account, databases, DATABASE_ID, COLLECTIONS, type Debt, type UserProfile } from "../lib/appwrite";
|
||||
import { Query } from "appwrite";
|
||||
import { Button } from "./ui/button";
|
||||
import { DebtCard } from "./DebtCard";
|
||||
// TODO: Migrate these components to Appwrite
|
||||
@@ -51,11 +52,11 @@ export function Dashboard() {
|
||||
const response = await databases.listDocuments(
|
||||
DATABASE_ID,
|
||||
COLLECTIONS.USER_PROFILES,
|
||||
[] // Query filters would go here in Appwrite
|
||||
[Query.equal('user_id', user.$id)]
|
||||
);
|
||||
|
||||
// Find profile for current user
|
||||
const profile = response.documents.find(doc => doc.user_id === user.$id);
|
||||
// Get profile for current user
|
||||
const profile = response.documents[0];
|
||||
setUserProfile(profile as UserProfile);
|
||||
|
||||
// Show onboarding if user hasn't completed it
|
||||
@@ -75,15 +76,11 @@ export function Dashboard() {
|
||||
const response = await databases.listDocuments(
|
||||
DATABASE_ID,
|
||||
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)
|
||||
const userDebts = response.documents
|
||||
.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[]);
|
||||
// Debts are already filtered and sorted by the query
|
||||
setDebts(response.documents as Debt[]);
|
||||
} catch (error) {
|
||||
console.error("Error fetching debts:", error);
|
||||
} finally {
|
||||
@@ -141,7 +138,7 @@ export function Dashboard() {
|
||||
};
|
||||
|
||||
const handleSignOut = async () => {
|
||||
await supabase.auth.signOut();
|
||||
await account.deleteSession('current');
|
||||
window.location.href = "/";
|
||||
};
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ import {
|
||||
Eye,
|
||||
} from "lucide-react";
|
||||
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 { formatCurrency } from "../lib/utils";
|
||||
import {
|
||||
@@ -219,18 +219,12 @@ export function DebtCard({ debt, onUpdate, debts, setDebts }: DebtCardProps) {
|
||||
},
|
||||
};
|
||||
|
||||
const { error } = await supabase
|
||||
.from("debts")
|
||||
.update({ metadata: updatedMetadata })
|
||||
.eq("id", debt.id);
|
||||
|
||||
if (error) {
|
||||
console.error("Error saving debt metadata:", error);
|
||||
toast.error("Error", {
|
||||
description: "Failed to save email changes. Please try again.",
|
||||
});
|
||||
return;
|
||||
}
|
||||
await databases.updateDocument(
|
||||
DATABASE_ID,
|
||||
COLLECTIONS.DEBTS,
|
||||
debt.$id,
|
||||
{ metadata: updatedMetadata }
|
||||
);
|
||||
|
||||
// Save variables to database
|
||||
await saveVariablesToDatabase(debt.id, variables);
|
||||
@@ -405,10 +399,10 @@ export function DebtCard({ debt, onUpdate, debts, setDebts }: DebtCardProps) {
|
||||
const response = await databases.listDocuments(
|
||||
DATABASE_ID,
|
||||
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);
|
||||
setHasServerToken(!!profile?.postmark_server_token);
|
||||
} catch (error) {
|
||||
|
||||
@@ -34,6 +34,7 @@ export type User = {
|
||||
};
|
||||
|
||||
export type Debt = {
|
||||
$id: string;
|
||||
id: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
@@ -67,6 +68,7 @@ export type Debt = {
|
||||
};
|
||||
|
||||
export type AuditLog = {
|
||||
$id: string;
|
||||
id: string;
|
||||
created_at: string;
|
||||
debt_id: string;
|
||||
@@ -75,6 +77,7 @@ export type AuditLog = {
|
||||
};
|
||||
|
||||
export type UserProfile = {
|
||||
$id: string;
|
||||
id: string;
|
||||
user_id: string;
|
||||
created_at: string;
|
||||
@@ -86,6 +89,7 @@ export type UserProfile = {
|
||||
};
|
||||
|
||||
export type AdditionalEmail = {
|
||||
$id: string;
|
||||
id: string;
|
||||
user_id: string;
|
||||
email_address: string;
|
||||
@@ -96,6 +100,7 @@ export type AdditionalEmail = {
|
||||
};
|
||||
|
||||
export type EmailProcessingUsage = {
|
||||
$id: string;
|
||||
id: string;
|
||||
user_id: string;
|
||||
month_year: string;
|
||||
@@ -105,6 +110,7 @@ export type EmailProcessingUsage = {
|
||||
};
|
||||
|
||||
export type DebtVariable = {
|
||||
$id: string;
|
||||
id: string;
|
||||
debt_id: string;
|
||||
variable_name: string;
|
||||
@@ -114,6 +120,7 @@ export type DebtVariable = {
|
||||
};
|
||||
|
||||
export type ConversationMessage = {
|
||||
$id: string;
|
||||
id: string;
|
||||
debt_id: string;
|
||||
message_type:
|
||||
|
||||
@@ -8,7 +8,7 @@ import { generateObject } from "ai";
|
||||
import { createGoogleGenerativeAI } from "@ai-sdk/google";
|
||||
import { z } from "zod";
|
||||
import { DATABASE_ID, COLLECTIONS } from "../../lib/appwrite";
|
||||
import { ID } from "appwrite";
|
||||
import { ID, Query } from "appwrite";
|
||||
|
||||
// Schema for debt information extraction
|
||||
const debtSchema = z.object({
|
||||
@@ -139,12 +139,10 @@ async function incrementEmailUsage(
|
||||
const response = await appwriteAdmin.databases.listDocuments(
|
||||
DATABASE_ID,
|
||||
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 =>
|
||||
doc.user_id === userId && doc.month_year === monthYear
|
||||
);
|
||||
const existingUsage = response.documents[0];
|
||||
|
||||
if (existingUsage) {
|
||||
// Update existing usage
|
||||
@@ -188,22 +186,17 @@ async function checkForExistingNegotiation(
|
||||
const response = await appwriteAdmin.databases.listDocuments(
|
||||
DATABASE_ID,
|
||||
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 metadata = debt.metadata as any;
|
||||
return (
|
||||
debt.status === "sent" ||
|
||||
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 metadata?.fromEmail === fromEmail &&
|
||||
metadata?.toEmail === toEmail;
|
||||
});
|
||||
|
||||
// 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;
|
||||
} catch (error) {
|
||||
console.error("Error in checkForExistingNegotiation:", error);
|
||||
|
||||
Reference in New Issue
Block a user