mirror of
https://github.com/marcogll/TaxHacker_s23.git
synced 2026-01-13 13:25:18 +00:00
feat: storage and token limiting
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
import { ActionState } from "@/lib/actions"
|
||||
import { getCurrentUser } from "@/lib/auth"
|
||||
import { getDirectorySize, getUserUploadsDirectory, unsortedFilePath } from "@/lib/files"
|
||||
import { getDirectorySize, getUserUploadsDirectory, isEnoughStorageToUploadFile, unsortedFilePath } from "@/lib/files"
|
||||
import { createFile } from "@/models/files"
|
||||
import { updateUser } from "@/models/users"
|
||||
import { randomUUID } from "crypto"
|
||||
@@ -24,6 +24,10 @@ export async function uploadFilesAction(formData: FormData): Promise<ActionState
|
||||
return { success: false, error: "Invalid file" }
|
||||
}
|
||||
|
||||
if (!isEnoughStorageToUploadFile(user, file.size)) {
|
||||
return { success: false, error: `Insufficient storage to upload this file: ${file.name}` }
|
||||
}
|
||||
|
||||
// Save file to filesystem
|
||||
const fileUuid = randomUUID()
|
||||
const relativeFilePath = await unsortedFilePath(fileUuid, file.name)
|
||||
|
||||
@@ -38,6 +38,8 @@ export default async function RootLayout({ children }: { children: React.ReactNo
|
||||
email: user.email,
|
||||
avatar: user.avatar || undefined,
|
||||
storageUsed: user.storageUsed || 0,
|
||||
storageLimit: user.storageLimit || -1,
|
||||
tokenBalance: user.tokenBalance || 0,
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import ProfileSettingsForm from "@/components/settings/profile-settings-form copy"
|
||||
import ProfileSettingsForm from "@/components/settings/profile-settings-form"
|
||||
import { getCurrentUser } from "@/lib/auth"
|
||||
|
||||
export default async function ProfileSettingsPage() {
|
||||
|
||||
@@ -3,7 +3,12 @@
|
||||
import { transactionFormSchema } from "@/forms/transactions"
|
||||
import { ActionState } from "@/lib/actions"
|
||||
import { getCurrentUser } from "@/lib/auth"
|
||||
import { getDirectorySize, getTransactionFileUploadPath, getUserUploadsDirectory } from "@/lib/files"
|
||||
import {
|
||||
getDirectorySize,
|
||||
getTransactionFileUploadPath,
|
||||
getUserUploadsDirectory,
|
||||
isEnoughStorageToUploadFile,
|
||||
} from "@/lib/files"
|
||||
import { updateField } from "@/models/fields"
|
||||
import { createFile, deleteFile } from "@/models/files"
|
||||
import {
|
||||
@@ -133,6 +138,11 @@ export async function uploadTransactionFilesAction(formData: FormData): Promise<
|
||||
|
||||
const userUploadsDirectory = await getUserUploadsDirectory(user)
|
||||
|
||||
const totalFileSize = files.reduce((acc, file) => acc + file.size, 0)
|
||||
if (!isEnoughStorageToUploadFile(user, totalFileSize)) {
|
||||
return { success: false, error: `Insufficient storage to upload new files` }
|
||||
}
|
||||
|
||||
const fileRecords = await Promise.all(
|
||||
files.map(async (file) => {
|
||||
const fileUuid = randomUUID()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use server"
|
||||
|
||||
import { analyzeTransaction } from "@/ai/analyze"
|
||||
import { AnalysisResult, analyzeTransaction } from "@/ai/analyze"
|
||||
import { AnalyzeAttachment, loadAttachmentsForAI } from "@/ai/attachments"
|
||||
import { buildLLMPrompt } from "@/ai/prompt"
|
||||
import { fieldsToJsonSchema } from "@/ai/schema"
|
||||
@@ -12,6 +12,7 @@ import { getTransactionFileUploadPath, getUserUploadsDirectory } from "@/lib/fil
|
||||
import { DEFAULT_PROMPT_ANALYSE_NEW_FILE } from "@/models/defaults"
|
||||
import { deleteFile, getFileById, updateFile } from "@/models/files"
|
||||
import { createTransaction, updateTransactionFiles } from "@/models/transactions"
|
||||
import { updateUser } from "@/models/users"
|
||||
import { Category, Field, File, Project, Transaction } from "@prisma/client"
|
||||
import { mkdir, rename } from "fs/promises"
|
||||
import { revalidatePath } from "next/cache"
|
||||
@@ -23,7 +24,7 @@ export async function analyzeFileAction(
|
||||
fields: Field[],
|
||||
categories: Category[],
|
||||
projects: Project[]
|
||||
): Promise<ActionState<Record<string, string>>> {
|
||||
): Promise<ActionState<AnalysisResult>> {
|
||||
const user = await getCurrentUser()
|
||||
|
||||
if (!file || file.userId !== user.id) {
|
||||
@@ -35,6 +36,10 @@ export async function analyzeFileAction(
|
||||
return { success: false, error: "OpenAI API key is not set" }
|
||||
}
|
||||
|
||||
if (!config.selfHosted.isEnabled && user.tokenBalance < 0) {
|
||||
return { success: false, error: "You used all your AI tokens, please upgrade your account" }
|
||||
}
|
||||
|
||||
let attachments: AnalyzeAttachment[] = []
|
||||
try {
|
||||
attachments = await loadAttachmentsForAI(user, file)
|
||||
@@ -56,6 +61,10 @@ export async function analyzeFileAction(
|
||||
|
||||
console.log("Analysis results:", results)
|
||||
|
||||
if (results.data?.tokensUsed && results.data.tokensUsed > 0) {
|
||||
await updateUser(user.id, { tokenBalance: { decrement: results.data.tokensUsed } })
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
"use client"
|
||||
|
||||
import { Button } from "@/components/ui/button"
|
||||
import * as Sentry from "@sentry/nextjs"
|
||||
import NextError from "next/error"
|
||||
import { Angry } from "lucide-react"
|
||||
import Link from "next/link"
|
||||
import { useEffect } from "react"
|
||||
|
||||
export default function GlobalError({ error }: { error: Error }) {
|
||||
@@ -12,11 +14,20 @@ export default function GlobalError({ error }: { error: Error }) {
|
||||
return (
|
||||
<html>
|
||||
<body>
|
||||
{/* `NextError` is the default Next.js error page component. Its type
|
||||
definition requires a `statusCode` prop. However, since the App Router
|
||||
does not expose status codes for errors, we simply pass 0 to render a
|
||||
generic error message. */}
|
||||
<NextError statusCode={0} />
|
||||
<div className="min-h-screen flex flex-col items-center justify-center bg-background p-4">
|
||||
<div className="text-center space-y-4">
|
||||
<Angry className="w-24 h-24 text-destructive mx-auto" />
|
||||
<h1 className="text-4xl font-bold text-foreground">Oops! Something went wrong</h1>
|
||||
<p className="text-muted-foreground max-w-md mx-auto">
|
||||
We apologize for the inconvenience. Our team has been notified and is working to fix the issue.
|
||||
</p>
|
||||
<div className="pt-4">
|
||||
<Button asChild>
|
||||
<Link href="/">Go Home</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user