generated from tsivinsky/go-template
allow to upload images
but also files, hmmmm
This commit is contained in:
45
auth/cookies.go
Normal file
45
auth/cookies.go
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SetUserCookie(w http.ResponseWriter, token string, expiryTime time.Time) {
|
||||||
|
http.SetCookie(w, &http.Cookie{
|
||||||
|
Name: "token",
|
||||||
|
Value: token,
|
||||||
|
Secure: true,
|
||||||
|
HttpOnly: true,
|
||||||
|
Path: "/",
|
||||||
|
Expires: expiryTime,
|
||||||
|
SameSite: http.SameSiteStrictMode,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func RemoveUserCookie(w http.ResponseWriter) {
|
||||||
|
http.SetCookie(w, &http.Cookie{
|
||||||
|
Name: "token",
|
||||||
|
Value: "",
|
||||||
|
Secure: true,
|
||||||
|
HttpOnly: true,
|
||||||
|
Path: "/",
|
||||||
|
Expires: time.Now().Add(-time.Hour),
|
||||||
|
SameSite: http.SameSiteStrictMode,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetUserIdFromRequest(r *http.Request) (int64, error) {
|
||||||
|
c, err := r.Cookie("token")
|
||||||
|
if err != nil {
|
||||||
|
return -1, fmt.Errorf("no token cookie: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
userId, err := ValidateUserToken(c.Value)
|
||||||
|
if err != nil {
|
||||||
|
return -1, fmt.Errorf("invalid token: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return userId, nil
|
||||||
|
}
|
||||||
48
auth/jwt.go
Normal file
48
auth/jwt.go
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/golang-jwt/jwt/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
var secretKey = os.Getenv("JWT_SECRET_KEY")
|
||||||
|
|
||||||
|
type UserClaims struct {
|
||||||
|
jwt.RegisteredClaims
|
||||||
|
UserID int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func GenerateUserToken(userId int64, expiryTime time.Time) (string, error) {
|
||||||
|
now := time.Now()
|
||||||
|
claims := UserClaims{
|
||||||
|
UserID: userId,
|
||||||
|
RegisteredClaims: jwt.RegisteredClaims{
|
||||||
|
IssuedAt: jwt.NewNumericDate(now),
|
||||||
|
ExpiresAt: jwt.NewNumericDate(expiryTime),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||||
|
|
||||||
|
return token.SignedString([]byte(secretKey))
|
||||||
|
}
|
||||||
|
|
||||||
|
func ValidateUserToken(token string) (int64, error) {
|
||||||
|
claims := &UserClaims{}
|
||||||
|
|
||||||
|
parsed, err := jwt.ParseWithClaims(token, claims, func(t *jwt.Token) (any, error) {
|
||||||
|
return []byte(secretKey), nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return -1, fmt.Errorf("failed to parse token: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !parsed.Valid {
|
||||||
|
return -1, fmt.Errorf("invalid token")
|
||||||
|
}
|
||||||
|
|
||||||
|
return claims.UserID, nil
|
||||||
|
}
|
||||||
27
db/migrations/20260316172048_init_images.sql
Normal file
27
db/migrations/20260316172048_init_images.sql
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
-- +goose Up
|
||||||
|
-- +goose StatementBegin
|
||||||
|
CREATE TABLE images (
|
||||||
|
id varchar primary key not null,
|
||||||
|
data blob not null,
|
||||||
|
user_id integer not null,
|
||||||
|
created_at datetime default current_timestamp,
|
||||||
|
updated_at datetime default current_timestamp,
|
||||||
|
|
||||||
|
FOREIGN KEY(user_id) REFERENCES users(id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TRIGGER update_images_updated_at
|
||||||
|
AFTER UPDATE ON images
|
||||||
|
FOR EACH ROW
|
||||||
|
BEGIN
|
||||||
|
UPDATE images
|
||||||
|
SET updated_at = current_timestamp
|
||||||
|
WHERE id = OLD.id;
|
||||||
|
END;
|
||||||
|
-- +goose StatementEnd
|
||||||
|
|
||||||
|
-- +goose Down
|
||||||
|
-- +goose StatementBegin
|
||||||
|
DROP TABLE images;
|
||||||
|
-- +goose StatementEnd
|
||||||
|
|
||||||
10
db/migrations/20260316180504_images_content_type_field.sql
Normal file
10
db/migrations/20260316180504_images_content_type_field.sql
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
-- +goose Up
|
||||||
|
-- +goose StatementBegin
|
||||||
|
ALTER TABLE images ADD COLUMN content_type varchar default '';
|
||||||
|
-- +goose StatementEnd
|
||||||
|
|
||||||
|
-- +goose Down
|
||||||
|
-- +goose StatementBegin
|
||||||
|
ALTER TABLE images DROP COLUMN content_type;
|
||||||
|
-- +goose StatementEnd
|
||||||
|
|
||||||
9
docs/api/.gitignore
vendored
Normal file
9
docs/api/.gitignore
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# Secrets
|
||||||
|
.env*
|
||||||
|
|
||||||
|
# Dependencies
|
||||||
|
node_modules
|
||||||
|
|
||||||
|
# OS files
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
7
docs/api/auth/folder.yml
Normal file
7
docs/api/auth/folder.yml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
info:
|
||||||
|
name: auth
|
||||||
|
type: folder
|
||||||
|
seq: 1
|
||||||
|
|
||||||
|
request:
|
||||||
|
auth: inherit
|
||||||
22
docs/api/auth/login user.yml
Normal file
22
docs/api/auth/login user.yml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
info:
|
||||||
|
name: login user
|
||||||
|
type: http
|
||||||
|
seq: 2
|
||||||
|
|
||||||
|
http:
|
||||||
|
method: POST
|
||||||
|
url: "{{base_url}}/api/auth/login"
|
||||||
|
body:
|
||||||
|
type: json
|
||||||
|
data: |-
|
||||||
|
{
|
||||||
|
"email": "daniil@tsivinsky.com",
|
||||||
|
"password": "Daniil2000"
|
||||||
|
}
|
||||||
|
auth: inherit
|
||||||
|
|
||||||
|
settings:
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
followRedirects: true
|
||||||
|
maxRedirects: 5
|
||||||
22
docs/api/auth/register user.yml
Normal file
22
docs/api/auth/register user.yml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
info:
|
||||||
|
name: register user
|
||||||
|
type: http
|
||||||
|
seq: 1
|
||||||
|
|
||||||
|
http:
|
||||||
|
method: POST
|
||||||
|
url: "{{base_url}}/api/auth/register"
|
||||||
|
body:
|
||||||
|
type: json
|
||||||
|
data: |-
|
||||||
|
{
|
||||||
|
"email": "daniil@tsivinsky.com",
|
||||||
|
"password": "Daniil2000"
|
||||||
|
}
|
||||||
|
auth: inherit
|
||||||
|
|
||||||
|
settings:
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
followRedirects: true
|
||||||
|
maxRedirects: 5
|
||||||
4
docs/api/environments/dev.yml
Normal file
4
docs/api/environments/dev.yml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
name: dev
|
||||||
|
variables:
|
||||||
|
- name: base_url
|
||||||
|
value: http://localhost:5000
|
||||||
19
docs/api/images/delete image.yml
Normal file
19
docs/api/images/delete image.yml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
info:
|
||||||
|
name: delete image
|
||||||
|
type: http
|
||||||
|
seq: 3
|
||||||
|
|
||||||
|
http:
|
||||||
|
method: DELETE
|
||||||
|
url: "{{base_url}}/api/images/:id"
|
||||||
|
params:
|
||||||
|
- name: id
|
||||||
|
value: 019cf7de-0844-76d0-9d84-a4bbe7afc475
|
||||||
|
type: path
|
||||||
|
auth: inherit
|
||||||
|
|
||||||
|
settings:
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
followRedirects: true
|
||||||
|
maxRedirects: 5
|
||||||
7
docs/api/images/folder.yml
Normal file
7
docs/api/images/folder.yml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
info:
|
||||||
|
name: images
|
||||||
|
type: folder
|
||||||
|
seq: 2
|
||||||
|
|
||||||
|
request:
|
||||||
|
auth: inherit
|
||||||
15
docs/api/images/get user images.yml
Normal file
15
docs/api/images/get user images.yml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
info:
|
||||||
|
name: get user images
|
||||||
|
type: http
|
||||||
|
seq: 2
|
||||||
|
|
||||||
|
http:
|
||||||
|
method: GET
|
||||||
|
url: "{{base_url}}/api/images"
|
||||||
|
auth: inherit
|
||||||
|
|
||||||
|
settings:
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
followRedirects: true
|
||||||
|
maxRedirects: 5
|
||||||
21
docs/api/images/upload new image.yml
Normal file
21
docs/api/images/upload new image.yml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
info:
|
||||||
|
name: upload new image
|
||||||
|
type: http
|
||||||
|
seq: 1
|
||||||
|
|
||||||
|
http:
|
||||||
|
method: POST
|
||||||
|
url: "{{base_url}}/api/images"
|
||||||
|
body:
|
||||||
|
type: file
|
||||||
|
data:
|
||||||
|
- filePath: /home/daniil/Pictures/glizzy.png
|
||||||
|
contentType: image/png
|
||||||
|
selected: true
|
||||||
|
auth: inherit
|
||||||
|
|
||||||
|
settings:
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
followRedirects: true
|
||||||
|
maxRedirects: 5
|
||||||
10
docs/api/opencollection.yml
Normal file
10
docs/api/opencollection.yml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
opencollection: 1.0.0
|
||||||
|
|
||||||
|
info:
|
||||||
|
name: image-storage
|
||||||
|
bundled: false
|
||||||
|
extensions:
|
||||||
|
bruno:
|
||||||
|
ignore:
|
||||||
|
- node_modules
|
||||||
|
- .git
|
||||||
3
go.mod
3
go.mod
@@ -3,6 +3,8 @@ module image-storage
|
|||||||
go 1.26.1
|
go 1.26.1
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.3.1
|
||||||
|
github.com/google/uuid v1.6.0
|
||||||
github.com/jmoiron/sqlx v1.4.0
|
github.com/jmoiron/sqlx v1.4.0
|
||||||
github.com/pressly/goose/v3 v3.27.0
|
github.com/pressly/goose/v3 v3.27.0
|
||||||
golang.org/x/crypto v0.48.0
|
golang.org/x/crypto v0.48.0
|
||||||
@@ -11,7 +13,6 @@ require (
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/mfridman/interpolate v0.0.2 // indirect
|
github.com/mfridman/interpolate v0.0.2 // indirect
|
||||||
github.com/ncruces/go-strftime v1.0.0 // indirect
|
github.com/ncruces/go-strftime v1.0.0 // indirect
|
||||||
|
|||||||
2
go.sum
2
go.sum
@@ -8,6 +8,8 @@ github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+m
|
|||||||
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||||
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
|
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
|
||||||
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
|
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
|
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
|
||||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
|
|||||||
104
main.go
104
main.go
@@ -3,9 +3,12 @@ package main
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"image-storage/auth"
|
||||||
"image-storage/model"
|
"image-storage/model"
|
||||||
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
@@ -55,6 +58,13 @@ func main() {
|
|||||||
return fmt.Errorf("failed to populate user object after creating it: %v", err)
|
return fmt.Errorf("failed to populate user object after creating it: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
expiryTime := time.Now().Add(time.Hour * 24 * 30)
|
||||||
|
token, err := auth.GenerateUserToken(user.ID, expiryTime)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to generate access token: %v", err)
|
||||||
|
}
|
||||||
|
auth.SetUserCookie(w, token, expiryTime)
|
||||||
|
|
||||||
return srv.JSON(w, user, 201)
|
return srv.JSON(w, user, 201)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -84,9 +94,103 @@ func main() {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
expiryTime := time.Now().Add(time.Hour * 24 * 30)
|
||||||
|
token, err := auth.GenerateUserToken(user.ID, expiryTime)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to generate access token: %v", err)
|
||||||
|
}
|
||||||
|
auth.SetUserCookie(w, token, expiryTime)
|
||||||
|
|
||||||
return srv.JSON(w, user, 200)
|
return srv.JSON(w, user, 200)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
srv.Handle("POST /api/images", func(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
userId, err := auth.GetUserIdFromRequest(r)
|
||||||
|
if err != nil {
|
||||||
|
srv.Error(w, "unauthorized", err, 401)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
user := &model.User{Model: model.Model{ID: userId}}
|
||||||
|
if err := user.FindByID(db); err != nil {
|
||||||
|
srv.Error(w, "user not found", nil, 401)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := io.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to read request body: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
img := &model.Image{
|
||||||
|
UserID: user.ID,
|
||||||
|
Data: data,
|
||||||
|
ContentType: r.Header.Get("Content-Type"),
|
||||||
|
}
|
||||||
|
if err := img.Create(db); err != nil {
|
||||||
|
srv.Error(w, "failed to save image to database: %v", err, 400)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return srv.JSON(w, img, 201)
|
||||||
|
})
|
||||||
|
|
||||||
|
srv.Handle("GET /api/images", func(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
userId, err := auth.GetUserIdFromRequest(r)
|
||||||
|
if err != nil {
|
||||||
|
srv.Error(w, "unauthorized", err, 401)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := db.Queryx("SELECT * FROM images WHERE user_id = ?", userId)
|
||||||
|
if err != nil {
|
||||||
|
srv.Error(w, "images not found", err, 400)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
images := []model.Image{}
|
||||||
|
for rows.Next() {
|
||||||
|
img := model.Image{}
|
||||||
|
if err := rows.StructScan(&img); err != nil {
|
||||||
|
log.Printf("failed to save image to struct: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
images = append(images, img)
|
||||||
|
}
|
||||||
|
|
||||||
|
return srv.JSON(w, images, 200)
|
||||||
|
})
|
||||||
|
|
||||||
|
srv.Handle("GET /images/{id}", func(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
img := &model.Image{ID: r.PathValue("id")}
|
||||||
|
if err := img.FindByID(db); err != nil {
|
||||||
|
srv.Error(w, "image not found", nil, 404)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", img.ContentType)
|
||||||
|
w.Write(img.Data)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
srv.Handle("DELETE /api/images/{id}", func(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
img := &model.Image{ID: r.PathValue("id")}
|
||||||
|
if err := img.FindByID(db); err != nil {
|
||||||
|
srv.Error(w, "image not found", nil, 404)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := img.DeleteByID(db); err != nil {
|
||||||
|
srv.Error(w, "failed to delete image: %v", err, 500)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return srv.JSON(w, struct {
|
||||||
|
Ok bool `json:"ok"`
|
||||||
|
}{true}, 200)
|
||||||
|
})
|
||||||
|
|
||||||
if err := srv.ListenAndServe(); err != nil {
|
if err := srv.ListenAndServe(); err != nil {
|
||||||
log.Fatalf("failed to start http server: %v", err)
|
log.Fatalf("failed to start http server: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
63
model/image.go
Normal file
63
model/image.go
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Image struct {
|
||||||
|
// uuid
|
||||||
|
ID string `json:"id" db:"id"`
|
||||||
|
Data []byte `json:"-" db:"data"`
|
||||||
|
ContentType string `json:"contentType" db:"content_type"`
|
||||||
|
UserID int64 `json:"userId" db:"user_id"`
|
||||||
|
CreatedAt time.Time `json:"createdAt" db:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updatedAt" db:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (img *Image) Create(db *sqlx.DB) error {
|
||||||
|
if img.ID == "" {
|
||||||
|
uid, err := uuid.NewV7()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to generate uuid: %v", err)
|
||||||
|
}
|
||||||
|
img.ID = uid.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := db.NamedExec("INSERT INTO images (id, data, user_id, content_type) VALUES (:id, :data, :user_id, :content_type)", map[string]any{
|
||||||
|
"id": img.ID,
|
||||||
|
"data": img.Data,
|
||||||
|
"user_id": img.UserID,
|
||||||
|
"content_type": img.ContentType,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (img *Image) FindByID(db *sqlx.DB) error {
|
||||||
|
row := db.QueryRowx("SELECT * FROM images WHERE id = ?", img.ID)
|
||||||
|
if row.Err() != nil {
|
||||||
|
return row.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := row.StructScan(img); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (img *Image) DeleteByID(db *sqlx.DB) error {
|
||||||
|
_, err := db.Exec("DELETE FROM images WHERE id = ?", img.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user