Copied over SWAssistant and CDR backend
Started converting them to new backend
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 Caleb Gardner
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,108 @@
|
||||
# swassistant-backend
|
||||
|
||||
Custom backend for [SWAssistant](https://github.com/CalebQ42/SWAssistant). Extension of [darkstorm-backend](https://github.com/CalebQ42/darkstorm-server/tree/main/internal/backend)
|
||||
|
||||
## APIs
|
||||
|
||||
For `POST` requests, the `X-API-Key` http header must be set.
|
||||
|
||||
### Profiles
|
||||
|
||||
#### Upload profile to share
|
||||
|
||||
Character, vehicles, and minion profiles.
|
||||
|
||||
> POST: /profile?type={character|vehicle|minion}
|
||||
|
||||
Upload a profile. `type` query is required.
|
||||
|
||||
Request Body:
|
||||
|
||||
```json
|
||||
{
|
||||
// profile data
|
||||
}
|
||||
```
|
||||
|
||||
Note: Only allows up to 5MB of data. If over 5MB returns 413. Further limits might be imposed in the future.
|
||||
|
||||
Response:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "profile ID",
|
||||
"expiration": 0 // Unix time (Seconds) of expiration
|
||||
}
|
||||
```
|
||||
|
||||
#### Get a shared profile
|
||||
|
||||
> GET: /profile/{profileID}
|
||||
|
||||
Get an uploaded profile.
|
||||
|
||||
Response:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "character|vehicle|minion",
|
||||
// profile data minus uid
|
||||
}
|
||||
```
|
||||
|
||||
### Rooms
|
||||
|
||||
All room requests must include both `X-API-Key` and `Authorization` headers.
|
||||
|
||||
#### Room list
|
||||
|
||||
> GET: /rooms
|
||||
|
||||
Get a list of rooms your currently a part of.
|
||||
|
||||
Response:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": "room ID",
|
||||
"name": "room name",
|
||||
"owner": "username"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
#### Create new room
|
||||
|
||||
> POST: /rooms/new?name={roomName}
|
||||
|
||||
Create a new room. `name` query is required.
|
||||
|
||||
Response:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "room ID",
|
||||
"name": "room name"
|
||||
}
|
||||
```
|
||||
|
||||
#### Get room info
|
||||
|
||||
> GET: /rooms/{roomID}
|
||||
|
||||
Get info about a room.
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "room ID",
|
||||
"name": "room name",
|
||||
"owner": "username",
|
||||
"users": [
|
||||
"username"
|
||||
],
|
||||
"profiles": [
|
||||
"profile uuids"
|
||||
]
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,69 @@
|
||||
package swassistant
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/CalebQ42/darkstorm-server/internal/backend"
|
||||
"github.com/CalebQ42/darkstorm-server/internal/backend/db"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
)
|
||||
|
||||
type SWBackend struct {
|
||||
back *backend.Backend
|
||||
db *mongo.Database
|
||||
}
|
||||
|
||||
func NewSWBackend(back *backend.Backend, db *mongo.Database) *SWBackend {
|
||||
go func() {
|
||||
for range time.Tick(time.Hour) {
|
||||
log.Println("SWAssistant: Deleting expired profiles")
|
||||
res, err := db.Collection("profiles").DeleteMany(context.TODO(), bson.M{"expiration": bson.M{"$lt": time.Now().Unix()}})
|
||||
if err == mongo.ErrNoDocuments {
|
||||
continue
|
||||
}
|
||||
log.Println("SWAssistant: Deleted", res.DeletedCount, "profiles")
|
||||
}
|
||||
}()
|
||||
return &SWBackend{
|
||||
back: back,
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SWBackend) AppID() string {
|
||||
return "swassistant"
|
||||
}
|
||||
|
||||
func (s *SWBackend) CountTable() backend.CountTable {
|
||||
return db.NewMongoTable[backend.CountLog](s.db.Collection("logs"))
|
||||
}
|
||||
|
||||
func (s *SWBackend) CrashTable() backend.CrashTable {
|
||||
return db.NewMongoCrashTable(s.db.Collection("crashes"), s.db.Collection("crashArchive"))
|
||||
}
|
||||
|
||||
func (s *SWBackend) AddCrash(cr backend.IndividualCrash) bool {
|
||||
res := s.db.Collection("versions").FindOne(context.TODO(), bson.M{"version": cr.Version})
|
||||
return res.Err() != mongo.ErrNoDocuments
|
||||
}
|
||||
|
||||
func (s *SWBackend) Extension(mux *http.ServeMux) {
|
||||
mux.HandleFunc("GET /swa/room", s.ListRooms)
|
||||
mux.HandleFunc("POST /swa/room", s.NewRoom)
|
||||
mux.HandleFunc("GET /swa/room/{roomID}", s.GetRoom)
|
||||
|
||||
mux.HandleFunc("POST /swa/profile", s.UploadProfile)
|
||||
mux.HandleFunc("GET /swa/profile/{profileID}", s.GetProfile)
|
||||
|
||||
//Legacy (TODO: remove this after a month or two after the applciation gets updated)
|
||||
mux.HandleFunc("GET /room/list", s.ListRooms)
|
||||
mux.HandleFunc("POST /room/new", s.NewRoom)
|
||||
mux.HandleFunc("GET /room/{roomID}", s.GetRoom)
|
||||
|
||||
mux.HandleFunc("POST /profile/upload", s.UploadProfile)
|
||||
mux.HandleFunc("GET /profile/{profileID}", s.GetProfile)
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package swassistant
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/CalebQ42/darkstorm-server/internal/backend"
|
||||
"github.com/lithammer/shortuuid/v3"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
)
|
||||
|
||||
type UploadedProf struct {
|
||||
Profile map[string]any `json:"profile" bson:"profile"`
|
||||
ID string `json:"id" bson:"_id"`
|
||||
Type string `json:"type" bson:"type"`
|
||||
Expiration int64 `json:"expiration" bson:"expiration"`
|
||||
}
|
||||
|
||||
func (s *SWBackend) UploadProfile(w http.ResponseWriter, r *http.Request) {
|
||||
hdr, err := s.back.VerifyHeader(w, r, "profile", false)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if hdr.Key.AppID != "swassistant" {
|
||||
backend.ReturnError(w, http.StatusUnauthorized, "unauthorized", "Application not authorized")
|
||||
return
|
||||
}
|
||||
profType := r.URL.Query().Get("type")
|
||||
if profType == "" || (profType != "character" && profType != "vehicle" && profType != "minion") {
|
||||
backend.ReturnError(w, http.StatusBadRequest, "bad request", "Application sent a bad request")
|
||||
return
|
||||
}
|
||||
if r.Body == nil {
|
||||
backend.ReturnError(w, http.StatusBadRequest, "bad request", "Application sent a bad request")
|
||||
return
|
||||
}
|
||||
data, err := io.ReadAll(r.Body)
|
||||
r.Body.Close()
|
||||
if err != nil || len(data) == 0 {
|
||||
backend.ReturnError(w, http.StatusBadRequest, "bad request", "Application sent a bad request")
|
||||
return
|
||||
} else if len(data) > 5242880 { // 5MB
|
||||
backend.ReturnError(w, http.StatusRequestEntityTooLarge, "too large", "Profile is too large")
|
||||
return
|
||||
}
|
||||
prof := make(map[string]any)
|
||||
err = json.Unmarshal(data, &prof)
|
||||
if err != nil {
|
||||
backend.ReturnError(w, http.StatusBadRequest, "bad request", "Application sent a bad request")
|
||||
return
|
||||
}
|
||||
delete(prof, "uid")
|
||||
toUpload := UploadedProf{
|
||||
ID: shortuuid.New(),
|
||||
Expiration: time.Now().Add(time.Hour * 12).Round(time.Hour).Unix(),
|
||||
Type: profType,
|
||||
Profile: prof,
|
||||
}
|
||||
_, err = s.db.Collection("profiles").InsertOne(context.TODO(), toUpload)
|
||||
if err != nil {
|
||||
backend.ReturnError(w, http.StatusInternalServerError, "internal", "Server error")
|
||||
log.Println("error inserting profile:", err)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
json.NewEncoder(w).Encode(map[string]any{"id": toUpload.ID, "expiration": toUpload.Expiration})
|
||||
}
|
||||
|
||||
func (s *SWBackend) GetProfile(w http.ResponseWriter, r *http.Request) {
|
||||
res := s.db.Collection("profiles").FindOne(context.TODO(), bson.M{"_id": r.PathValue("profileID")})
|
||||
if res.Err() == mongo.ErrNoDocuments {
|
||||
backend.ReturnError(w, 404, "not found", "Profile not found")
|
||||
return
|
||||
} else if res.Err() != nil {
|
||||
backend.ReturnError(w, http.StatusInternalServerError, "internal", "Server error")
|
||||
log.Println("error getting profile:", res.Err())
|
||||
return
|
||||
}
|
||||
var prof UploadedProf
|
||||
err := res.Decode(&prof)
|
||||
if err != nil {
|
||||
backend.ReturnError(w, http.StatusInternalServerError, "internal", "Server error")
|
||||
log.Println("error decoding profile:", err)
|
||||
return
|
||||
}
|
||||
prof.Profile["type"] = prof.Type
|
||||
json.NewEncoder(w).Encode(prof.Profile)
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
package swassistant
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/CalebQ42/darkstorm-server/internal/backend"
|
||||
"github.com/google/uuid"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
)
|
||||
|
||||
type Room struct {
|
||||
ID string `json:"id" bson:"_id"`
|
||||
Name string `json:"name" bson:"name"`
|
||||
Owner string `json:"owner" bson:"owner"`
|
||||
Users []string `json:"users" bson:"users"`
|
||||
Profiles []string `json:"profiles" bson:"profiles"`
|
||||
}
|
||||
|
||||
func (s *SWBackend) ListRooms(w http.ResponseWriter, r *http.Request) {
|
||||
hdr, err := s.back.VerifyHeader(w, r, "rooms", false)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if hdr.Key.AppID != "swassistant" || hdr.User == nil {
|
||||
backend.ReturnError(w, http.StatusUnauthorized, "unauthorized", "Application not authorized")
|
||||
return
|
||||
}
|
||||
res, err := s.db.Collection("rooms").Find(context.TODO(), bson.M{"users": hdr.User.Username}, options.Find().SetProjection(bson.M{"_id": 1, "name": 1, "owner": 1}))
|
||||
if err != nil && err != mongo.ErrNoDocuments {
|
||||
log.Println("error getting room list:", err)
|
||||
backend.ReturnError(w, http.StatusInternalServerError, "internal", "Server error")
|
||||
return
|
||||
}
|
||||
out := make([]struct {
|
||||
ID string `json:"id" bson:"_id"`
|
||||
Name string `json:"name" bson:"name"`
|
||||
Owner string `json:"owner" bson:"owner"`
|
||||
}, 0)
|
||||
if err == nil {
|
||||
err = res.All(context.TODO(), &out)
|
||||
if err != nil {
|
||||
log.Println("error decoding room list:", err)
|
||||
backend.ReturnError(w, http.StatusInternalServerError, "internal", "Server error")
|
||||
return
|
||||
}
|
||||
}
|
||||
json.NewEncoder(w).Encode(out)
|
||||
}
|
||||
|
||||
func (s *SWBackend) NewRoom(w http.ResponseWriter, r *http.Request) {
|
||||
hdr, err := s.back.VerifyHeader(w, r, "rooms", false)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if hdr.Key.AppID != "swassistant" || hdr.User == nil {
|
||||
backend.ReturnError(w, http.StatusUnauthorized, "unauthorized", "Application not authorized")
|
||||
return
|
||||
}
|
||||
if req.Method != http.MethodPost || req.Query["name"] == nil || len(req.Query["name"]) != 1 || req.Query["name"][0] == "" {
|
||||
req.Resp.WriteHeader(http.StatusBadRequest)
|
||||
return true
|
||||
} else if req.User == nil {
|
||||
req.Resp.WriteHeader(http.StatusUnauthorized)
|
||||
return true
|
||||
}
|
||||
//TODO: check room name for unsavory words
|
||||
newRoom := Room{
|
||||
ID: uuid.NewString(),
|
||||
Name: req.Query["name"][0],
|
||||
Owner: req.User.Username,
|
||||
Users: []string{},
|
||||
Profiles: []string{},
|
||||
}
|
||||
_, err := s.db.Collection("rooms").InsertOne(context.TODO(), newRoom)
|
||||
if err != nil {
|
||||
log.Println("SWAssistant: Error creating room:", err)
|
||||
req.Resp.WriteHeader(http.StatusInternalServerError)
|
||||
return true
|
||||
}
|
||||
out, err := json.Marshal(map[string]string{"id": newRoom.ID, "name": newRoom.Name})
|
||||
if err != nil {
|
||||
log.Println("SWAssistant: Error encoding new room:", err)
|
||||
req.Resp.WriteHeader(http.StatusInternalServerError)
|
||||
return true
|
||||
}
|
||||
req.Resp.WriteHeader(http.StatusCreated)
|
||||
_, err = req.Resp.Write(out)
|
||||
if err != nil {
|
||||
log.Println("SWAssistant: Error writing new room:", err)
|
||||
req.Resp.WriteHeader(http.StatusInternalServerError)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *SWBackend) GetRoom(w http.ResponseWriter, r *http.Request) {
|
||||
hdr, err := s.back.VerifyHeader(w, r, "rooms", false)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if hdr.Key.AppID != "swassistant" || hdr.User == nil {
|
||||
backend.ReturnError(w, http.StatusUnauthorized, "unauthorized", "Application not authorized")
|
||||
return
|
||||
}
|
||||
if req.Method != http.MethodGet {
|
||||
req.Resp.WriteHeader(http.StatusBadRequest)
|
||||
return true
|
||||
} else if req.User == nil {
|
||||
req.Resp.WriteHeader(http.StatusUnauthorized)
|
||||
return true
|
||||
}
|
||||
res := s.db.Collection("rooms").FindOne(context.TODO(), bson.M{"_id": req.Path[1]})
|
||||
if res.Err() == mongo.ErrNoDocuments {
|
||||
req.Resp.WriteHeader(http.StatusNotFound)
|
||||
return true
|
||||
} else if res.Err() != nil {
|
||||
log.Println("SWAssistant: Error getting room:", res.Err())
|
||||
req.Resp.WriteHeader(http.StatusInternalServerError)
|
||||
return true
|
||||
}
|
||||
r := Room{}
|
||||
err := res.Decode(&r)
|
||||
if err != nil {
|
||||
log.Println("SWAssistant: Error decoding room:", err)
|
||||
req.Resp.WriteHeader(http.StatusInternalServerError)
|
||||
return true
|
||||
}
|
||||
out, err := json.Marshal(r)
|
||||
if err != nil {
|
||||
log.Println("SWAssistant: Error encoding room:", err)
|
||||
req.Resp.WriteHeader(http.StatusInternalServerError)
|
||||
return true
|
||||
}
|
||||
_, err = req.Resp.Write(out)
|
||||
if err != nil {
|
||||
log.Println("SWAssistant: Error writing room:", err)
|
||||
req.Resp.WriteHeader(http.StatusInternalServerError)
|
||||
}
|
||||
return true
|
||||
}
|
||||
Reference in New Issue
Block a user