mirror of
https://github.com/marcogll/TaxHacker_s23.git
synced 2026-01-13 21:35:19 +00:00
feat: calculate used storage
This commit is contained in:
@@ -45,6 +45,7 @@ export async function analyzeTransaction(
|
||||
})
|
||||
|
||||
console.log("ChatGPT response:", response.output_text)
|
||||
console.log("ChatGPT tokens used:", response.usage)
|
||||
|
||||
const result = JSON.parse(response.output_text)
|
||||
return { success: true, data: result }
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
|
||||
import { ActionState } from "@/lib/actions"
|
||||
import { getCurrentUser } from "@/lib/auth"
|
||||
import { getUserUploadsDirectory, unsortedFilePath } from "@/lib/files"
|
||||
import { getDirectorySize, getUserUploadsDirectory, unsortedFilePath } from "@/lib/files"
|
||||
import { createFile } from "@/models/files"
|
||||
import { updateUser } from "@/models/users"
|
||||
import { randomUUID } from "crypto"
|
||||
import { mkdir, writeFile } from "fs/promises"
|
||||
import { revalidatePath } from "next/cache"
|
||||
@@ -50,6 +51,10 @@ export async function uploadFilesAction(formData: FormData): Promise<ActionState
|
||||
})
|
||||
)
|
||||
|
||||
// Update user storage used
|
||||
const storageUsed = await getDirectorySize(await getUserUploadsDirectory(user))
|
||||
await updateUser(user.id, { storageUsed })
|
||||
|
||||
console.log("uploadedFiles", uploadedFiles)
|
||||
|
||||
revalidatePath("/unsorted")
|
||||
|
||||
@@ -32,18 +32,21 @@ export default async function RootLayout({ children }: { children: React.ReactNo
|
||||
const user = await getCurrentUser()
|
||||
const unsortedFilesCount = await getUnsortedFilesCount(user.id)
|
||||
|
||||
const userProfile = {
|
||||
id: user.id,
|
||||
name: user.name || "",
|
||||
email: user.email,
|
||||
avatar: user.avatar || undefined,
|
||||
storageUsed: user.storageUsed || 0,
|
||||
}
|
||||
|
||||
return (
|
||||
<NotificationProvider>
|
||||
<ScreenDropArea>
|
||||
<SidebarProvider>
|
||||
<MobileMenu unsortedFilesCount={unsortedFilesCount} />
|
||||
<AppSidebar
|
||||
profile={{
|
||||
id: user.id,
|
||||
name: user.name || "",
|
||||
email: user.email,
|
||||
avatar: user.avatar || undefined,
|
||||
}}
|
||||
profile={userProfile}
|
||||
unsortedFilesCount={unsortedFilesCount}
|
||||
isSelfHosted={config.selfHosted.isEnabled}
|
||||
/>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { transactionFormSchema } from "@/forms/transactions"
|
||||
import { ActionState } from "@/lib/actions"
|
||||
import { getCurrentUser } from "@/lib/auth"
|
||||
import { getTransactionFileUploadPath, getUserUploadsDirectory } from "@/lib/files"
|
||||
import { getDirectorySize, getTransactionFileUploadPath, getUserUploadsDirectory } from "@/lib/files"
|
||||
import { updateField } from "@/models/fields"
|
||||
import { createFile, deleteFile } from "@/models/files"
|
||||
import {
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
updateTransaction,
|
||||
updateTransactionFiles,
|
||||
} from "@/models/transactions"
|
||||
import { updateUser } from "@/models/users"
|
||||
import { Transaction } from "@prisma/client"
|
||||
import { randomUUID } from "crypto"
|
||||
import { mkdir, writeFile } from "fs/promises"
|
||||
@@ -106,6 +107,11 @@ export async function deleteTransactionFileAction(
|
||||
)
|
||||
|
||||
await deleteFile(fileId, user.id)
|
||||
|
||||
// Update user storage used
|
||||
const storageUsed = await getDirectorySize(await getUserUploadsDirectory(user))
|
||||
await updateUser(user.id, { storageUsed })
|
||||
|
||||
revalidatePath(`/transactions/${transactionId}`)
|
||||
return { success: true, data: transaction }
|
||||
}
|
||||
@@ -169,6 +175,10 @@ export async function uploadTransactionFilesAction(formData: FormData): Promise<
|
||||
: fileRecords.map((file) => file.id)
|
||||
)
|
||||
|
||||
// Update user storage used
|
||||
const storageUsed = await getDirectorySize(await getUserUploadsDirectory(user))
|
||||
await updateUser(user.id, { storageUsed })
|
||||
|
||||
revalidatePath(`/transactions/${transactionId}`)
|
||||
return { success: true }
|
||||
} catch (error) {
|
||||
|
||||
@@ -45,10 +45,6 @@ export const viewport: Viewport = {
|
||||
export default async function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
|
||||
</head>
|
||||
<body className="min-h-screen bg-white antialiased">{children}</body>
|
||||
</html>
|
||||
)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"use client"
|
||||
|
||||
import { formatBytes } from "@/lib/utils"
|
||||
import { File } from "@prisma/client"
|
||||
import Image from "next/image"
|
||||
import Link from "next/link"
|
||||
@@ -9,9 +10,7 @@ export function FilePreview({ file }: { file: File }) {
|
||||
const [isEnlarged, setIsEnlarged] = useState(false)
|
||||
|
||||
const fileSize =
|
||||
file.metadata && typeof file.metadata === "object" && "size" in file.metadata
|
||||
? Number(file.metadata.size) / 1024 / 1024
|
||||
: 0
|
||||
file.metadata && typeof file.metadata === "object" && "size" in file.metadata ? Number(file.metadata.size) : 0
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -45,7 +44,7 @@ export function FilePreview({ file }: { file: File }) {
|
||||
<strong>Uploaded:</strong> {format(file.createdAt, "MMM d, yyyy")}
|
||||
</p> */}
|
||||
<p className="text-sm">
|
||||
<strong>Size:</strong> {fileSize < 1 ? (fileSize * 1024).toFixed(2) + " KB" : fileSize.toFixed(2) + " MB"}
|
||||
<strong>Size:</strong> {formatBytes(fileSize)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -10,7 +10,8 @@ import {
|
||||
import { SidebarMenuButton } from "@/components/ui/sidebar"
|
||||
import { UserProfile } from "@/lib/auth"
|
||||
import { authClient } from "@/lib/auth-client"
|
||||
import { LogOut, MoreVertical, User } from "lucide-react"
|
||||
import { formatBytes } from "@/lib/utils"
|
||||
import { HardDrive, LogOut, MoreVertical, User } from "lucide-react"
|
||||
import Link from "next/link"
|
||||
import { redirect } from "next/navigation"
|
||||
|
||||
@@ -59,6 +60,13 @@ export default function SidebarUser({ profile, isSelfHosted }: { profile: UserPr
|
||||
Your Subscription
|
||||
</Link>
|
||||
</DropdownMenuItem> */}
|
||||
|
||||
<DropdownMenuItem asChild>
|
||||
<Link href="/settings/profile" className="flex items-center gap-2">
|
||||
<HardDrive className="h-4 w-4" />
|
||||
Storage: {profile.storageUsed ? formatBytes(profile.storageUsed) : "N/A"}
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
{!isSelfHosted && (
|
||||
<>
|
||||
|
||||
@@ -17,6 +17,7 @@ export type UserProfile = {
|
||||
name: string
|
||||
email: string
|
||||
avatar?: string
|
||||
storageUsed?: number
|
||||
}
|
||||
|
||||
export const auth = betterAuth({
|
||||
|
||||
20
lib/files.ts
20
lib/files.ts
@@ -1,5 +1,5 @@
|
||||
import { File, Transaction, User } from "@prisma/client"
|
||||
import { access, constants } from "fs/promises"
|
||||
import { access, constants, readdir, stat } from "fs/promises"
|
||||
import path from "path"
|
||||
|
||||
export const FILE_UPLOAD_PATH = path.resolve(process.env.UPLOAD_PATH || "./uploads")
|
||||
@@ -52,3 +52,21 @@ export async function fileExists(filePath: string) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export async function getDirectorySize(directoryPath: string) {
|
||||
let totalSize = 0
|
||||
async function calculateSize(dir: string) {
|
||||
const files = await readdir(dir, { withFileTypes: true })
|
||||
for (const file of files) {
|
||||
const fullPath = path.join(dir, file.name)
|
||||
if (file.isDirectory()) {
|
||||
await calculateSize(fullPath)
|
||||
} else if (file.isFile()) {
|
||||
const stats = await stat(fullPath)
|
||||
totalSize += stats.size
|
||||
}
|
||||
}
|
||||
}
|
||||
await calculateSize(directoryPath)
|
||||
return totalSize
|
||||
}
|
||||
|
||||
12
lib/utils.ts
12
lib/utils.ts
@@ -16,6 +16,18 @@ export function formatCurrency(total: number, currency: string) {
|
||||
}).format(total / 100)
|
||||
}
|
||||
|
||||
export function formatBytes(bytes: number) {
|
||||
if (bytes === 0) return "0 Bytes"
|
||||
|
||||
const sizes = ["Bytes", "KB", "MB", "GB"]
|
||||
const maxIndex = sizes.length - 1
|
||||
|
||||
const i = Math.min(Math.floor(Math.log10(bytes) / Math.log10(1024)), maxIndex)
|
||||
const value = bytes / Math.pow(1024, i)
|
||||
|
||||
return `${parseFloat(value.toFixed(2))} ${sizes[i]}`
|
||||
}
|
||||
|
||||
export function codeFromName(name: string, maxLength: number = 16) {
|
||||
const code = slugify(name, {
|
||||
replacement: "_",
|
||||
|
||||
10
prisma/migrations/20250410130313_add_storage/migration.sql
Normal file
10
prisma/migrations/20250410130313_add_storage/migration.sql
Normal file
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `image` on the `users` table. All the data in the column will be lost.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "users" DROP COLUMN "image",
|
||||
ADD COLUMN "storage_used" INTEGER DEFAULT 0,
|
||||
ADD COLUMN "token_balance" INTEGER DEFAULT 0;
|
||||
@@ -26,12 +26,11 @@ model User {
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
membershipPlan String? @map("membership_plan")
|
||||
membershipExpiresAt DateTime? @map("membership_expires_at")
|
||||
|
||||
sessions Session[]
|
||||
|
||||
emailVerified Boolean @default(false) @map("is_email_verified")
|
||||
image String?
|
||||
storageUsed Int? @default(0) @map("storage_used")
|
||||
tokenBalance Int? @default(0) @map("token_balance")
|
||||
accounts Account[]
|
||||
sessions Session[]
|
||||
|
||||
@@map("users")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user