From 9aa3b9b25f560bc9d72500a2bd19709bb4ee70b5 Mon Sep 17 00:00:00 2001 From: Arjuna Date: Tue, 13 Jan 2026 17:10:25 +0700 Subject: [PATCH 1/4] feat: seaweedfs (bucket) docker --- docker-compose.dev.yml | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index b1afa5a..93eca5e 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -39,9 +39,22 @@ services: networks: - erp_network_dev + seaweedfs: + image: chrislusf/seaweedfs + container_name: cl_bucket_dev + restart: always + ports: + - "8333:8333" + - "8888:8888" + command: "server -s3 -filer -s3.port=8333 -filer.port=8888 -master.volumeSizeLimitMB=1024" + volumes: + - seaweed_data_dev:/data + networks: + - erp_network_dev + volumes: pg_data_dev: - + seaweed_data_dev: networks: erp_network_dev: driver: bridge \ No newline at end of file From 687336a011efcb7a2adb41e28af4fe17aef5c573 Mon Sep 17 00:00:00 2001 From: Arjuna Date: Wed, 14 Jan 2026 18:50:00 +0700 Subject: [PATCH 2/4] feat: Storage bucket connected --- Backend/database/bucket.go | 25 +++++++++++++++++++++++++ Backend/go.mod | 12 ++++++++++++ Backend/go.sum | 23 +++++++++++++++++++++++ Backend/main.go | 1 + 4 files changed, 61 insertions(+) create mode 100644 Backend/database/bucket.go diff --git a/Backend/database/bucket.go b/Backend/database/bucket.go new file mode 100644 index 0000000..796afb4 --- /dev/null +++ b/Backend/database/bucket.go @@ -0,0 +1,25 @@ +package database + +import ( + "log" + + "github.com/minio/minio-go/v7" + "github.com/minio/minio-go/v7/pkg/credentials" +) + +func InitBucket() *minio.Client { + endpoint := "127.0.0.1:8333" + accessKeyID := "" + secretAccessKey := "" + useSSL := false + + minioClient, err := minio.New(endpoint, &minio.Options{ + Creds: credentials.NewStaticV4(accessKeyID, secretAccessKey, ""), + Secure: useSSL, + }) + if err != nil { + log.Fatalln("Failed to initialize minio client:", err) + } + log.Println("Successfully connected to Minio") + return minioClient +} diff --git a/Backend/go.mod b/Backend/go.mod index a91da1c..e0dba23 100644 --- a/Backend/go.mod +++ b/Backend/go.mod @@ -15,14 +15,17 @@ require ( github.com/bytedance/sonic v1.14.2 // indirect github.com/bytedance/sonic/loader v0.4.0 // indirect github.com/cloudwego/base64x v0.1.6 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect github.com/gabriel-vasile/mimetype v1.4.12 // indirect github.com/gin-contrib/sse v1.1.0 // indirect + github.com/go-ini/ini v1.67.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.30.1 // indirect github.com/goccy/go-json v0.10.5 // indirect github.com/goccy/go-yaml v1.19.1 // indirect github.com/golang-jwt/jwt/v5 v5.3.0 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/pgx/v5 v5.7.6 // indirect @@ -30,14 +33,22 @@ require ( github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/compress v1.18.0 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect + github.com/klauspost/crc32 v1.3.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/minio/crc64nvme v1.1.0 // indirect + github.com/minio/md5-simd v1.1.2 // indirect + github.com/minio/minio-go/v7 v7.0.97 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/philhofer/fwd v1.2.0 // indirect github.com/quic-go/qpack v0.6.0 // indirect github.com/quic-go/quic-go v0.58.0 // indirect + github.com/rs/xid v1.6.0 // indirect + github.com/tinylib/msgp v1.3.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.3.1 // indirect go.uber.org/mock v0.6.0 // indirect @@ -48,4 +59,5 @@ require ( golang.org/x/sys v0.39.0 // indirect golang.org/x/text v0.32.0 // indirect google.golang.org/protobuf v1.36.11 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/Backend/go.sum b/Backend/go.sum index 83424c0..cee7637 100644 --- a/Backend/go.sum +++ b/Backend/go.sum @@ -9,6 +9,8 @@ github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gE github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw= github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/gin-contrib/cors v1.7.6 h1:3gQ8GMzs1Ylpf70y8bMw4fVpycXIeX1ZemuSQIsnQQY= @@ -17,6 +19,8 @@ github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk= github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls= +github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= +github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= @@ -34,6 +38,8 @@ github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArs github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= @@ -50,12 +56,23 @@ github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/klauspost/crc32 v1.3.0 h1:sSmTt3gUt81RP655XGZPElI0PelVTZ6YwCRnPSupoFM= +github.com/klauspost/crc32 v1.3.0/go.mod h1:D7kQaZhnkX/Y0tstFGf8VUzv2UofNGqCjnC3zdHB0Hw= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/minio/crc64nvme v1.1.0 h1:e/tAguZ+4cw32D+IO/8GSf5UVr9y+3eJcxZI2WOO/7Q= +github.com/minio/crc64nvme v1.1.0/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg= +github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34= +github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM= +github.com/minio/minio-go/v7 v7.0.97 h1:lqhREPyfgHTB/ciX8k2r8k0D93WaFqxbJX36UZq5occ= +github.com/minio/minio-go/v7 v7.0.97/go.mod h1:re5VXuo0pwEtoNLsNuSr0RrLfT/MBtohwdaSmPPSRSk= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -63,12 +80,16 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM= +github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII= github.com/quic-go/quic-go v0.58.0 h1:ggY2pvZaVdB9EyojxL1p+5mptkuHyX5MOSv4dgWF4Ug= github.com/quic-go/quic-go v0.58.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU= +github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= +github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -81,6 +102,8 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/tinylib/msgp v1.3.0 h1:ULuf7GPooDaIlbyvgAxBV/FI7ynli6LZ1/nVUNu+0ww= +github.com/tinylib/msgp v1.3.0/go.mod h1:ykjzy2wzgrlvpDCRc4LA8UXy6D8bzMSuAF3WD57Gok0= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY= diff --git a/Backend/main.go b/Backend/main.go index 87019e8..76e4de8 100644 --- a/Backend/main.go +++ b/Backend/main.go @@ -15,6 +15,7 @@ import ( func main() { database.ConnectDB() + database.InitBucket() err := database.DB.AutoMigrate( &profile.User{}, From bae80b96c8ff87476119b2562cc63d88487268d1 Mon Sep 17 00:00:00 2001 From: Arjuna Date: Thu, 15 Jan 2026 13:39:32 +0700 Subject: [PATCH 3/4] feat: Storage bucket added --- Backend/database/bucket.go | 45 +++++++++++++++ Backend/internal/profile/handler.go | 33 +++++++++++ Backend/internal/profile/modelDB.go | 1 + Backend/internal/profile/router.go | 12 +++- .../profile/services/profileService.ts | 13 ++++- Frontend/project-CL/src/pages/profile.tsx | 57 +++++++++++++++++-- 6 files changed, 152 insertions(+), 9 deletions(-) diff --git a/Backend/database/bucket.go b/Backend/database/bucket.go index 796afb4..e389b31 100644 --- a/Backend/database/bucket.go +++ b/Backend/database/bucket.go @@ -1,7 +1,11 @@ package database import ( + "context" + "fmt" "log" + "mime/multipart" + "time" "github.com/minio/minio-go/v7" "github.com/minio/minio-go/v7/pkg/credentials" @@ -23,3 +27,44 @@ func InitBucket() *minio.Client { log.Println("Successfully connected to Minio") return minioClient } + +func UploadFile(client *minio.Client, file *multipart.FileHeader) (string, error) { + ctx := context.Background() + bucketName := "user-profile" + + src, err := file.Open() + if err != nil { + return "", err + } + defer func(src multipart.File) { + err := src.Close() + if err != nil { + + } + }(src) + + exists, err := client.BucketExists(ctx, bucketName) + if err != nil { + return "", err + } + if !exists { + err := client.MakeBucket(ctx, bucketName, minio.MakeBucketOptions{}) + if err != nil { + return "", fmt.Errorf("failed to create bucket %s: %v", bucketName, err) + } + log.Println("Successfully created bucket:", bucketName) + } + + objectName := fmt.Sprintf("file_%d_%s", time.Now().Unix(), file.Filename) + contentType := file.Header.Get("Content-Type") + + info, err := client.PutObject(ctx, bucketName, objectName, src, file.Size, minio.PutObjectOptions{ + ContentType: contentType, + }) + if err != nil { + return "", err + } + log.Println("Successfully uploaded file:", info) + fileURL := fmt.Sprintf("http://localhost:8333/%s/%s", bucketName, objectName) + return fileURL, nil +} diff --git a/Backend/internal/profile/handler.go b/Backend/internal/profile/handler.go index 48b727e..2298dbe 100644 --- a/Backend/internal/profile/handler.go +++ b/Backend/internal/profile/handler.go @@ -4,8 +4,17 @@ import ( "personal-erp-backend/database" "github.com/gin-gonic/gin" + "github.com/minio/minio-go/v7" ) +type UploadHandler struct { + minioClient *minio.Client +} + +func NewUploadHandler(client *minio.Client) *UploadHandler { + return &UploadHandler{minioClient: client} +} + func GetUser(c *gin.Context) { var user User userID, _ := c.Get("userID") @@ -33,3 +42,27 @@ func UpdateUser(c *gin.Context) { } c.JSON(200, gin.H{"Success": user}) } + +func (h *UploadHandler) UploadAvatar(c *gin.Context) { + file, err := c.FormFile("avatar") + if err != nil { + c.JSON(400, gin.H{"message": err}) + return + } + fileURL, err := database.UploadFile(h.minioClient, file) + if err != nil { + c.JSON(400, gin.H{"message": err}) + return + } + var user User + userID, _ := c.Get("userID") + if err := database.DB.Where("id = ?", userID).First(&user).Error; err != nil { + c.JSON(404, gin.H{"message": err}) + return + } + if err := database.DB.Model(&user).Update("profile_url", fileURL).Error; err != nil { + c.JSON(404, gin.H{"message": err}) + return + } + c.JSON(200, gin.H{"Success": fileURL}) +} diff --git a/Backend/internal/profile/modelDB.go b/Backend/internal/profile/modelDB.go index bf8b0ce..8aef97f 100644 --- a/Backend/internal/profile/modelDB.go +++ b/Backend/internal/profile/modelDB.go @@ -10,4 +10,5 @@ type User struct { PhoneNumber string `gorm:"size:255" json:"phone_number"` Country string `gorm:"size:255" json:"country"` TeleUsername string `gorm:"size:255" json:"tele_username"` + ProfileURL string `json:"profile_url"` } diff --git a/Backend/internal/profile/router.go b/Backend/internal/profile/router.go index 3e7ae6b..76478b4 100644 --- a/Backend/internal/profile/router.go +++ b/Backend/internal/profile/router.go @@ -1,11 +1,19 @@ package profile -import "github.com/gin-gonic/gin" +import ( + "personal-erp-backend/database" + + "github.com/gin-gonic/gin" +) func RegisterRouter(r *gin.RouterGroup) { + minioClient := database.InitBucket() + uploadHandler := NewUploadHandler(minioClient) + profile := r.Group("/profile") { profile.GET("/user", GetUser) - profile.POST("/user", UpdateUser) + profile.PUT("/user", UpdateUser) + profile.POST("/upload/avatar", uploadHandler.UploadAvatar) } } diff --git a/Frontend/project-CL/src/features/profile/services/profileService.ts b/Frontend/project-CL/src/features/profile/services/profileService.ts index 1851dc2..a2ffcba 100644 --- a/Frontend/project-CL/src/features/profile/services/profileService.ts +++ b/Frontend/project-CL/src/features/profile/services/profileService.ts @@ -9,6 +9,7 @@ export interface UserProfile { phone_number: string; country: string; tele_username: string; + profile_url?: string; } export const profileService = { @@ -17,7 +18,17 @@ export const profileService = { return response.data.Success; }, updateUser: async (data: Partial) => { - const response = await api.post<{ Success: UserProfile }>('/profile/user', data); + const response = await api.put<{ Success: UserProfile }>('/profile/user', data); + return response.data.Success; + }, + uploadAvatar: async (file: File) => { + const formData = new FormData(); + formData.append('avatar', file); + const response = await api.post<{ Success: string }>('/profile/upload/avatar', formData, { + headers: { + 'Content-Type': 'multipart/form-data', + }, + }); return response.data.Success; } }; diff --git a/Frontend/project-CL/src/pages/profile.tsx b/Frontend/project-CL/src/pages/profile.tsx index a349c0b..ab66671 100644 --- a/Frontend/project-CL/src/pages/profile.tsx +++ b/Frontend/project-CL/src/pages/profile.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect } from 'react'; +import { useState, useEffect, useRef } from 'react'; import { User, Mail, Shield, Camera, Save, Phone, MapPin, Send } from 'lucide-react'; import Card from '../components/Card'; import Input from '../components/Input'; @@ -21,7 +21,9 @@ export default function Profile() { currentPassword: '', newPassword: '', confirmPassword: '', + profile_url: '', }); + const fileInputRef = useRef(null); useEffect(() => { const fetchUser = async () => { @@ -36,6 +38,7 @@ export default function Profile() { phone_number: user.phone_number || '', country: user.country || '', tele_username: user.tele_username || '', + profile_url: user.profile_url || '', })); } catch (error) { console.error("Failed to fetch user:", error); @@ -69,6 +72,30 @@ export default function Profile() { } }; + const handleImageClick = () => { + fileInputRef.current?.click(); + }; + + const handleImageChange = async (e: React.ChangeEvent) => { + const file = e.target.files?.[0]; + if (!file) return; + + // Optional: specific max size check or type check + if (file.size > 5 * 1024 * 1024) { // 5MB limit example + alert("File size too large (max 5MB)"); + return; + } + + try { + // Optimistic UI update or loading state could go here + const url = await profileService.uploadAvatar(file); + setFormData(prev => ({ ...prev, profile_url: url })); + } catch (error) { + console.error("Failed to upload avatar:", error); + alert("Failed to upload avatar"); + } + }; + return (
{/* Header */} @@ -81,11 +108,29 @@ export default function Profile() { {/* Left Column - Profile Card */}
-
-
- - {formData.username.charAt(0).toUpperCase()} - +
+ +
+ {formData.profile_url ? ( + Profile + ) : ( + + {formData.username.charAt(0).toUpperCase()} + + )}
From 5d8794b4409ec3b83eab82d887e08aa15f65a6af Mon Sep 17 00:00:00 2001 From: Arjuna Date: Thu, 15 Jan 2026 20:04:38 +0700 Subject: [PATCH 4/4] feat: Bucket safety config added --- .github/workflows/deploy.yml | 10 ++++++++++ .gitignore | 3 ++- Backend/database/bucket.go | 13 +++++++++---- docker-compose.dev.yml | 7 ++++--- docker-compose.prod.yml | 14 ++++++++++++++ seaweedfs_config/s3_config.template.json | 14 ++++++++++++++ 6 files changed, 53 insertions(+), 8 deletions(-) create mode 100644 seaweedfs_config/s3_config.template.json diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 67b9d38..b6aff13 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -27,6 +27,16 @@ jobs: echo "${{ secrets.VITE_ENV_FILE }}" > .env.production cat .env.production + - name: Create seaweedfs config + env: + BUCKET_ACCESS_KEY: ${{ secrets.BUCKET_ACCESS_KEY }} + BUCKET_SECRET_KEY: ${{ secrets.BUCKET_SECRET_KEY }} + run: | + echo "Generating seaweedfs config" + mkdir -p seaweedfs_config + envsubst < seaweedfs_config/s3_config.template.json > seaweedfs_config/s3_config.json + echo "Seaweedfs config generated" + - name: Restart docker run: | echo "Restarting docker" diff --git a/.gitignore b/.gitignore index 42bb985..a683cc1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .env* .idea -docker-compose.override.yml \ No newline at end of file +docker-compose.override.yml +seaweedfs_config/s3_config.json \ No newline at end of file diff --git a/Backend/database/bucket.go b/Backend/database/bucket.go index e389b31..3bf5f63 100644 --- a/Backend/database/bucket.go +++ b/Backend/database/bucket.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "mime/multipart" + "os" "time" "github.com/minio/minio-go/v7" @@ -12,9 +13,9 @@ import ( ) func InitBucket() *minio.Client { - endpoint := "127.0.0.1:8333" - accessKeyID := "" - secretAccessKey := "" + endpoint := os.Getenv("BUCKET_ENDPOINT") + accessKeyID := os.Getenv("BUCKET_ACCESS_KEY") + secretAccessKey := os.Getenv("BUCKET_SECRET_KEY") useSSL := false minioClient, err := minio.New(endpoint, &minio.Options{ @@ -31,6 +32,10 @@ func InitBucket() *minio.Client { func UploadFile(client *minio.Client, file *multipart.FileHeader) (string, error) { ctx := context.Background() bucketName := "user-profile" + publicDomain := os.Getenv("BUCKET_PUBLIC_DOMAIN") + if publicDomain == "" { + publicDomain = "http://localhost:8334" + } src, err := file.Open() if err != nil { @@ -65,6 +70,6 @@ func UploadFile(client *minio.Client, file *multipart.FileHeader) (string, error return "", err } log.Println("Successfully uploaded file:", info) - fileURL := fmt.Sprintf("http://localhost:8333/%s/%s", bucketName, objectName) + fileURL := fmt.Sprintf("%s/%s/%s", publicDomain, bucketName, objectName) return fileURL, nil } diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 93eca5e..d074e6f 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -44,11 +44,12 @@ services: container_name: cl_bucket_dev restart: always ports: - - "8333:8333" - - "8888:8888" - command: "server -s3 -filer -s3.port=8333 -filer.port=8888 -master.volumeSizeLimitMB=1024" + - "8334:8333" + - "8889:8888" + command: "server -s3 -filer -s3.port=8333 -filer.port=8888 -master.volumeSizeLimitMB=1024 -volume.max=100 -s3.config=/etc/seaweedfs/s3_config.json" volumes: - seaweed_data_dev:/data + - ./seaweedfs_config/s3_config.json:/etc/seaweedfs/s3_config.json networks: - erp_network_dev diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index fa23fb5..d7eaac4 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -41,8 +41,22 @@ services: networks: - erp_network_prod + seaweedfs: + image: chrislusf/seaweedfs + container_name: cl_bucket_production + restart: always + ports: + - "8333:8333" + - "8888:8888" + command: "server -s3 -filer -s3.port=8333 -filer.port=8888 -master.volumeSizeLimitMB=1024 volume.max=100" + volumes: + - seaweed_data_prod:/data + networks: + - erp_network_prod + volumes: pg_data_prod: + seaweed_data_prod: networks: erp_network_prod: diff --git a/seaweedfs_config/s3_config.template.json b/seaweedfs_config/s3_config.template.json new file mode 100644 index 0000000..7694a88 --- /dev/null +++ b/seaweedfs_config/s3_config.template.json @@ -0,0 +1,14 @@ +{ + "identities": [ + { + "name": "Core-Life-Bucket", + "credentials": [ + { + "accessKey": "$BUCKET_ACCESS_KEY", + "secretKey": "$BUCKET_SECRET_KEY" + } + ], + "actions": [ "Read", "Write", "List", "Tagging", "Admin"] + } + ] +}