implement a metrics endpoint as well as a user get/one endpoint
Cette révision appartient à :
Parent
2cd7f4019c
révision
5cd05ba871
7 fichiers modifiés avec 210 ajouts et 13 suppressions
|
@ -1,9 +1,32 @@
|
|||
package controllers
|
||||
|
||||
import "net/http"
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/hbjydev/mangadex-next/database"
|
||||
)
|
||||
|
||||
type HealthController struct{}
|
||||
|
||||
func (h *HealthController) Healthy(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
func (h *HealthController) Metrics(w http.ResponseWriter, r *http.Request) {
|
||||
var output string
|
||||
|
||||
output += fmt.Sprintf("mangadex_item_count{type=\"user\"} %v\n", countUsers())
|
||||
|
||||
w.Write([]byte(output))
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
func countUsers() int {
|
||||
row := database.DB.QueryRow("SELECT count(id) FROM users")
|
||||
var count int
|
||||
if err := row.Scan(&count); err != nil {
|
||||
return -1
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
|
56
controllers/user.go
Fichier normal
56
controllers/user.go
Fichier normal
|
@ -0,0 +1,56 @@
|
|||
package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/hbjydev/mangadex-next/models"
|
||||
)
|
||||
|
||||
type UserController struct{}
|
||||
|
||||
func (u *UserController) GetAll(w http.ResponseWriter, r *http.Request) {
|
||||
users, err := models.GetUsers()
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
usersJSON, err := json.Marshal(users)
|
||||
if err != nil {
|
||||
log.Println("/users: failed to marshal users array")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Add("Content-Type", "application/json; charset=utf-8")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write(usersJSON)
|
||||
}
|
||||
|
||||
func (u *UserController) GetOne(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
|
||||
user, err := models.UserByUsername(vars["username"])
|
||||
if err != nil {
|
||||
if err.Error() == "sql: no rows in result set" {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
} else {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
userJSON, err := user.Normalize()
|
||||
if err != nil {
|
||||
log.Printf("/user/%v: failed to marshal user", vars["username"])
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Add("Content-Type", "application/json; charset=utf-8")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte(*userJSON))
|
||||
}
|
|
@ -17,6 +17,8 @@ schemes:
|
|||
tags:
|
||||
- name: health
|
||||
description: 'Service health endpoints'
|
||||
- name: users
|
||||
description: 'Service user endpoints'
|
||||
|
||||
paths:
|
||||
/-/healthy:
|
||||
|
@ -28,4 +30,39 @@ paths:
|
|||
responses:
|
||||
'200':
|
||||
description: 'Everything is healthy.'
|
||||
/-/metrics:
|
||||
get:
|
||||
summary: Retrieves some metrics about the API server and database.
|
||||
operationId: metrics
|
||||
tags:
|
||||
- health
|
||||
responses:
|
||||
'200':
|
||||
description: 'A Prometheus-compatible metrics list.'
|
||||
|
||||
/users:
|
||||
get:
|
||||
summary: Retrieves every user in the database.
|
||||
operationId: getAllUsers
|
||||
tags:
|
||||
- users
|
||||
responses:
|
||||
'200':
|
||||
description: 'All users in the database.'
|
||||
|
||||
/users/{username}:
|
||||
get:
|
||||
summary: Retrieves a single user from the database.
|
||||
operationId: getOneUser
|
||||
tags:
|
||||
- users
|
||||
parameters:
|
||||
- in: path
|
||||
name: username
|
||||
schema:
|
||||
type: string
|
||||
required: true
|
||||
description: The username of the user you want information on.
|
||||
responses:
|
||||
'200':
|
||||
description: 'A single user from the database.'
|
||||
|
|
3
main.go
3
main.go
|
@ -42,6 +42,9 @@ func main() {
|
|||
healthRouter := routers.HealthRouter{}
|
||||
healthRouter.RegisterRoutes(r)
|
||||
|
||||
userRouter := routers.UserRouter{}
|
||||
userRouter.RegisterRoutes(r)
|
||||
|
||||
log.Println("Starting server on :3000...")
|
||||
log.Fatal(http.ListenAndServe(":3000", r))
|
||||
}
|
||||
|
|
|
@ -11,11 +11,10 @@ import (
|
|||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
type password string
|
||||
type User struct {
|
||||
ID string `json:"id"`
|
||||
Username string `json:"username"`
|
||||
Password password `json:"password"`
|
||||
Password string `json:"-"`
|
||||
Email string `json:"email"`
|
||||
LevelID string `json:"level_id"`
|
||||
JoinedAt time.Time `json:"joined_at"`
|
||||
|
@ -30,8 +29,59 @@ type User struct {
|
|||
AvatarURL string `json:"avatar"`
|
||||
}
|
||||
|
||||
func (password) MarshalJSON() ([]byte, error) {
|
||||
return []byte(`""`), nil
|
||||
func GetUsers() ([]User, error) {
|
||||
rows, err := database.DB.Query(`
|
||||
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
|
||||
`)
|
||||
if err != nil {
|
||||
log.Printf("Users: error at SQL query: %v\n", err)
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
users := make([]User, 0)
|
||||
for rows.Next() {
|
||||
var u User
|
||||
var ls mysql.NullTime
|
||||
var ja mysql.NullTime
|
||||
var ua mysql.NullTime
|
||||
|
||||
if err := rows.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")
|
||||
}
|
||||
|
||||
users = append(users, u)
|
||||
}
|
||||
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func UserByUsername(username string) (*User, error) {
|
||||
|
@ -39,7 +89,8 @@ func UserByUsername(username string) (*User, error) {
|
|||
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
|
||||
joined_at, update_at
|
||||
FROM users
|
||||
WHERE username = ?
|
||||
`, username)
|
||||
|
||||
|
|
|
@ -7,17 +7,19 @@ import (
|
|||
"github.com/hbjydev/mangadex-next/controllers"
|
||||
)
|
||||
|
||||
type HealthRouter struct {
|
||||
Controller controllers.HealthController
|
||||
}
|
||||
type HealthRouter struct{}
|
||||
|
||||
func (h *HealthRouter) healthcheck(w http.ResponseWriter, r *http.Request) {
|
||||
controller := controllers.HealthController{}
|
||||
controller.Healthy(w, r)
|
||||
}
|
||||
|
||||
func (h *HealthRouter) RegisterRoutes(r *mux.Router) {
|
||||
pathPrefix := r.PathPrefix("/-")
|
||||
|
||||
pathPrefix.Path("/healthy").HandlerFunc(h.healthcheck).Methods("GET")
|
||||
func (h *HealthRouter) metrics(w http.ResponseWriter, r *http.Request) {
|
||||
controller := controllers.HealthController{}
|
||||
controller.Metrics(w, r)
|
||||
}
|
||||
|
||||
func (h *HealthRouter) RegisterRoutes(r *mux.Router) {
|
||||
r.HandleFunc("/-/healthy", h.healthcheck)
|
||||
r.HandleFunc("/-/metrics", h.metrics)
|
||||
}
|
||||
|
|
25
routers/user.go
Fichier normal
25
routers/user.go
Fichier normal
|
@ -0,0 +1,25 @@
|
|||
package routers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/hbjydev/mangadex-next/controllers"
|
||||
)
|
||||
|
||||
type UserRouter struct{}
|
||||
|
||||
func (u *UserRouter) getAll(w http.ResponseWriter, r *http.Request) {
|
||||
controller := controllers.UserController{}
|
||||
controller.GetAll(w, r)
|
||||
}
|
||||
|
||||
func (u *UserRouter) getOne(w http.ResponseWriter, r *http.Request) {
|
||||
controller := controllers.UserController{}
|
||||
controller.GetOne(w, r)
|
||||
}
|
||||
|
||||
func (u *UserRouter) RegisterRoutes(r *mux.Router) {
|
||||
r.HandleFunc("/users", u.getAll)
|
||||
r.HandleFunc("/users/{username}", u.getOne)
|
||||
}
|
Référencer dans un nouveau ticket