SCGR commited on
Commit
7104814
·
1 Parent(s): 7882b58

upload handler

Browse files
backend/handlers/upload.go CHANGED
@@ -6,20 +6,20 @@ import (
6
  "database/sql"
7
  "encoding/hex"
8
  "io"
9
- "mime/multipart"
10
  "net/http"
11
  "time"
12
 
13
  "github.com/gin-gonic/gin"
14
  "github.com/google/uuid"
15
- "github.com/minio/minio-go/v7"
16
- "github.com/lib/pq"
17
  )
18
 
 
19
  // wire this in main.go: r.POST("/maps", deps.UploadMap)
20
  type UploadDeps struct {
21
  DB *sql.DB
22
- Storage *minio.Client
23
  Bucket string
24
  RegionOK map[string]bool // in‑memory lookup, seeded at start
25
  // same for SourceOK, CategoryOK, CountryOK
@@ -53,7 +53,12 @@ func (d *UploadDeps) UploadMap(c *gin.Context) {
53
  // repeat for source / category / countries…
54
 
55
  // ---- 3. Read file + hash it ----------------------------------------
56
- buf, _ := io.ReadAll(file)
 
 
 
 
 
57
  sha := sha256.Sum256(buf)
58
  shaHex := hex.EncodeToString(sha[:])
59
 
@@ -61,15 +66,16 @@ func (d *UploadDeps) UploadMap(c *gin.Context) {
61
  objKey := "maps/" + time.Now().Format("2006/01/02/") + shaHex + ".png"
62
 
63
  // ---- 4. Upload to object storage -----------------------------------
64
- _, err = d.Storage.PutObject(
65
- c, d.Bucket, objKey,
66
- bytes.NewReader(buf), int64(len(buf)),
67
- minio.PutObjectOptions{ContentType: fileHdr.Header.Get("Content-Type")},
68
- )
69
- if err != nil {
70
- c.JSON(http.StatusInternalServerError, gin.H{"error": "storage failed"})
71
- return
72
- }
 
73
 
74
  // ---- 5. Insert into maps -------------------------------------------
75
  mapID := uuid.New()
 
6
  "database/sql"
7
  "encoding/hex"
8
  "io"
 
9
  "net/http"
10
  "time"
11
 
12
  "github.com/gin-gonic/gin"
13
  "github.com/google/uuid"
14
+ "github.com/lib/pq"
15
+ "github.com/SCGR-1/promptaid-backend/internal/storage"
16
  )
17
 
18
+
19
  // wire this in main.go: r.POST("/maps", deps.UploadMap)
20
  type UploadDeps struct {
21
  DB *sql.DB
22
+ Storage storage.ObjectStore
23
  Bucket string
24
  RegionOK map[string]bool // in‑memory lookup, seeded at start
25
  // same for SourceOK, CategoryOK, CountryOK
 
53
  // repeat for source / category / countries…
54
 
55
  // ---- 3. Read file + hash it ----------------------------------------
56
+ var buf []byte
57
+ buf, err = io.ReadAll(file)
58
+ if err != nil {
59
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "read failed"})
60
+ return
61
+ }
62
  sha := sha256.Sum256(buf)
63
  shaHex := hex.EncodeToString(sha[:])
64
 
 
66
  objKey := "maps/" + time.Now().Format("2006/01/02/") + shaHex + ".png"
67
 
68
  // ---- 4. Upload to object storage -----------------------------------
69
+ ctx := c.Request.Context()
70
+ if err := d.Storage.Put(
71
+ ctx, objKey,
72
+ bytes.NewReader(buf), int64(len(buf)),
73
+ fileHdr.Header.Get("Content-Type"),
74
+ ); err != nil {
75
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "storage failed"})
76
+ return
77
+ }
78
+
79
 
80
  // ---- 5. Insert into maps -------------------------------------------
81
  mapID := uuid.New()
backend/internal/storage/local.go ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package storage
2
+
3
+ import (
4
+ "context"
5
+ "io"
6
+ "os"
7
+ "path/filepath"
8
+ "time"
9
+ )
10
+
11
+ type LocalStore struct{ root string }
12
+
13
+ func NewLocalStore(root string) *LocalStore { return &LocalStore{root: root} }
14
+
15
+ func (l *LocalStore) Put(_ context.Context, key string, r io.Reader, _ int64, _ string) error {
16
+ full := filepath.Join(l.root, key)
17
+ if err := os.MkdirAll(filepath.Dir(full), 0o755); err != nil {
18
+ return err
19
+ }
20
+ f, err := os.Create(full)
21
+ if err != nil {
22
+ return err
23
+ }
24
+ defer f.Close()
25
+ _, err = io.Copy(f, r)
26
+ return err
27
+ }
28
+
29
+ func (l *LocalStore) Link(_ context.Context, key string, _ time.Duration) (string, error) {
30
+ // Served by Gin static handler: router.Static("/static", "./uploads")
31
+ return "/static/" + key, nil
32
+ }
33
+
34
+ func (l *LocalStore) Root() string {
35
+ return l.root
36
+ }
backend/internal/storage/s3.go ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package storage
2
+
3
+ import (
4
+ "context"
5
+ "time"
6
+ "io"
7
+
8
+ "github.com/minio/minio-go/v7"
9
+ "github.com/minio/minio-go/v7/pkg/credentials"
10
+ )
11
+
12
+ type S3Store struct {
13
+ cli *minio.Client
14
+ bucket string
15
+ }
16
+
17
+ func NewS3Store(endpoint, accessKey, secretKey, bucket string, useSSL bool) (*S3Store, error) {
18
+ cli, err := minio.New(endpoint, &minio.Options{
19
+ Creds: credentials.NewStaticV4(accessKey, secretKey, ""),
20
+ Secure: useSSL,
21
+ })
22
+ if err != nil {
23
+ return nil, err
24
+ }
25
+ return &S3Store{cli: cli, bucket: bucket}, nil
26
+ }
27
+
28
+ func (s *S3Store) Put(ctx context.Context, key string, r io.Reader, size int64, ctype string) error {
29
+ _, err := s.cli.PutObject(ctx, s.bucket, key, r, size,
30
+ minio.PutObjectOptions{ContentType: ctype})
31
+ return err
32
+ }
33
+
34
+ func (s *S3Store) Link(ctx context.Context, key string, ttl time.Duration) (string, error) {
35
+ u, err := s.cli.PresignedGetObject(ctx, s.bucket, key, ttl, nil)
36
+ if err != nil {
37
+ return "", err
38
+ }
39
+ return u.String(), nil
40
+ }
backend/internal/storage/storage.go ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package storage
2
+
3
+ import (
4
+ "context"
5
+ "time"
6
+ "io"
7
+ )
8
+
9
+ // ObjectStore is a minimal interface for saving and later linking to blobs.
10
+ type ObjectStore interface {
11
+ // Put permanently stores the object under key.
12
+ Put(ctx context.Context, key string, r io.Reader, size int64, contentType string) error
13
+
14
+ // Link returns a URL valid for roughly ttl (ignored by LocalStore).
15
+ Link(ctx context.Context, key string, ttl time.Duration) (string, error)
16
+ }
backend/main.go CHANGED
@@ -1,14 +1,71 @@
1
  package main
2
 
3
  import (
4
- "net/http"
5
- "github.com/gin-gonic/gin"
 
 
 
 
 
 
 
6
  )
7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  func main() {
9
- r := gin.Default()
10
- r.GET("/health", func(c *gin.Context) {
11
- c.JSON(http.StatusOK, gin.H{"status": "ok"})
12
- })
13
- r.Run()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  }
 
1
  package main
2
 
3
  import (
4
+ "database/sql"
5
+ "log"
6
+ "os"
7
+
8
+ "github.com/gin-gonic/gin"
9
+ _ "github.com/lib/pq"
10
+
11
+ "github.com/SCGR-1/promptaid-backend/internal/storage"
12
+ "github.com/SCGR-1/promptaid-backend/handlers"
13
  )
14
 
15
+ type Config struct {
16
+ S3Bucket string
17
+ UploadDir string
18
+ }
19
+
20
+ func loadConfig() Config {
21
+ return Config{
22
+ S3Bucket: os.Getenv("S3_BUCKET"),
23
+ UploadDir: os.Getenv("UPLOAD_DIR"),
24
+ }
25
+ }
26
+
27
+
28
  func main() {
29
+
30
+ cfg := loadConfig()
31
+
32
+ // ---- 1. connect DB ----
33
+ db, err := sql.Open("postgres", os.Getenv("DATABASE_URL"))
34
+ if err != nil { log.Fatal(err) }
35
+
36
+ // ---- 2. choose storage driver ----
37
+ var store storage.ObjectStore
38
+ switch os.Getenv("STORAGE_DRIVER") {
39
+ case "s3":
40
+ store, err = storage.NewS3Store(
41
+ os.Getenv("S3_ENDPOINT"),
42
+ os.Getenv("S3_KEY"),
43
+ os.Getenv("S3_SECRET"),
44
+ os.Getenv("S3_BUCKET"),
45
+ os.Getenv("S3_SSL") == "true",
46
+ )
47
+ if err != nil { log.Fatal(err) }
48
+ default: // local
49
+ uploadDir := os.Getenv("UPLOAD_DIR")
50
+ if uploadDir == "" { uploadDir = "./uploads" }
51
+ store = storage.NewLocalStore(uploadDir)
52
+ }
53
+
54
+ uploadDeps := handlers.UploadDeps{
55
+ DB: db,
56
+ Storage: store,
57
+ Bucket: cfg.S3Bucket,
58
+ RegionOK: make(map[string]bool),
59
+ }
60
+
61
+ // ---- 3. build server ----
62
+ r := gin.Default()
63
+
64
+ if l, ok := store.(*storage.LocalStore); ok {
65
+ r.Static("/static", l.Root()) // add Root() getter or hardcode "./uploads"
66
+ }
67
+
68
+ r.POST("/maps", uploadDeps.UploadMap)
69
+
70
+ log.Fatal(r.Run(":8080"))
71
  }
backend/server/server.go ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package server //
2
+
3
+ import (
4
+ "database/sql"
5
+ "github.com/SCGR-1/promptaid-backend/internal/storage"
6
+ )
7
+
8
+ type Server struct {
9
+ db *sql.DB
10
+ store storage.ObjectStore
11
+ }
12
+
13
+ func NewServer(db *sql.DB, store storage.ObjectStore) *Server {
14
+ return &Server{db: db, store: store}
15
+ }