feat: implement user model + migrations
Cette révision appartient à :
Parent
4b518d43ba
révision
a2cf369d11
6 fichiers modifiés avec 109 ajouts et 0 suppressions
|
@ -28,3 +28,10 @@ URI from normal.
|
|||
```env
|
||||
DATABASE_URI="<username>:<password>@tcp(<host>:<port>)/<database>"
|
||||
```
|
||||
|
||||
## Migrations
|
||||
|
||||
This repository uses the `migrate` CLI to run its migrations.
|
||||
|
||||
See https://github.com/golang-migrate/migrate/tree/master/cmd/migrate for an
|
||||
installation guide.
|
||||
|
|
1
go.mod
1
go.mod
|
@ -6,4 +6,5 @@ require (
|
|||
github.com/getsentry/sentry-go v0.10.0 // indirect
|
||||
github.com/go-sql-driver/mysql v1.5.0 // indirect
|
||||
github.com/gorilla/mux v1.8.0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20210317152858-513c2a44f670 // indirect
|
||||
)
|
||||
|
|
6
go.sum
6
go.sum
|
@ -137,6 +137,8 @@ golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnf
|
|||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210317152858-513c2a44f670 h1:gzMM0EjIYiRmJI3+jBdFuoynZlpxa2JQZsolKu09BXo=
|
||||
golang.org/x/crypto v0.0.0-20210317152858-513c2a44f670/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
|
@ -146,6 +148,7 @@ golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn
|
|||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
|
@ -155,8 +158,11 @@ golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5h
|
|||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190327201419-c70d86f8b7cf/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
|
|
1
migrations/000001_init_schema.down.sql
Fichier normal
1
migrations/000001_init_schema.down.sql
Fichier normal
|
@ -0,0 +1 @@
|
|||
DROP TABLE `users`;
|
28
migrations/000001_init_schema.up.sql
Fichier normal
28
migrations/000001_init_schema.up.sql
Fichier normal
|
@ -0,0 +1,28 @@
|
|||
CREATE TABLE `users` (
|
||||
id BINARY(16) NOT NULL UNIQUE DEFAULT UNHEX(REPLACE(UUID(), '-', '')),
|
||||
username VARCHAR(32) NOT NULL UNIQUE,
|
||||
email VARCHAR(320) NOT NULL UNIQUE,
|
||||
password VARCHAR(60) NOT NULL,
|
||||
level_id INT NOT NULL DEFAULT 0,
|
||||
last_seen DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
website VARCHAR(2048),
|
||||
biography TEXT,
|
||||
views INT NOT NULL DEFAULT 0,
|
||||
uploads INT NOT NULL DEFAULT 0,
|
||||
premium BOOL NOT NULL DEFAULT false,
|
||||
md_at_home BOOL NOT NULL DEFAULT false,
|
||||
avatar_url VARCHAR(2048) NOT NULL,
|
||||
|
||||
joined_at DATETIME DEFAULT NOW(),
|
||||
update_at DATETIME DEFAULT NOW() ON UPDATE NOW()
|
||||
);
|
||||
|
||||
INSERT INTO `users` (username, email, password, website, biography, avatar_url)
|
||||
VALUES (
|
||||
'root',
|
||||
'root@example.com',
|
||||
'$2a$10$EMCB89WuPHdGMMY.3Xy2yuxHsjVW36sDuq01y4lyU2zcH1JHJSen6',
|
||||
'https://example.com',
|
||||
'The root account used on initial installation.',
|
||||
'https://www.gravatar.com/avatar/205e460b479e2e5b48aec07710c08d50.jpg'
|
||||
);
|
|
@ -2,15 +2,24 @@ package models
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/go-sql-driver/mysql"
|
||||
"github.com/hbjydev/mangadex-next/database"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
type password string
|
||||
type User struct {
|
||||
ID string `json:"id"`
|
||||
Username string `json:"username"`
|
||||
Password password `json:"password"`
|
||||
Email string `json:"email"`
|
||||
LevelID string `json:"level_id"`
|
||||
JoinedAt time.Time `json:"joined_at"`
|
||||
UpdateAt time.Time `json:"update_at"`
|
||||
LastSeen time.Time `json:"last_seen"`
|
||||
Website string `json:"website"`
|
||||
Biography string `json:"biography"`
|
||||
|
@ -21,6 +30,55 @@ type User struct {
|
|||
AvatarURL string `json:"avatar"`
|
||||
}
|
||||
|
||||
func (password) MarshalJSON() ([]byte, error) {
|
||||
return []byte(`""`), nil
|
||||
}
|
||||
|
||||
func UserByUsername(username string) (*User, error) {
|
||||
row := database.DB.QueryRow(`
|
||||
SELECT
|
||||
hex(id), username, email, password, level_id, last_seen, website,
|
||||
biography, views, uploads, premium, md_at_home, avatar_url,
|
||||
joined_at, update_at FROM users
|
||||
WHERE username = ?
|
||||
`, username)
|
||||
|
||||
var u User
|
||||
var ls mysql.NullTime
|
||||
var ja mysql.NullTime
|
||||
var ua mysql.NullTime
|
||||
|
||||
if err := row.Scan(&u.ID, &u.Username, &u.Email, &u.Password, &u.LevelID,
|
||||
&ls, &u.Website, &u.Biography, &u.Views, &u.Uploads, &u.Premium,
|
||||
&u.MDAtHome, &u.AvatarURL, &ja, &ua); err != nil {
|
||||
log.Printf("UserByUsername: error at SQL query: %v\n", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if ls.Valid {
|
||||
u.LastSeen = ls.Time
|
||||
} else {
|
||||
log.Printf("UserByUsername: invalid SQL datetime value (id %v)\n", u.ID)
|
||||
return nil, errors.New("invalid sql datetime value")
|
||||
}
|
||||
|
||||
if ja.Valid {
|
||||
u.JoinedAt = ja.Time
|
||||
} else {
|
||||
log.Printf("UserByUsername: invalid SQL datetime value (id %v)\n", u.ID)
|
||||
return nil, errors.New("invalid sql datetime value")
|
||||
}
|
||||
|
||||
if ua.Valid {
|
||||
u.UpdateAt = ua.Time
|
||||
} else {
|
||||
log.Printf("UserByUsername: invalid SQL datetime value (id %v)\n", u.ID)
|
||||
return nil, errors.New("invalid sql datetime value")
|
||||
}
|
||||
|
||||
return &u, nil
|
||||
}
|
||||
|
||||
func UserFromJSON(jsonString string) (*User, error) {
|
||||
var user User
|
||||
|
||||
|
@ -32,6 +90,14 @@ func UserFromJSON(jsonString string) (*User, error) {
|
|||
return &user, nil
|
||||
}
|
||||
|
||||
func (u *User) CheckPassword(candidate string) bool {
|
||||
err := bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(candidate))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (u *User) Normalize() (*string, error) {
|
||||
bytes, err := json.Marshal(u)
|
||||
if err != nil {
|
||||
|
|
Référencer dans un nouveau ticket