mirror of
https://github.com/spliit-app/spliit.git
synced 2026-03-04 12:06:11 +01:00
Support for additional S3 providers (#71)
* support for other s3 providers * remove redundant route options * use type safe env * prettier
This commit is contained in:
@@ -74,6 +74,12 @@ S3_UPLOAD_BUCKET=name-of-s3-bucket
|
|||||||
S3_UPLOAD_REGION=us-east-1
|
S3_UPLOAD_REGION=us-east-1
|
||||||
```
|
```
|
||||||
|
|
||||||
|
You can also use other S3 providers by providing a custom endpoint:
|
||||||
|
|
||||||
|
```.env
|
||||||
|
S3_UPLOAD_ENDPOINT=http://localhost:9000
|
||||||
|
```
|
||||||
|
|
||||||
### Create expense from receipt
|
### Create expense from receipt
|
||||||
|
|
||||||
You can offer users to create expense by uploading a receipt. This feature relies on [OpenAI GPT-4 with Vision](https://platform.openai.com/docs/guides/vision).
|
You can offer users to create expense by uploading a receipt. This feature relies on [OpenAI GPT-4 with Vision](https://platform.openai.com/docs/guides/vision).
|
||||||
|
|||||||
@@ -1,14 +1,27 @@
|
|||||||
|
/**
|
||||||
|
* Undefined entries are not supported. Push optional patterns to this array only if defined.
|
||||||
|
* @type {import('next/dist/shared/lib/image-config').RemotePattern}
|
||||||
|
*/
|
||||||
|
const remotePatterns = []
|
||||||
|
|
||||||
|
// S3 Storage
|
||||||
|
if (process.env.S3_UPLOAD_ENDPOINT) {
|
||||||
|
// custom endpoint for providers other than AWS
|
||||||
|
const url = new URL(process.env.S3_UPLOAD_ENDPOINT);
|
||||||
|
remotePatterns.push({
|
||||||
|
hostname: url.hostname,
|
||||||
|
})
|
||||||
|
} else if (process.env.S3_UPLOAD_BUCKET && process.env.S3_UPLOAD_REGION) {
|
||||||
|
// default provider
|
||||||
|
remotePatterns.push({
|
||||||
|
hostname: `${process.env.S3_UPLOAD_BUCKET}.s3.${process.env.S3_UPLOAD_REGION}.amazonaws.com`,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/** @type {import('next').NextConfig} */
|
/** @type {import('next').NextConfig} */
|
||||||
const nextConfig = {
|
const nextConfig = {
|
||||||
images: {
|
images: {
|
||||||
remotePatterns:
|
remotePatterns
|
||||||
process.env.S3_UPLOAD_BUCKET && process.env.S3_UPLOAD_REGION
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
hostname: `${process.env.S3_UPLOAD_BUCKET}.s3.${process.env.S3_UPLOAD_REGION}.amazonaws.com`,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: [],
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { randomId } from '@/lib/api'
|
import { randomId } from '@/lib/api'
|
||||||
|
import { env } from '@/lib/env'
|
||||||
import { POST as route } from 'next-s3-upload/route'
|
import { POST as route } from 'next-s3-upload/route'
|
||||||
|
|
||||||
export const POST = route.configure({
|
export const POST = route.configure({
|
||||||
@@ -8,4 +9,7 @@ export const POST = route.configure({
|
|||||||
const random = randomId()
|
const random = randomId()
|
||||||
return `document-${timestamp}-${random}${extension.toLowerCase()}`
|
return `document-${timestamp}-${random}${extension.toLowerCase()}`
|
||||||
},
|
},
|
||||||
|
endpoint: env.S3_UPLOAD_ENDPOINT,
|
||||||
|
// forcing path style is only necessary for providers other than AWS
|
||||||
|
forcePathStyle: !!env.S3_UPLOAD_ENDPOINT,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import { useToast } from '@/components/ui/use-toast'
|
|||||||
import { randomId } from '@/lib/api'
|
import { randomId } from '@/lib/api'
|
||||||
import { ExpenseFormValues } from '@/lib/schemas'
|
import { ExpenseFormValues } from '@/lib/schemas'
|
||||||
import { Loader2, Plus, Trash, X } from 'lucide-react'
|
import { Loader2, Plus, Trash, X } from 'lucide-react'
|
||||||
import { getImageData, useS3Upload } from 'next-s3-upload'
|
import { getImageData, usePresignedUpload } from 'next-s3-upload'
|
||||||
import Image from 'next/image'
|
import Image from 'next/image'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ type Props = {
|
|||||||
|
|
||||||
export function ExpenseDocumentsInput({ documents, updateDocuments }: Props) {
|
export function ExpenseDocumentsInput({ documents, updateDocuments }: Props) {
|
||||||
const [pending, setPending] = useState(false)
|
const [pending, setPending] = useState(false)
|
||||||
const { FileInput, openFileDialog, uploadToS3 } = useS3Upload()
|
const { FileInput, openFileDialog, uploadToS3 } = usePresignedUpload() // use presigned uploads to addtionally support providers other than AWS
|
||||||
const { toast } = useToast()
|
const { toast } = useToast()
|
||||||
|
|
||||||
const handleFileChange = async (file: File) => {
|
const handleFileChange = async (file: File) => {
|
||||||
|
|||||||
@@ -17,12 +17,14 @@ const envSchema = z
|
|||||||
S3_UPLOAD_SECRET: z.string().optional(),
|
S3_UPLOAD_SECRET: z.string().optional(),
|
||||||
S3_UPLOAD_BUCKET: z.string().optional(),
|
S3_UPLOAD_BUCKET: z.string().optional(),
|
||||||
S3_UPLOAD_REGION: z.string().optional(),
|
S3_UPLOAD_REGION: z.string().optional(),
|
||||||
|
S3_UPLOAD_ENDPOINT: z.string().optional(),
|
||||||
NEXT_PUBLIC_ENABLE_RECEIPT_EXTRACT: z.coerce.boolean().default(false),
|
NEXT_PUBLIC_ENABLE_RECEIPT_EXTRACT: z.coerce.boolean().default(false),
|
||||||
OPENAI_API_KEY: z.string().optional(),
|
OPENAI_API_KEY: z.string().optional(),
|
||||||
})
|
})
|
||||||
.superRefine((env, ctx) => {
|
.superRefine((env, ctx) => {
|
||||||
if (
|
if (
|
||||||
env.NEXT_PUBLIC_ENABLE_EXPENSE_DOCUMENTS &&
|
env.NEXT_PUBLIC_ENABLE_EXPENSE_DOCUMENTS &&
|
||||||
|
// S3_UPLOAD_ENDPOINT is fully optional as it will only be used for providers other than AWS
|
||||||
(!env.S3_UPLOAD_BUCKET ||
|
(!env.S3_UPLOAD_BUCKET ||
|
||||||
!env.S3_UPLOAD_KEY ||
|
!env.S3_UPLOAD_KEY ||
|
||||||
!env.S3_UPLOAD_REGION ||
|
!env.S3_UPLOAD_REGION ||
|
||||||
|
|||||||
Reference in New Issue
Block a user