init
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) (uint, error) {
|
||||
c, err := r.Cookie("token")
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("no token cookie: %v", err)
|
||||
}
|
||||
|
||||
userId, err := ValidateUserToken(c.Value)
|
||||
if err != nil {
|
||||
return 0, 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 uint
|
||||
}
|
||||
|
||||
func GenerateUserToken(userId uint, 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) (uint, error) {
|
||||
claims := &UserClaims{}
|
||||
|
||||
parsed, err := jwt.ParseWithClaims(token, claims, func(t *jwt.Token) (any, error) {
|
||||
return []byte(secretKey), nil
|
||||
})
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to parse token: %v", err)
|
||||
}
|
||||
|
||||
if !parsed.Valid {
|
||||
return 0, fmt.Errorf("invalid token")
|
||||
}
|
||||
|
||||
return claims.UserID, nil
|
||||
}
|
||||
8
docs/api/auth/folder.bru
Normal file
8
docs/api/auth/folder.bru
Normal file
@@ -0,0 +1,8 @@
|
||||
meta {
|
||||
name: auth
|
||||
seq: 1
|
||||
}
|
||||
|
||||
auth {
|
||||
mode: inherit
|
||||
}
|
||||
23
docs/api/auth/login user.bru
Normal file
23
docs/api/auth/login user.bru
Normal file
@@ -0,0 +1,23 @@
|
||||
meta {
|
||||
name: login user
|
||||
type: http
|
||||
seq: 2
|
||||
}
|
||||
|
||||
post {
|
||||
url: {{base_url}}/auth/login
|
||||
body: json
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"login": "tsivinsky",
|
||||
"password": "Daniil2000"
|
||||
}
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
15
docs/api/auth/logout user.bru
Normal file
15
docs/api/auth/logout user.bru
Normal file
@@ -0,0 +1,15 @@
|
||||
meta {
|
||||
name: logout user
|
||||
type: http
|
||||
seq: 3
|
||||
}
|
||||
|
||||
post {
|
||||
url: {{base_url}}/auth/logout
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
}
|
||||
24
docs/api/auth/register new user.bru
Normal file
24
docs/api/auth/register new user.bru
Normal file
@@ -0,0 +1,24 @@
|
||||
meta {
|
||||
name: register new user
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
post {
|
||||
url: {{base_url}}/auth/register
|
||||
body: json
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"email": "daniil@tsivinsky.com",
|
||||
"login": "tsivinsky",
|
||||
"password": "Daniil2000"
|
||||
}
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
9
docs/api/bruno.json
Normal file
9
docs/api/bruno.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"version": "1",
|
||||
"name": "wishlify",
|
||||
"type": "collection",
|
||||
"ignore": [
|
||||
"node_modules",
|
||||
".git"
|
||||
]
|
||||
}
|
||||
3
docs/api/environments/dev.bru
Normal file
3
docs/api/environments/dev.bru
Normal file
@@ -0,0 +1,3 @@
|
||||
vars {
|
||||
base_url: http://localhost:5000
|
||||
}
|
||||
8
docs/api/user/folder.bru
Normal file
8
docs/api/user/folder.bru
Normal file
@@ -0,0 +1,8 @@
|
||||
meta {
|
||||
name: user
|
||||
seq: 2
|
||||
}
|
||||
|
||||
auth {
|
||||
mode: inherit
|
||||
}
|
||||
15
docs/api/user/get user.bru
Normal file
15
docs/api/user/get user.bru
Normal file
@@ -0,0 +1,15 @@
|
||||
meta {
|
||||
name: get user
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{base_url}}/user
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
}
|
||||
23
docs/api/wishlists/create wishlist.bru
Normal file
23
docs/api/wishlists/create wishlist.bru
Normal file
@@ -0,0 +1,23 @@
|
||||
meta {
|
||||
name: create wishlist
|
||||
type: http
|
||||
seq: 2
|
||||
}
|
||||
|
||||
post {
|
||||
url: {{base_url}}/user/wishlists
|
||||
body: json
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"name": "test2",
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
20
docs/api/wishlists/delete user wishlist.bru
Normal file
20
docs/api/wishlists/delete user wishlist.bru
Normal file
@@ -0,0 +1,20 @@
|
||||
meta {
|
||||
name: delete user wishlist
|
||||
type: http
|
||||
seq: 3
|
||||
}
|
||||
|
||||
delete {
|
||||
url: {{base_url}}/user/wishlists/:uid
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
params:path {
|
||||
uid: 019c6dd3-1832-75c1-8955-d545efeb3474
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
8
docs/api/wishlists/folder.bru
Normal file
8
docs/api/wishlists/folder.bru
Normal file
@@ -0,0 +1,8 @@
|
||||
meta {
|
||||
name: wishlists
|
||||
seq: 3
|
||||
}
|
||||
|
||||
auth {
|
||||
mode: inherit
|
||||
}
|
||||
16
docs/api/wishlists/get user wishlists.bru
Normal file
16
docs/api/wishlists/get user wishlists.bru
Normal file
@@ -0,0 +1,16 @@
|
||||
meta {
|
||||
name: get user wishlists
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{base_url}}/user/wishlists
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
20
docs/api/wishlists/get wishlist.bru
Normal file
20
docs/api/wishlists/get wishlist.bru
Normal file
@@ -0,0 +1,20 @@
|
||||
meta {
|
||||
name: get wishlist
|
||||
type: http
|
||||
seq: 4
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{base_url}}/wishlists/:uid
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
params:path {
|
||||
uid: 019c6dcd-fde9-7374-a731-f03af290ea85
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
18
go.mod
Normal file
18
go.mod
Normal file
@@ -0,0 +1,18 @@
|
||||
module wishlify
|
||||
|
||||
go 1.25.7
|
||||
|
||||
require (
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1
|
||||
golang.org/x/crypto v0.48.0
|
||||
gorm.io/driver/sqlite v1.6.0
|
||||
gorm.io/gorm v1.31.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.22 // indirect
|
||||
golang.org/x/text v0.34.0 // indirect
|
||||
)
|
||||
18
go.sum
Normal file
18
go.sum
Normal file
@@ -0,0 +1,18 @@
|
||||
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/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
||||
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||
gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ=
|
||||
gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8=
|
||||
gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg=
|
||||
gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
|
||||
239
main.go
Normal file
239
main.go
Normal file
@@ -0,0 +1,239 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
"wishlify/auth"
|
||||
"wishlify/model"
|
||||
"wishlify/router"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func main() {
|
||||
dbPath := os.Getenv("DB_PATH")
|
||||
if dbPath == "" {
|
||||
dbPath = "./sqlite.db"
|
||||
}
|
||||
|
||||
db, err := gorm.Open(sqlite.Open("./sqlite.db"))
|
||||
if err != nil {
|
||||
log.Fatalf("failed to connect to db: %v\n", err)
|
||||
}
|
||||
|
||||
if err := db.AutoMigrate(&model.User{}, &model.Wishlist{}); err != nil {
|
||||
log.Fatalf("failed to migrate db: %v\n", err)
|
||||
}
|
||||
|
||||
router := router.New()
|
||||
|
||||
router.Handle("POST /auth/register", func(w http.ResponseWriter, r *http.Request) error {
|
||||
var body struct {
|
||||
Email string `json:"email"`
|
||||
Login string `json:"login"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
||||
w.WriteHeader(500)
|
||||
return fmt.Errorf("failed to decode request body: %v", err)
|
||||
}
|
||||
|
||||
if body.Email == "" || body.Login == "" || body.Password == "" {
|
||||
w.WriteHeader(400)
|
||||
return fmt.Errorf("invalid request; email, login and password are required")
|
||||
}
|
||||
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(body.Password), 10)
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
return fmt.Errorf("failed to generate password hash: %v", err)
|
||||
}
|
||||
|
||||
user := &model.User{
|
||||
Email: body.Email,
|
||||
Login: body.Login,
|
||||
Password: string(hash),
|
||||
}
|
||||
if tx := db.Create(user); tx.Error != nil {
|
||||
w.WriteHeader(400)
|
||||
return fmt.Errorf("failed to create user: %v", tx.Error)
|
||||
}
|
||||
|
||||
expiryTime := time.Now().Add(time.Hour * 24 * 7)
|
||||
token, err := auth.GenerateUserToken(user.ID, expiryTime)
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
return fmt.Errorf("failed to generate jwt: %v", err)
|
||||
}
|
||||
|
||||
auth.SetUserCookie(w, token, expiryTime)
|
||||
|
||||
w.WriteHeader(201)
|
||||
return router.SendJSON(w, user)
|
||||
})
|
||||
|
||||
router.Handle("POST /auth/login", func(w http.ResponseWriter, r *http.Request) error {
|
||||
var body struct {
|
||||
Login string `json:"login"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
||||
w.WriteHeader(400)
|
||||
return fmt.Errorf("failed to decode request body: %v", err)
|
||||
}
|
||||
|
||||
if body.Login == "" || body.Password == "" {
|
||||
w.WriteHeader(400)
|
||||
return fmt.Errorf("invalid request; login and password are required")
|
||||
}
|
||||
|
||||
user := &model.User{}
|
||||
if tx := db.First(user, "login = ?", body.Login); tx.Error != nil {
|
||||
w.WriteHeader(404)
|
||||
return fmt.Errorf("user not found: %v", tx.Error)
|
||||
}
|
||||
|
||||
if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(body.Password)); err != nil {
|
||||
w.WriteHeader(400)
|
||||
return fmt.Errorf("invalid password: %v", err)
|
||||
}
|
||||
|
||||
expiryTime := time.Now().Add(time.Hour * 24 * 7)
|
||||
token, err := auth.GenerateUserToken(user.ID, expiryTime)
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
return fmt.Errorf("failed to generate jwt: %v", err)
|
||||
}
|
||||
|
||||
auth.SetUserCookie(w, token, expiryTime)
|
||||
|
||||
w.WriteHeader(200)
|
||||
return router.SendJSON(w, user)
|
||||
})
|
||||
|
||||
router.Handle("POST /auth/logout", func(w http.ResponseWriter, r *http.Request) error {
|
||||
auth.RemoveUserCookie(w)
|
||||
w.WriteHeader(200)
|
||||
return router.SendJSON(w, struct {
|
||||
Ok bool `json:"ok"`
|
||||
}{true})
|
||||
})
|
||||
|
||||
router.Handle("GET /user", func(w http.ResponseWriter, r *http.Request) error {
|
||||
userId, err := auth.GetUserIdFromRequest(r)
|
||||
if err != nil {
|
||||
w.WriteHeader(401)
|
||||
return fmt.Errorf("unauthorized: %v", err)
|
||||
}
|
||||
|
||||
user := &model.User{}
|
||||
if tx := db.First(user, "id = ?", userId); tx.Error != nil {
|
||||
w.WriteHeader(404)
|
||||
return fmt.Errorf("user not found: %v", tx.Error)
|
||||
}
|
||||
|
||||
return router.SendJSON(w, user)
|
||||
})
|
||||
|
||||
router.Handle("GET /user/wishlists", func(w http.ResponseWriter, r *http.Request) error {
|
||||
userId, err := auth.GetUserIdFromRequest(r)
|
||||
if err != nil {
|
||||
w.WriteHeader(401)
|
||||
return fmt.Errorf("unauthorized: %v", err)
|
||||
}
|
||||
|
||||
wishlists := []model.Wishlist{}
|
||||
if tx := db.Find(&wishlists, "user_id = ?", userId); tx.Error != nil {
|
||||
w.WriteHeader(404)
|
||||
return fmt.Errorf("no wishlists found: %v", err)
|
||||
}
|
||||
|
||||
w.WriteHeader(200)
|
||||
return router.SendJSON(w, wishlists)
|
||||
})
|
||||
|
||||
router.Handle("POST /user/wishlists", func(w http.ResponseWriter, r *http.Request) error {
|
||||
userId, err := auth.GetUserIdFromRequest(r)
|
||||
if err != nil {
|
||||
w.WriteHeader(401)
|
||||
return fmt.Errorf("unauthorized: %v", err)
|
||||
}
|
||||
|
||||
var body struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
||||
w.WriteHeader(400)
|
||||
return fmt.Errorf("failed to decode request body: %v", err)
|
||||
}
|
||||
|
||||
if body.Name == "" {
|
||||
w.WriteHeader(400)
|
||||
return fmt.Errorf("invalid request; name is required")
|
||||
}
|
||||
|
||||
uid, err := uuid.NewV7()
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
return fmt.Errorf("failed to generate uuid for new wishlist: %v", err)
|
||||
}
|
||||
|
||||
wishlist := &model.Wishlist{
|
||||
UUID: uid.String(),
|
||||
Name: body.Name,
|
||||
Description: body.Description,
|
||||
UserID: userId,
|
||||
}
|
||||
if tx := db.Create(wishlist); tx.Error != nil {
|
||||
w.WriteHeader(500)
|
||||
return fmt.Errorf("failed to create wishlist: %v", tx.Error)
|
||||
}
|
||||
|
||||
return router.SendJSON(w, wishlist)
|
||||
})
|
||||
|
||||
router.Handle("GET /wishlists/{uid}", func(w http.ResponseWriter, r *http.Request) error {
|
||||
uid := r.PathValue("uid")
|
||||
|
||||
wishlist := &model.Wishlist{}
|
||||
if tx := db.First(wishlist, "uuid = ?", uid); tx.Error != nil {
|
||||
w.WriteHeader(404)
|
||||
return fmt.Errorf("wishlist not found: %v", tx.Error)
|
||||
}
|
||||
|
||||
w.WriteHeader(200)
|
||||
return router.SendJSON(w, wishlist)
|
||||
})
|
||||
|
||||
router.Handle("DELETE /user/wishlists/{uid}", func(w http.ResponseWriter, r *http.Request) error {
|
||||
userId, err := auth.GetUserIdFromRequest(r)
|
||||
if err != nil {
|
||||
w.WriteHeader(401)
|
||||
return fmt.Errorf("unauthorized: %v", err)
|
||||
}
|
||||
|
||||
uid := r.PathValue("uid")
|
||||
|
||||
if tx := db.Delete(&model.Wishlist{}, "uuid = ? AND user_id = ?", uid, userId); tx.Error != nil {
|
||||
w.WriteHeader(404)
|
||||
return fmt.Errorf("failed to delete wishlist: %v", tx.Error)
|
||||
}
|
||||
|
||||
return router.SendJSON(w, struct {
|
||||
Ok bool `json:"ok"`
|
||||
}{true})
|
||||
})
|
||||
|
||||
log.Println("starting http server")
|
||||
if err := http.ListenAndServe(":5000", router.Mux()); err != nil {
|
||||
log.Fatalf("failed to start http server: %v\n", err)
|
||||
}
|
||||
}
|
||||
10
model/model.go
Normal file
10
model/model.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package model
|
||||
|
||||
import "time"
|
||||
|
||||
type Model struct {
|
||||
ID uint `json:"id" gorm:"primarykey"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
DeletedAt *time.Time `json:"deletedAt" gorm:"index"`
|
||||
}
|
||||
9
model/user.go
Normal file
9
model/user.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package model
|
||||
|
||||
type User struct {
|
||||
Model
|
||||
|
||||
Email string `json:"email" gorm:"unique"`
|
||||
Login string `json:"login" gorm:"unique"`
|
||||
Password string `json:"-"`
|
||||
}
|
||||
14
model/wishlist.go
Normal file
14
model/wishlist.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package model
|
||||
|
||||
import "time"
|
||||
|
||||
type Wishlist struct {
|
||||
UUID string `json:"uuid"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
UserID uint `json:"userId"`
|
||||
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
DeletedAt *time.Time `json:"deletedAt" gorm:"index"`
|
||||
}
|
||||
49
router/router.go
Normal file
49
router/router.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type router struct {
|
||||
mux *http.ServeMux
|
||||
}
|
||||
|
||||
func (router *router) Mux() *http.ServeMux {
|
||||
return router.mux
|
||||
}
|
||||
|
||||
func (router *router) MsgError(msg string, err error) string {
|
||||
m := msg
|
||||
if err != nil {
|
||||
m = fmt.Sprintf("%s: %v", msg, err)
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func (router *router) SendJSON(w http.ResponseWriter, data any) error {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(data); err != nil {
|
||||
return fmt.Errorf("something bad happened: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (router *router) Handle(pattern string, handler func(w http.ResponseWriter, r *http.Request) error) {
|
||||
router.mux.HandleFunc(pattern, func(w http.ResponseWriter, r *http.Request) {
|
||||
if err := handler(w, r); err != nil {
|
||||
router.SendJSON(w, struct {
|
||||
Error string `json:"error"`
|
||||
}{
|
||||
Error: err.Error(),
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func New() *router {
|
||||
return &router{
|
||||
mux: http.NewServeMux(),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user