mirror of
https://github.com/Unkn0wnCat/matrix-veles.git
synced 2025-04-28 17:56:49 +02:00
Add entry API endpoints
This commit is contained in:
parent
c8d1c33cb4
commit
1e071ffed4
8 changed files with 325 additions and 31 deletions
|
@ -78,8 +78,4 @@ func loadConfig() {
|
|||
if err := viper.ReadInConfig(); err == nil {
|
||||
log.Println("Using config file:", viper.ConfigFileUsed())
|
||||
}
|
||||
|
||||
if viper.GetString("bot.web.secret") == "hunter2" {
|
||||
log.Println("Web secret is not set! YOUR INSTALLATION IS INSECURE!")
|
||||
}
|
||||
}
|
||||
|
|
2
go.mod
2
go.mod
|
@ -3,8 +3,8 @@ module github.com/Unkn0wnCat/matrix-veles
|
|||
go 1.16
|
||||
|
||||
require (
|
||||
github.com/go-chi/chi/v5 v5.0.7
|
||||
github.com/golang-jwt/jwt/v4 v4.3.0
|
||||
github.com/gorilla/mux v1.8.0
|
||||
github.com/spf13/cobra v1.3.0
|
||||
github.com/spf13/viper v1.10.1
|
||||
go.mongodb.org/mongo-driver v1.8.3
|
||||
|
|
3
go.sum
3
go.sum
|
@ -121,6 +121,8 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
|
|||
github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=
|
||||
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-chi/chi/v5 v5.0.7 h1:rDTPXLDHGATaeHvVlLcR4Qe0zftYethFucbjVQ1PxU8=
|
||||
github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
|
@ -209,7 +211,6 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+
|
|||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
|
||||
github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=
|
||||
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||
|
|
|
@ -61,6 +61,41 @@ func GetEntryByID(id primitive.ObjectID) (*model.DBEntry, error) {
|
|||
return &object, nil
|
||||
}
|
||||
|
||||
func GetEntries(first int64, cursor *primitive.ObjectID) ([]*model.DBEntry, error) {
|
||||
db := DbClient.Database(viper.GetString("bot.mongo.database"))
|
||||
|
||||
opts := options.FindOptions{
|
||||
Limit: &first,
|
||||
}
|
||||
|
||||
filter := bson.M{}
|
||||
|
||||
if cursor != nil {
|
||||
filter = bson.M{
|
||||
"_id": bson.M{
|
||||
"$gt": *cursor,
|
||||
},
|
||||
}
|
||||
log.Println(filter)
|
||||
}
|
||||
|
||||
res, err := db.Collection(viper.GetString("bot.mongo.collection.entries")).Find(context.TODO(), filter, &opts)
|
||||
if err != nil {
|
||||
return nil, res.Err()
|
||||
}
|
||||
|
||||
var object []*model.DBEntry
|
||||
|
||||
err = res.All(context.TODO(), &object)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Println("DBG1")
|
||||
|
||||
return object, nil
|
||||
}
|
||||
|
||||
func GetEntryByHash(hash string) (*model.DBEntry, error) {
|
||||
db := DbClient.Database(viper.GetString("bot.mongo.database"))
|
||||
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
package model
|
||||
|
||||
import "go.mongodb.org/mongo-driver/bson/primitive"
|
||||
import (
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
"time"
|
||||
)
|
||||
|
||||
type DBComment struct {
|
||||
Timestamp time.Time `bson:"timestamp" json:"timestamp"`
|
||||
CommentedBy *primitive.ObjectID `bson:"commented_by" json:"commented_by"` // CommentedBy contains a reference to the user who commented
|
||||
Content string `bson:"content" json:"content"` // Content is the body of the comment
|
||||
}
|
||||
|
|
|
@ -4,28 +4,52 @@ import (
|
|||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func SetupAPI(router *mux.Router) {
|
||||
router.NotFoundHandler = NotFoundHandler{}
|
||||
router.MethodNotAllowedHandler = MethodNotAllowedHandler{}
|
||||
func SetupAPI() chi.Router {
|
||||
router := chi.NewRouter()
|
||||
|
||||
router.Path("/auth/login").Methods("POST").HandlerFunc(apiHandleAuthLogin)
|
||||
router.Path("/auth/register").Methods("POST").HandlerFunc(apiHandleAuthRegister)
|
||||
router.NotFound(notFoundHandler)
|
||||
router.MethodNotAllowed(methodNotAllowedHandler)
|
||||
|
||||
bot := router.PathPrefix("/bot").Subrouter()
|
||||
bot.Use(checkAuthMiddleware)
|
||||
//router.NotFoundHandler = NotFoundHandler{}
|
||||
//router.MethodNotAllowedHandler = MethodNotAllowedHandler{}
|
||||
|
||||
bot.Path("/test").HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
||||
writer.WriteHeader(200)
|
||||
router.Post("/auth/login", apiHandleAuthLogin)
|
||||
router.Post("/auth/register", apiHandleAuthRegister)
|
||||
|
||||
claims := request.Context().Value("claims").(jwtClaims)
|
||||
router.Route("/entries", func(r chi.Router) {
|
||||
r.Use(checkAuthMiddleware)
|
||||
|
||||
writer.Write([]byte(`hello ` + claims.Username))
|
||||
r.Get("/", apiHandleBotEntriesList)
|
||||
r.Post("/", apiHandleBotEntriesPost)
|
||||
|
||||
r.Get("/by-hash/{hash}", apiHandleBotEntryByHash)
|
||||
r.Get("/{id}", apiHandleBotEntry)
|
||||
})
|
||||
|
||||
router.Route("/test", func(r chi.Router) {
|
||||
r.Use(checkAuthMiddleware)
|
||||
|
||||
r.Get("/", func(writer http.ResponseWriter, request *http.Request) {
|
||||
writer.WriteHeader(200)
|
||||
|
||||
claims := request.Context().Value("claims").(jwtClaims)
|
||||
|
||||
writer.Write([]byte(`hello ` + claims.Username))
|
||||
})
|
||||
})
|
||||
|
||||
return router
|
||||
}
|
||||
|
||||
func getClaims(request *http.Request) jwtClaims {
|
||||
claims := request.Context().Value("claims").(jwtClaims)
|
||||
|
||||
return claims
|
||||
}
|
||||
|
||||
func checkAuthMiddleware(next http.Handler) http.Handler {
|
||||
|
@ -69,18 +93,14 @@ func writeJSONError(res http.ResponseWriter, statusCode int, err error) {
|
|||
_, _ = res.Write(enc)
|
||||
}
|
||||
|
||||
type NotFoundHandler struct{}
|
||||
|
||||
func (NotFoundHandler) ServeHTTP(res http.ResponseWriter, req *http.Request) {
|
||||
func notFoundHandler(res http.ResponseWriter, req *http.Request) {
|
||||
res.Header().Set("Content-Type", "application/json")
|
||||
res.WriteHeader(http.StatusNotFound)
|
||||
_, _ = res.Write([]byte(`{"error": "not_found","error_code":404}`))
|
||||
_, _ = res.Write([]byte(`{"error": "not found","error_code":404}`))
|
||||
}
|
||||
|
||||
type MethodNotAllowedHandler struct{}
|
||||
|
||||
func (MethodNotAllowedHandler) ServeHTTP(res http.ResponseWriter, req *http.Request) {
|
||||
func methodNotAllowedHandler(res http.ResponseWriter, req *http.Request) {
|
||||
res.Header().Set("Content-Type", "application/json")
|
||||
res.WriteHeader(http.StatusMethodNotAllowed)
|
||||
_, _ = res.Write([]byte(`{"error": "method_not_allowed","error_code":405}`))
|
||||
_, _ = res.Write([]byte(`{"error": "method not allowed","error_code":405}`))
|
||||
}
|
||||
|
|
230
internal/web/api/bot_entries.go
Normal file
230
internal/web/api/bot_entries.go
Normal file
|
@ -0,0 +1,230 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/Unkn0wnCat/matrix-veles/internal/db"
|
||||
"github.com/Unkn0wnCat/matrix-veles/internal/db/model"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type apiEntryPostBody struct {
|
||||
Hash string `json:"hash"`
|
||||
Tags []string `json:"tags"`
|
||||
PartOf []*primitive.ObjectID `json:"part_of"`
|
||||
FileURL string `json:"file_url"`
|
||||
Comment *string `json:"comment"`
|
||||
}
|
||||
|
||||
func apiHandleBotEntriesPost(res http.ResponseWriter, req *http.Request) {
|
||||
var body apiEntryPostBody
|
||||
|
||||
err := json.NewDecoder(req.Body).Decode(&body)
|
||||
if err != nil {
|
||||
writeJSONError(res, http.StatusBadRequest, errors.New("malformed body"))
|
||||
return
|
||||
}
|
||||
|
||||
existingEntry, err := db.GetEntryByHash(body.Hash)
|
||||
if err == nil {
|
||||
writeJSONError(res, http.StatusConflict, fmt.Errorf("hash already in database: %s", existingEntry.ID))
|
||||
return
|
||||
}
|
||||
if !errors.Is(err, mongo.ErrNoDocuments) {
|
||||
// TODO: LOG THIS ERROR
|
||||
writeJSONError(res, http.StatusInternalServerError, errors.New("database error"))
|
||||
return
|
||||
}
|
||||
|
||||
user := getClaims(req)
|
||||
userId, err := primitive.ObjectIDFromHex(user.Subject)
|
||||
if err != nil {
|
||||
// TODO: LOG THIS ERROR
|
||||
log.Println(userId)
|
||||
writeJSONError(res, http.StatusInternalServerError, errors.New("internal corruption 0x01"))
|
||||
return
|
||||
}
|
||||
|
||||
for i, partOf := range body.PartOf {
|
||||
list, err := db.GetListByID(*partOf)
|
||||
if err != nil {
|
||||
if errors.Is(err, mongo.ErrNoDocuments) {
|
||||
writeJSONError(res, http.StatusNotFound, fmt.Errorf("invalid partof value at index %d - not found", i))
|
||||
return
|
||||
}
|
||||
// TODO: LOG THIS ERROR
|
||||
writeJSONError(res, http.StatusInternalServerError, errors.New("database error"))
|
||||
return
|
||||
}
|
||||
|
||||
authorized := false
|
||||
|
||||
for _, maintainer := range list.Maintainers {
|
||||
log.Println(maintainer, userId)
|
||||
if maintainer.Hex() == userId.Hex() {
|
||||
authorized = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !authorized {
|
||||
writeJSONError(res, http.StatusUnauthorized, fmt.Errorf("invalid partof value at index %d - not authorized", i))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
newEntry := model.DBEntry{
|
||||
ID: primitive.NewObjectID(),
|
||||
Tags: body.Tags,
|
||||
PartOf: body.PartOf,
|
||||
HashValue: body.Hash,
|
||||
FileURL: body.FileURL,
|
||||
Timestamp: time.Now(),
|
||||
AddedBy: &userId,
|
||||
Comments: nil,
|
||||
}
|
||||
|
||||
if body.Comment != nil && *body.Comment != "" {
|
||||
newEntry.Comments = append(newEntry.Comments, &model.DBComment{
|
||||
CommentedBy: &userId,
|
||||
Content: *body.Comment,
|
||||
Timestamp: time.Now(),
|
||||
})
|
||||
}
|
||||
|
||||
err = db.SaveEntry(&newEntry)
|
||||
if err != nil {
|
||||
// TODO: LOG THIS ERROR
|
||||
writeJSONError(res, http.StatusInternalServerError, errors.New("database error"))
|
||||
return
|
||||
}
|
||||
|
||||
encoded, err := json.Marshal(newEntry)
|
||||
if err != nil {
|
||||
// TODO: LOG THIS ERROR
|
||||
writeJSONError(res, http.StatusInternalServerError, errors.New("could not marshal data"))
|
||||
return
|
||||
}
|
||||
|
||||
res.Header().Set("Content-Type", "application/json")
|
||||
res.WriteHeader(http.StatusOK)
|
||||
res.Write(encoded)
|
||||
}
|
||||
|
||||
func apiHandleBotEntriesList(res http.ResponseWriter, req *http.Request) {
|
||||
requestUri, err := url.ParseRequestURI(req.RequestURI)
|
||||
if err != nil {
|
||||
writeJSONError(res, http.StatusBadRequest, errors.New("unable to parse uri"))
|
||||
return
|
||||
}
|
||||
|
||||
first := int64(50)
|
||||
var cursor *primitive.ObjectID
|
||||
|
||||
if requestUri.Query().Has("first") {
|
||||
first2, err := strconv.Atoi(requestUri.Query().Get("first"))
|
||||
if err != nil {
|
||||
writeJSONError(res, http.StatusBadRequest, errors.New("malformed query"))
|
||||
return
|
||||
}
|
||||
first = int64(first2)
|
||||
}
|
||||
|
||||
if requestUri.Query().Has("cursor") {
|
||||
cursor2, err := primitive.ObjectIDFromHex(requestUri.Query().Get("cursor"))
|
||||
if err != nil {
|
||||
writeJSONError(res, http.StatusBadRequest, errors.New("malformed query"))
|
||||
return
|
||||
}
|
||||
cursor = &cursor2
|
||||
}
|
||||
|
||||
entries, err := db.GetEntries(first, cursor)
|
||||
if err != nil {
|
||||
if !errors.Is(err, mongo.ErrNoDocuments) {
|
||||
// TODO: LOG THIS ERROR
|
||||
writeJSONError(res, http.StatusInternalServerError, errors.New("database error"))
|
||||
return
|
||||
}
|
||||
res.Header().Set("Content-Type", "application/json")
|
||||
res.WriteHeader(http.StatusOK)
|
||||
res.Write([]byte("[]"))
|
||||
return
|
||||
}
|
||||
|
||||
encoded, err := json.Marshal(entries)
|
||||
if err != nil {
|
||||
// TODO: LOG THIS ERROR
|
||||
writeJSONError(res, http.StatusInternalServerError, errors.New("could not marshal data"))
|
||||
return
|
||||
}
|
||||
|
||||
res.Header().Set("Content-Type", "application/json")
|
||||
res.WriteHeader(http.StatusOK)
|
||||
res.Write(encoded)
|
||||
}
|
||||
|
||||
func apiHandleBotEntry(res http.ResponseWriter, req *http.Request) {
|
||||
requestedId := chi.URLParam(req, "id")
|
||||
objectId, err := primitive.ObjectIDFromHex(requestedId)
|
||||
if err != nil {
|
||||
writeJSONError(res, http.StatusNotFound, errors.New("malformed id"))
|
||||
return
|
||||
}
|
||||
|
||||
entry, err := db.GetEntryByID(objectId)
|
||||
if err != nil {
|
||||
if errors.Is(err, mongo.ErrNoDocuments) {
|
||||
writeJSONError(res, http.StatusNotFound, errors.New("not found"))
|
||||
return
|
||||
}
|
||||
// TODO: LOG THIS ERROR
|
||||
writeJSONError(res, http.StatusInternalServerError, errors.New("database error"))
|
||||
return
|
||||
}
|
||||
|
||||
encoded, err := json.Marshal(entry)
|
||||
if err != nil {
|
||||
// TODO: LOG THIS ERROR
|
||||
writeJSONError(res, http.StatusInternalServerError, errors.New("could not marshal data"))
|
||||
return
|
||||
}
|
||||
|
||||
res.Header().Set("Content-Type", "application/json")
|
||||
res.WriteHeader(http.StatusOK)
|
||||
res.Write(encoded)
|
||||
}
|
||||
|
||||
func apiHandleBotEntryByHash(res http.ResponseWriter, req *http.Request) {
|
||||
requestedHash := chi.URLParam(req, "hash")
|
||||
|
||||
entry, err := db.GetEntryByHash(requestedHash)
|
||||
if err != nil {
|
||||
if errors.Is(err, mongo.ErrNoDocuments) {
|
||||
writeJSONError(res, http.StatusNotFound, errors.New("not found"))
|
||||
return
|
||||
}
|
||||
// TODO: LOG THIS ERROR
|
||||
writeJSONError(res, http.StatusInternalServerError, errors.New("database error"))
|
||||
return
|
||||
}
|
||||
|
||||
encoded, err := json.Marshal(entry)
|
||||
if err != nil {
|
||||
// TODO: LOG THIS ERROR
|
||||
writeJSONError(res, http.StatusInternalServerError, errors.New("could not marshal data"))
|
||||
return
|
||||
}
|
||||
|
||||
res.Header().Set("Content-Type", "application/json")
|
||||
res.WriteHeader(http.StatusOK)
|
||||
res.Write(encoded)
|
||||
}
|
|
@ -2,7 +2,8 @@ package web
|
|||
|
||||
import (
|
||||
"github.com/Unkn0wnCat/matrix-veles/internal/web/api"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
"github.com/spf13/viper"
|
||||
"log"
|
||||
"net/http"
|
||||
|
@ -10,12 +11,19 @@ import (
|
|||
)
|
||||
|
||||
func StartServer() {
|
||||
r := mux.NewRouter()
|
||||
if viper.GetString("bot.web.secret") == "hunter2" {
|
||||
log.Println("Web secret is not set! REFUSING TO START WEB SERVER!")
|
||||
return
|
||||
}
|
||||
|
||||
r := chi.NewRouter()
|
||||
|
||||
r.Use(middleware.Logger)
|
||||
|
||||
r.HandleFunc("/", HomeHandler)
|
||||
//r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir("./static"))))
|
||||
|
||||
apiRouter := r.PathPrefix("/api").Subrouter()
|
||||
api.SetupAPI(apiRouter)
|
||||
r.Mount("/api", api.SetupAPI())
|
||||
|
||||
srv := &http.Server{
|
||||
Handler: r,
|
||||
|
|
Loading…
Add table
Reference in a new issue