1
0
Bifurcation 0

implement a metrics endpoint as well as a user get/one endpoint

Cette révision appartient à :
Hayden Young 2021-03-21 20:50:11 +00:00
Parent 2cd7f4019c
révision 5cd05ba871
7 fichiers modifiés avec 210 ajouts et 13 suppressions

Voir le fichier

@ -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
Voir le fichier

@ -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))
}

Voir le fichier

@ -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.'

Voir le fichier

@ -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))
}

Voir le fichier

@ -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)

Voir le fichier

@ -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
Voir le fichier

@ -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)
}