Browse Source

Merge remote-tracking branch 'origin/master' into webui

webui
Stian Aune 5 years ago
parent
commit
a2f2c2b0bc
  1. 20
      Gopkg.lock
  2. 12
      cmd/lucifer-server/main.go
  3. 70
      controllers/user-controller.go
  4. 94
      database/sqlite/user-repository.go
  5. 4
      internal/config/config.go
  6. 18
      internal/respond/error.go
  7. 14
      internal/respond/json.go
  8. 20
      models/user.go

20
Gopkg.lock

@ -1,6 +1,18 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
name = "github.com/gorilla/context"
packages = ["."]
revision = "08b5f424b9271eedf6f9f0ce86cb9396ed337a42"
version = "v1.1.1"
[[projects]]
name = "github.com/gorilla/mux"
packages = ["."]
revision = "e3702bed27f0d39777b0b37b664b6280e8ef8fbf"
version = "v1.6.2"
[[projects]]
name = "github.com/jmoiron/sqlx"
packages = [
@ -25,9 +37,15 @@
]
revision = "ff983b9c42bc9fbf91556e191cc8efb585c16908"
[[projects]]
name = "gopkg.in/yaml.v2"
packages = ["."]
revision = "51d6538a90f86fe93ac480b35f37b2be17fef232"
version = "v2.2.2"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "8237f2aa046d402c0ec32f98a3e48419917aeab2979b0b394ce6f7873bc989c2"
inputs-digest = "52d5ff5f5eabb740093e6799fdc91d2d453a3cfd576031a15a0d1a1b7d5109b8"
solver-name = "gps-cdcl"
solver-version = 1

12
cmd/lucifer-server/main.go

@ -2,7 +2,11 @@ package main
import (
"log"
"net/http"
"github.com/gorilla/mux"
"git.aiterp.net/lucifer/lucifer/controllers"
"git.aiterp.net/lucifer/lucifer/database/sqlite"
"git.aiterp.net/lucifer/lucifer/internal/config"
)
@ -17,4 +21,12 @@ func main() {
if err != nil {
log.Fatalln("Failed to set up database:", err)
}
userController := controllers.NewUserController(sqlite.UserRepository)
router := mux.NewRouter()
userController.Mount(router, "/api/user/")
http.ListenAndServe(conf.Server.Address, router)
}

70
controllers/user-controller.go

@ -0,0 +1,70 @@
package controllers
import (
"encoding/json"
"net/http"
"git.aiterp.net/lucifer/lucifer/internal/respond"
"git.aiterp.net/lucifer/lucifer/models"
"github.com/gorilla/mux"
)
// The UserController is a controller for all user inports.
type UserController struct {
users models.UserRepository
}
// getUsers (`GET /`): List users
func (c *UserController) getUsers(w http.ResponseWriter, r *http.Request) {
// TODO: Check session
users, err := c.users.List(r.Context())
if err != nil {
respond.Error(w, 500, "db_error", err.Error())
return
}
respond.JSON(w, 200, users)
}
// login (`POST /login`): Log in as user
func (c *UserController) login(w http.ResponseWriter, r *http.Request) {
loginData := struct {
Username string `json:"username"`
Password string `json:"password"`
}{}
err := json.NewDecoder(r.Body).Decode(&loginData)
if err != nil {
respond.Error(w, 400, "invalid_json", "Input is not valid JSON.")
return
}
user, err := c.users.FindByName(r.Context(), loginData.Username)
if err != nil {
respond.Error(w, http.StatusUnauthorized, "login_failed", "Login failed.")
return
}
if err := user.CheckPassword(loginData.Password); err != nil {
respond.Error(w, http.StatusUnauthorized, "login_failed", "Login failed.")
return
}
// TODO: Open session
respond.JSON(w, 200, user)
}
// Mount mounts the controller
func (c *UserController) Mount(router *mux.Router, prefix string) {
sub := router.PathPrefix(prefix).Subrouter()
sub.Handle("/", http.HandlerFunc(c.getUsers)).Methods("GET")
sub.Handle("/login", http.HandlerFunc(c.login)).Methods("POST")
}
// NewUserController creates a new UserController.
func NewUserController(users models.UserRepository) *UserController {
return &UserController{users: users}
}

94
database/sqlite/user-repository.go

@ -0,0 +1,94 @@
package sqlite
import (
"context"
"git.aiterp.net/lucifer/lucifer/models"
)
// UserRepository is a sqlite database.
var UserRepository = &userRepository{}
type userRepository struct{}
func (repo *userRepository) FindByID(ctx context.Context, id int) (models.User, error) {
row := db.QueryRowxContext(ctx, "SELECT * FROM user WHERE id=?", id)
if err := row.Err(); err != nil {
return models.User{}, err
}
user := models.User{}
if err := row.StructScan(&user); err != nil {
return models.User{}, err
}
return user, nil
}
func (repo *userRepository) FindByName(ctx context.Context, name string) (models.User, error) {
row := db.QueryRowxContext(ctx, "SELECT * FROM user WHERE name=?", name)
if err := row.Err(); err != nil {
return models.User{}, err
}
user := models.User{}
if err := row.StructScan(&user); err != nil {
return models.User{}, err
}
return user, nil
}
func (repo *userRepository) List(ctx context.Context) ([]models.User, error) {
res, err := db.QueryxContext(ctx, "SELECT * FROM user")
if err != nil {
return nil, err
} else if err := res.Err(); err != nil {
return nil, err
}
users := make([]models.User, 0, 64)
for res.Next() {
user := models.User{}
if err := res.StructScan(&user); err != nil {
return nil, err
}
users = append(users, user)
}
return users, nil
}
func (repo *userRepository) Insert(ctx context.Context, user models.User) (models.User, error) {
res, err := db.NamedExecContext(ctx, "INSERT INTO user (name, hash) VALUES(:name, :hash)", user)
if err != nil {
return models.User{}, err
}
id, err := res.LastInsertId()
if err != nil {
return models.User{}, err
}
user.ID = int(id)
return user, nil
}
func (repo *userRepository) Update(ctx context.Context, user models.User) error {
_, err := db.NamedExecContext(ctx, "UPDATE user SET name=:name AND hash=:hash WHERE id=:id", user)
if err != nil {
return err
}
return nil
}
func (repo *userRepository) Remove(ctx context.Context, user models.User) error {
_, err := db.ExecContext(ctx, "REMOVE FROM user WHERE id=?", user.ID)
if err != nil {
return err
}
return err
}

4
internal/config/config.go

@ -11,6 +11,10 @@ type Config struct {
DB struct {
FileName string `yaml:"file_name"`
} `yaml:"db"`
Server struct {
Address string `yaml:"address"`
} `yaml:"server"`
}
// Load loads the first valid config file from the list of file paths.

18
internal/respond/error.go

@ -0,0 +1,18 @@
package respond
import "net/http"
// Error responds with a standardized error object.
func Error(w http.ResponseWriter, code int, kind string, message string) {
type errorContent struct {
Code int `json:"code"`
Kind string `json:"kind"`
Message string `json:"message"`
}
JSON(w, code, &errorContent{
Code: code,
Kind: kind,
Message: message,
})
}

14
internal/respond/json.go

@ -0,0 +1,14 @@
package respond
import (
"encoding/json"
"net/http"
)
// JSON gets the json value.
func JSON(w http.ResponseWriter, code int, data interface{}) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(code)
json.NewEncoder(w).Encode(data)
}

20
models/user.go

@ -1,14 +1,16 @@
package models
import (
"context"
"golang.org/x/crypto/bcrypt"
)
// The User model represents a registered user.
type User struct {
ID int
Name string
PassHash []byte
ID int `db:"id" json:"id"`
Name string `db:"name" json:"name"`
PassHash []byte `db:"hash" json:"-"`
}
// SetPassword sets the user's password
@ -35,10 +37,10 @@ func (user *User) CheckPassword(attempt string) error {
// UserRepository is an interface for all database operations
// the user model makes.
type UserRepository interface {
FindUserByID(id int) (User, error)
FindUserByName(name string) (User, error)
ListUsers() ([]User, error)
InsertUser(user User) (int, error)
UpdateUser(user User) error
RemoveUser(user User) error
FindByID(ctx context.Context, id int) (User, error)
FindByName(ctx context.Context, name string) (User, error)
List(ctx context.Context) ([]User, error)
Insert(ctx context.Context, user User) (User, error)
Update(ctx context.Context, user User) error
Remove(ctx context.Context, user User) error
}
Loading…
Cancel
Save