FILE / Unmei/Backend
routes/routes.go
Исходный файл и его история в репозитории.
243 lines
5.8 KiB
Go
243 lines
5.8 KiB
Go
package routes
|
|
|
|
import (
|
|
"backend/database"
|
|
"backend/utils"
|
|
"database/sql"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"github.com/getsentry/sentry-go"
|
|
"github.com/go-chi/chi/v5"
|
|
"github.com/golang-jwt/jwt/v5"
|
|
"io"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
var lpServer *LongPollServer
|
|
|
|
func RegisterRouters(r chi.Router) {
|
|
lpServer = InitLongPollServer()
|
|
|
|
r.Use(apiMiddleware)
|
|
r.Use(LimitMiddleware)
|
|
go CleanupVisitors()
|
|
r.Use(JWTMiddleware)
|
|
|
|
r.Get("/version", func(w http.ResponseWriter, _ *http.Request) {
|
|
Response(w, nil, map[string]string{
|
|
"version": os.Getenv("VERSION"),
|
|
"build": os.Getenv("BUILD"),
|
|
})
|
|
})
|
|
|
|
r.Route("/auth", RegisterAuthRoutes)
|
|
r.Route("/users", RegisterUsersRoutes)
|
|
r.Route("/novels", RegisterNovelsRoutes)
|
|
r.Route("/characters", RegisterCharactersRoutes)
|
|
r.Route("/genres", RegisterGenresRoutes)
|
|
r.Route("/games", RegisterGamesRoutes)
|
|
r.Route("/clubs", RegisterClubRoutes)
|
|
r.Route("/groups", RegisterGroupsRoutes)
|
|
r.Route("/news", RegisterNewsRoutes)
|
|
r.Route("/tokens", RegisterTokensRouter)
|
|
r.Route("/lp", RegisterLongPollRoutes)
|
|
|
|
if utils.IsDevelopment() && utils.IsDebug() {
|
|
err := chi.Walk(r, func(method string, route string, handler http.Handler, middlewares ...func(http.Handler) http.Handler) error {
|
|
log.Printf("[%s]: %s\n", method, route)
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
log.Fatalln(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func Response(w http.ResponseWriter, err error, data interface{}) {
|
|
const baseResponse = `{"error":false,"data":%v}`
|
|
|
|
if err != nil {
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
NotFound.Ferror(w)
|
|
} else {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
Error(w, err)
|
|
}
|
|
} else {
|
|
j, err := json.Marshal(&data)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
sentry.CaptureException(err)
|
|
return
|
|
}
|
|
|
|
_, err = fmt.Fprintf(w, baseResponse, string(j))
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
sentry.CaptureException(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func ResponsePagination(w http.ResponseWriter, err error, data interface{}, pagination interface{}) {
|
|
const baseResponse = `{"error":false,"pagination":%v,"data":%v}`
|
|
|
|
if err != nil {
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
NotFound.Ferror(w)
|
|
} else {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
Error(w, err)
|
|
}
|
|
} else {
|
|
jsonData, err := json.Marshal(&data)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
sentry.CaptureException(err)
|
|
return
|
|
}
|
|
|
|
jsonPagination, err := json.Marshal(&pagination)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
sentry.CaptureException(err)
|
|
return
|
|
}
|
|
|
|
_, err = fmt.Fprintf(w, baseResponse, string(jsonPagination), string(jsonData))
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
sentry.CaptureException(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func RawError(w http.ResponseWriter, e string) {
|
|
const baseError = `{"error":true,"error_data":{"code":-1,"text":"%v"}}`
|
|
|
|
_, err := fmt.Fprintf(w, baseError, strings.ReplaceAll(e, `"`, `\"`))
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
sentry.CaptureException(err)
|
|
}
|
|
}
|
|
|
|
func Error(w http.ResponseWriter, e error) {
|
|
const baseError = `{"error":true,"error_data":{"code":-1,"text":"%v"}}`
|
|
|
|
_, err := fmt.Fprintf(w, baseError, strings.ReplaceAll(e.Error(), `"`, `\"`))
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
sentry.CaptureException(err)
|
|
}
|
|
}
|
|
|
|
func JSON(w http.ResponseWriter, data string) {
|
|
_, err := fmt.Fprint(w, data)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
sentry.CaptureException(err)
|
|
}
|
|
}
|
|
|
|
func ReadBody(r *http.Request) ([]byte, error) {
|
|
body, err := io.ReadAll(r.Body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = r.Body.Close()
|
|
return body, err
|
|
}
|
|
|
|
func IURLParam(r *http.Request, key string) int {
|
|
par := chi.URLParam(r, key)
|
|
i, err := strconv.Atoi(par)
|
|
if err != nil {
|
|
return 0
|
|
} else {
|
|
return i
|
|
}
|
|
}
|
|
|
|
func GetCurrentUserID(r *http.Request) (uint64, error) {
|
|
tokenCookie, err := r.Cookie("token")
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
token, err := jwt.Parse(tokenCookie.Value, func(token *jwt.Token) (interface{}, error) {
|
|
if token.Method != jwt.SigningMethodHS256 {
|
|
return nil, fmt.Errorf("invalid singning method: %s", token.Method)
|
|
}
|
|
return []byte(os.Getenv("AUTH_TOKEN_SECRET")), nil
|
|
})
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
claims := token.Claims.(jwt.MapClaims)
|
|
id := int(claims["id"].(float64))
|
|
return uint64(id), nil
|
|
}
|
|
|
|
func GetCurrentUser(r *http.Request) (*database.User, error) {
|
|
cookie, err := r.Cookie("token")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(cookie.Value) == 0 {
|
|
return nil, fmt.Errorf("token cookie is empty")
|
|
}
|
|
|
|
user := new(database.User)
|
|
tokenString := cookie.Value
|
|
|
|
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (i interface{}, e error) {
|
|
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
|
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
|
|
}
|
|
return []byte(os.Getenv("AUTH_TOKEN_SECRET")), nil
|
|
})
|
|
if err != nil {
|
|
return user, err
|
|
}
|
|
if !token.Valid {
|
|
return user, fmt.Errorf("token not valid")
|
|
}
|
|
if claims, ok := token.Claims.(jwt.MapClaims); ok {
|
|
user, err = database.FetchUser(int(claims["id"].(float64)))
|
|
}
|
|
return user, err
|
|
}
|
|
|
|
func apiMiddleware(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Add("Access-Control-Allow-Origin", utils.GetFrontendURL())
|
|
w.Header().Add("Access-Control-Allow-Credentials", "true")
|
|
w.Header().Add("Content-Type", "application/json; charset=utf-8")
|
|
//w.Header().Add("Cache-Control", "max-age=60")
|
|
|
|
if r.Method == http.MethodOptions {
|
|
w.Header().Add("Access-Control-Allow-Headers", "Content-Type, Date, Content-Length, Location, dev, cache")
|
|
w.Header().Add("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT, OPTIONS")
|
|
return
|
|
}
|
|
|
|
if database.Database == nil {
|
|
log.Println("db not initialized")
|
|
return
|
|
}
|
|
err := database.Database.Ping()
|
|
if err != nil && strings.Contains(err.Error(), "broken pipe") {
|
|
database.ConnectToDB()
|
|
log.Println("connection to db reopened")
|
|
}
|
|
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
}
|