diff --git a/.env.template b/.env.template index ccd8822..1ffd853 100644 --- a/.env.template +++ b/.env.template @@ -5,5 +5,5 @@ COUNTERS_MONGODB_URI= AUTH_OIDC_ID= AUTH_OIDC_SECRET= -# DEV_FORCE_IS_EBOARD= -# DEV_FORCE_IS_RTP= \ No newline at end of file +DEV_FORCE_IS_EBOARD= +DEV_FORCE_IS_RTP= \ No newline at end of file diff --git a/Containerfile b/Containerfile index 3780ff6..40a538c 100644 --- a/Containerfile +++ b/Containerfile @@ -4,8 +4,7 @@ WORKDIR /src/ RUN apk add git COPY go.* . RUN go mod download -COPY database database -COPY logging logging +COPY internal internal COPY *.go . RUN go build -v -o counters @@ -13,7 +12,7 @@ FROM docker.io/alpine RUN apk add --no-cache tzdata ENV TZ=America/New_York RUN cp /usr/share/zoneinfo/America/New_York /etc/localtime -COPY static /static +# COPY static /static COPY templates /templates COPY --from=build /src/counters /counters diff --git a/README.md b/README.md index 723dc94..0d1e1be 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ This project ALSO uses a modified version of bootstrap 5! Check it out [here!](h ## Local Development ### Installing -1. Clone and cd into the repo: git clone https://github.com/WeatherGod3218/counters +1. Clone and cd into the repo: git clone https://github.com/ComputerScienceHouse/counters >> (OPTIONAL): Make another branch if your working on a large thing! ### Setup diff --git a/api.go b/api_old.txt similarity index 98% rename from api.go rename to api_old.txt index 65a3462..7be20e3 100644 --- a/api.go +++ b/api_old.txt @@ -4,8 +4,8 @@ import ( "net/http" "sort" - "github.com/WeatherGod3218/counters/database" - "github.com/WeatherGod3218/counters/logging" + "github.com/ComputerScienceHouse/counters/database" + "github.com/ComputerScienceHouse/counters/internal/logging" "github.com/gin-gonic/gin" "github.com/sirupsen/logrus" diff --git a/database/counter.go b/database/counter.go deleted file mode 100644 index 54ffc31..0000000 --- a/database/counter.go +++ /dev/null @@ -1,233 +0,0 @@ -package database - -import ( - "context" - "time" - - "go.mongodb.org/mongo-driver/v2/bson" - "go.mongodb.org/mongo-driver/v2/mongo/options" - - "github.com/WeatherGod3218/counters/logging" - "github.com/sirupsen/logrus" -) - -type Counter struct { - Id bson.ObjectID `bson:"_id,omitempty"` - UserID string `bson:"uuid"` - CreatedBy string `bson:"createdBy"` - Title string `bson:"title"` - Description string `bson:"description"` - LastReset Reset `bson:"lastReset"` -} - -func CreateCounter(ctx context.Context, counter *Counter) error { - ctx, cancel := context.WithTimeout(ctx, 10*time.Second) - defer cancel() - - newHistory := []Reset{counter.LastReset} - - history := History{ - Id: counter.Id, - History: newHistory, - } - - session, err := Client.StartSession() - if err != nil { - return err - } - defer session.EndSession(context.Background()) - - _, err = session.WithTransaction(ctx, func(sessCtx context.Context) (any, error) { - _, err := Client.Database(db).Collection("counters").InsertOne(sessCtx, counter) - if err != nil { - return nil, err - } - - _, err = Client.Database(db).Collection("history").InsertOne(sessCtx, history) - if err != nil { - return nil, err - } - - return nil, nil - }) - - return err -} - -func GetCounterFromId(ctx context.Context, id string) (*Counter, error) { - ctx, cancel := context.WithTimeout(ctx, 10*time.Second) - defer cancel() - - objId, _ := bson.ObjectIDFromHex(id) - var counter Counter - - if err := Client.Database(db).Collection("counters").FindOne(ctx, bson.M{"_id": objId}).Decode(&counter); err != nil { - return nil, err - } - - return &counter, nil -} - -func GetAllCounters(ctx context.Context) ([]*Counter, error) { - ctx, cancel := context.WithTimeout(ctx, 10*time.Second) - defer cancel() - - cursor, err := Client.Database(db).Collection("counters").Find(ctx, bson.M{}) - if err != nil { - return nil, err - } - - var counters []*Counter - if err := cursor.All(ctx, &counters); err != nil { - return nil, err - } - - return counters, nil -} - -func DeleteCounter(ctx context.Context, counterId bson.ObjectID) error { - ctx, cancel := context.WithTimeout(ctx, 10*time.Second) - defer cancel() - - counter, err := GetCounterFromId(context.Background(), counterId.Hex()) - if err != nil { - logging.Logger.WithFields(logrus.Fields{"error": err, "module": "reset", "method": "DeleteReset"}).Fatal("error deleting the counter!") - } - - return counter.Delete(ctx) -} - -func (counter *Counter) Delete(ctx context.Context) error { - ctx, cancel := context.WithTimeout(ctx, 10*time.Second) - defer cancel() - - session, err := Client.StartSession() - if err != nil { - return err - } - defer session.EndSession(context.Background()) - - _, err = session.WithTransaction(ctx, func(sessCtx context.Context) (any, error) { - _, err = Client.Database(db).Collection("history").DeleteOne( - sessCtx, - bson.M{"_id": counter.Id}, - ) - - if err != nil { - return nil, err - } - - _, err = Client.Database(db).Collection("counters").DeleteOne( - sessCtx, - bson.M{"_id": counter.Id}, - ) - - if err != nil { - return nil, err - } - - return nil, nil - }) - - if err != nil { - logging.Logger.WithFields(logrus.Fields{"error": err, "module": "counter", "method": "Delete", "counter": counter.Id}).Warn("error deleting the counter!") - return err - } - - return nil -} - -func (counter *Counter) Update(ctx context.Context) (bool, error) { - ctx, cancel := context.WithTimeout(ctx, 10*time.Second) - defer cancel() - - session, err := Client.StartSession() - if err != nil { - return false, err - } - defer session.EndSession(context.Background()) - - counterEmpty := false - - _, err = session.WithTransaction(ctx, func(sessCtx context.Context) (any, error) { - var history History - - err = Client.Database(db).Collection("history").FindOne( - sessCtx, - bson.M{"_id": counter.Id}, - options.FindOne().SetProjection(bson.M{"history": bson.M{"$slice": 1}}), - ).Decode(&history) - - if err != nil { - return nil, err - } - - if len(history.History) == 0 { - counterEmpty = true - return nil, nil - } - - lastReset := history.History[0] - _, err = Client.Database(db).Collection("counters").UpdateOne( - sessCtx, - bson.M{"_id": counter.Id}, - bson.M{"$set": bson.M{"lastReset": lastReset}}, - ) - if err != nil { - return nil, err - } - - counter.LastReset = lastReset - return nil, nil - }) - - if err != nil { - logging.Logger.WithFields(logrus.Fields{"error": err, "module": "counter", "method": "Update", "counter": counter.Id}).Warn("error updating the counter!") - return false, err - } - - if counterEmpty { - return false, counter.Delete(context.Background()) - } - - return true, nil -} - -func (counter *Counter) Reset(ctx context.Context, reset *Reset) (any, error) { - ctx, cancel := context.WithTimeout(ctx, 10*time.Second) - defer cancel() - - session, err := Client.StartSession() - if err != nil { - return true, err - } - defer session.EndSession(context.Background()) - - _, err = session.WithTransaction(ctx, func(sessCtx context.Context) (any, error) { - _, err = Client.Database(db).Collection("history").UpdateOne( - sessCtx, - bson.M{"_id": counter.Id}, - bson.M{"$push": bson.M{ - "history": bson.M{ - "$each": bson.A{reset}, - "$sort": bson.M{"timestamp": -1}, - }, - }}, - ) - if err != nil { - return nil, err - } - - return nil, nil - }) - - if err != nil { - logging.Logger.WithFields(logrus.Fields{"error": err, "module": "counter", "method": "Reset", "counter": counter.Id}).Fatal("error updating counters history!") - } - - return counter.Update(context.Background()) -} - -func (c *Counter) IDHex() string { - return c.Id.Hex() -} diff --git a/database/database.go b/database/database.go deleted file mode 100644 index ff90694..0000000 --- a/database/database.go +++ /dev/null @@ -1,52 +0,0 @@ -// Package database contains modules for each struct in the database -package database - -import ( - "context" - "os" - "strings" - "time" - - "github.com/WeatherGod3218/counters/logging" - "github.com/sirupsen/logrus" - "go.mongodb.org/mongo-driver/v2/mongo" - "go.mongodb.org/mongo-driver/v2/mongo/options" - "go.mongodb.org/mongo-driver/v2/mongo/readpref" -) - -var Client *mongo.Client -var db = "" - -// Connects to the MongoDB database -func Connect() *mongo.Client { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - - uri := os.Getenv("COUNTERS_MONGODB_URI") - client, err := mongo.Connect(options.Client().ApplyURI(uri)) - - if err != nil { - logging.Logger.WithFields(logrus.Fields{"error": err, "module": "database", "method": "Connect"}).Fatal("error connecting to database, URI: " + uri + "meow") - } - - if err = client.Ping(ctx, readpref.Primary()); err != nil { - logging.Logger.WithFields(logrus.Fields{"error": err, "module": "database", "method": "Connect"}).Fatal("error pinging database") - } - - logging.Logger.WithFields(logrus.Fields{"module": "database", "method": "Connect"}).Info("connected to mongodb") - db = strings.Split(strings.Split(uri, "/")[3], "?")[0] - - return client -} - -// Disconnects from the MongoDB database -func Disconnect() { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - - if err := Client.Disconnect(ctx); err != nil { - logging.Logger.WithFields(logrus.Fields{"error": err, "module": "database", "method": "Disconnect"}).Fatal("error disconnecting from database") - } - - logging.Logger.WithFields(logrus.Fields{"module": "database", "method": "Disconnect"}).Info("disconnected from database") -} diff --git a/database/history.go b/database/history.go deleted file mode 100644 index 91cb6cf..0000000 --- a/database/history.go +++ /dev/null @@ -1,109 +0,0 @@ -package database - -import ( - "context" - "time" - - "go.mongodb.org/mongo-driver/v2/bson" -) - -// "go.mongodb.org/mongo-driver/v2/bson" -// "go.mongodb.org/mongo-driver/v2/mongo" -// "go.mongodb.org/mongo-driver/v2/mongo/options" - -type History struct { - Id bson.ObjectID `bson:"_id,omitempty"` - History []Reset `bson:"history"` -} - -func GetHistoryFromId(ctx context.Context, id string) (*History, error) { - ctx, cancel := context.WithTimeout(ctx, 10*time.Second) - defer cancel() - - objId, _ := bson.ObjectIDFromHex(id) - var history History - - if err := Client.Database(db).Collection("history").FindOne(ctx, bson.M{"_id": objId}).Decode(&history); err != nil { - return nil, err - } - - return &history, nil -} - -// func (counter *Counter) Reset(ctx context.Context, reset *Reset) error { -// ctx, cancel := context.WithTimeout(ctx, 10*time.Second) -// defer cancel() - -// objId := counter.Id - -// _, err := Client.Database(db).Collection("counters").UpdateOne(ctx, bson.M{"_id": objId}, -// bson.M{ -// "$set": bson.M{"lastReset": reset}, -// "$push": bson.M{"history": reset}, -// }) -// if err != nil { -// return err -// } - -// return nil -// } - -// func (counter *Counter) Close(ctx context.Context) error { -// ctx, cancel := context.WithTimeout(ctx, 10*time.Second) -// defer cancel() - -// objId := counter.Id - -// _, err := Client.Database(db).Collection("counters").UpdateOne(ctx, bson.M{"_id": objId}, bson.M{"$set": bson.M{"open": false}}) -// if err != nil { -// return err -// } - -// return nil -// } - -// func (c *Counter) IDHex() string { -// return c.Id.Hex() -// } - -// func GetActiveCounters(ctx context.Context) ([]*Counter, error) { -// ctx, cancel := context.WithTimeout(ctx, 10*time.Second) -// defer cancel() - -// var counters []*Counter - -// slackThreadReset := Reset{Instigator: "Jeff Mahoney", Reporter: "Eli Mares", Description: "Another thread has hit #ask-eboard", Timestamp: time.Date(2026, 2, 28, 11, 0, 0, 0, time.UTC)} -// slackThreadHistory := make([]Reset, 1) -// slackThreadHistory[0] = slackThreadReset - -// counters = append(counters, -// &Counter{Id: "1", Title: "Useless Slack Thread", Description: "Hello People, its time", CreatedBy: "weather", -// LastReset: slackThreadReset, History: slackThreadHistory}, -// ) - -// mathExamReset := Reset{Instigator: "Eli Mares", Reporter: "Declan McHale", Description: "Bombed his Calc 2 Final", Timestamp: time.Date(2026, 4, 30, 16, 15, 0, 0, time.UTC)} -// mathExamHistory := make([]Reset, 1) -// mathExamHistory[0] = mathExamReset - -// counters = append(counters, -// &Counter{Id: "2", Title: "CSHer Failing a Math Class", Description: "Why did you do this", CreatedBy: "weather", LastReset: mathExamReset, History: mathExamHistory}, -// ) - -// meowReset := Reset{Instigator: "Eli Mares", Reporter: "Noah Opcomm", Description: "Caught his ass meowing in #announcements", Timestamp: time.Date(2026, 5, 3, 12, 51, 0, 0, time.UTC)} -// meowResetHistory := make([]Reset, 1) -// meowResetHistory[0] = meowReset - -// counters = append(counters, -// &Counter{Id: "3", Title: "meow", Description: "mweoowwww", CreatedBy: "weather", LastReset: meowReset, History: meowResetHistory}, -// ) - -// igorReset := Reset{Instigator: "Cooper", Reporter: "Eli Mares", Description: "Taking a wild guess all the way from wisconsin", Timestamp: time.Date(2026, 5, 6, 12, 51, 0, 0, time.UTC)} -// igorResetHistory := make([]Reset, 1) -// igorResetHistory[0] = igorReset - -// counters = append(counters, -// &Counter{Id: "4", Title: "Cooper last played hollow knight", Description: "Hi Cooper!", CreatedBy: "weather", LastReset: igorReset, History: igorResetHistory}, -// ) - -// return counters, nil -// } diff --git a/database/reset.go b/database/reset.go deleted file mode 100644 index 8474c7b..0000000 --- a/database/reset.go +++ /dev/null @@ -1,47 +0,0 @@ -package database - -import ( - "context" - "time" - - "github.com/WeatherGod3218/counters/logging" - "github.com/sirupsen/logrus" - "go.mongodb.org/mongo-driver/v2/bson" -) - -type Reset struct { - Id bson.ObjectID `bson:"_id,omitempty"` - UserID string `bson:"uuid"` - Reporter string `bson:"reporter"` - Description string `bson:"description"` - Timestamp int64 `bson:"timestamp"` -} - -func (r *Reset) IDHex() string { - return r.Id.Hex() -} - -func DeleteReset(ctx context.Context, counterId bson.ObjectID, resetId bson.ObjectID) (bool, error) { - ctx, cancel := context.WithTimeout(ctx, 15*time.Second) - defer cancel() - - _, err := Client.Database(db).Collection("history").UpdateOne( - ctx, - bson.M{"_id": counterId}, - bson.M{"$pull": bson.M{ - "history": bson.M{"_id": resetId}, - }}, - ) - - if err != nil { - return false, err - } - - counter, err := GetCounterFromId(ctx, counterId.Hex()) - if err != nil { - logging.Logger.WithFields(logrus.Fields{"error": err, "module": "reset", "method": "DeleteReset"}).Warn("error deleting the counter!") - return false, err - } - - return counter.Update(ctx) -} diff --git a/go.mod b/go.mod index 264854b..bdcba85 100644 --- a/go.mod +++ b/go.mod @@ -1,51 +1,56 @@ -module github.com/WeatherGod3218/counters +module github.com/ComputerScienceHouse/counters go 1.26.2 require ( - github.com/computersciencehouse/csh-auth/v2 v2.0.2 + github.com/computersciencehouse/csh-auth/v2 v2.1.0 github.com/gin-gonic/gin v1.12.0 - github.com/sirupsen/logrus v1.9.4 - go.mongodb.org/mongo-driver/v2 v2.6.0 + github.com/sirupsen/logrus v1.10.1 + go.mongodb.org/mongo-driver/v2 v2.8.0 ) require ( github.com/bytedance/gopkg v0.1.4 // indirect - github.com/bytedance/sonic v1.15.1 // indirect - github.com/bytedance/sonic/loader v0.5.1 // indirect + github.com/bytedance/sonic v1.15.2 // indirect + github.com/bytedance/sonic/loader v0.5.2 // indirect github.com/cloudwego/base64x v0.1.7 // indirect - github.com/coreos/go-oidc/v3 v3.18.0 // indirect - github.com/gabriel-vasile/mimetype v1.4.13 // indirect + github.com/coreos/go-oidc/v3 v3.20.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.15 // indirect github.com/gin-contrib/sse v1.1.1 // indirect github.com/go-jose/go-jose/v4 v4.1.4 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.30.2 // indirect + github.com/go-playground/validator/v10 v10.30.3 // indirect github.com/goccy/go-json v0.10.6 // indirect github.com/goccy/go-yaml v1.19.2 // indirect github.com/golang-jwt/jwt/v5 v5.3.1 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/pgx/v5 v5.10.0 // indirect + github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/compress v1.18.6 // indirect - github.com/klauspost/cpuid/v2 v2.3.0 // indirect - github.com/leodido/go-urn v1.4.0 // indirect - github.com/mattn/go-isatty v0.0.22 // indirect + github.com/klauspost/compress v1.19.2 // indirect + github.com/klauspost/cpuid/v2 v2.4.0 // indirect + github.com/leodido/go-urn v1.5.0 // indirect + github.com/mattn/go-isatty v0.0.24 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/pelletier/go-toml/v2 v2.3.1 // indirect + github.com/pelletier/go-toml/v2 v2.4.3 // indirect github.com/quic-go/qpack v0.6.0 // indirect - github.com/quic-go/quic-go v0.59.0 // indirect + github.com/quic-go/quic-go v0.61.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect - github.com/ugorji/go/codec v1.3.1 // indirect + github.com/ugorji/go/codec v1.3.2 // indirect github.com/xdg-go/pbkdf2 v1.0.0 // indirect github.com/xdg-go/scram v1.2.0 // indirect github.com/xdg-go/stringprep v1.0.4 // indirect github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect - golang.org/x/arch v0.27.0 // indirect - golang.org/x/crypto v0.51.0 // indirect - golang.org/x/net v0.54.0 // indirect + golang.org/x/arch v0.30.0 // indirect + golang.org/x/crypto v0.55.0 // indirect + golang.org/x/net v0.58.0 // indirect golang.org/x/oauth2 v0.36.0 // indirect - golang.org/x/sync v0.20.0 // indirect - golang.org/x/sys v0.44.0 // indirect - golang.org/x/text v0.37.0 // indirect - google.golang.org/protobuf v1.36.11 // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/sys v0.47.0 // indirect + golang.org/x/text v0.41.0 // indirect + google.golang.org/protobuf v1.36.12 // indirect ) diff --git a/go.sum b/go.sum index 6969014..bcad006 100644 --- a/go.sum +++ b/go.sum @@ -2,19 +2,29 @@ github.com/bytedance/gopkg v0.1.4 h1:oZnQwnX82KAIWb7033bEwtxvTqXcYMxDBaQxo5JJHWM github.com/bytedance/gopkg v0.1.4/go.mod h1:v1zWfPm21Fb+OsyXN2VAHdL6TBb2L88anLQgdyje6R4= github.com/bytedance/sonic v1.15.1 h1:nJD5PmM0vY7J8CT6MxoqbVAAMhkSmV2HgRAUrrpLoOw= github.com/bytedance/sonic v1.15.1/go.mod h1:mT2NbXunuaEbnZ+mRIX/vYqKISmgEuHFDI4UzmKx2SA= +github.com/bytedance/sonic v1.15.2 h1:90H+rcF/FwLXwfB1cudOLq/je83n683Utf4Cbp0xHCo= +github.com/bytedance/sonic v1.15.2/go.mod h1:mT2NbXunuaEbnZ+mRIX/vYqKISmgEuHFDI4UzmKx2SA= github.com/bytedance/sonic/loader v0.5.1 h1:Ygpfa9zwRCCKSlrp5bBP/b/Xzc3VxsAW+5NIYXrOOpI= github.com/bytedance/sonic/loader v0.5.1/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= +github.com/bytedance/sonic/loader v0.5.2 h1:0QtP1gevc1OZ6/H8Lb9BRZiCXd1Ftjd3OKuj1T1lBIo= +github.com/bytedance/sonic/loader v0.5.2/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= github.com/cloudwego/base64x v0.1.7 h1:NppS+Fgzg5ovhn4NkUXaDT3x9jldgH5ToMCqzBSi2zI= github.com/cloudwego/base64x v0.1.7/go.mod h1:Cu1PV9zfrSf7ET2tIbWbbEy7jO7HHJ13q4X2SQ8aWYg= github.com/computersciencehouse/csh-auth/v2 v2.0.2 h1:xlxv2w2cmiWriVNhwA8Io1FWI54sDmv7c12gkVAcK30= github.com/computersciencehouse/csh-auth/v2 v2.0.2/go.mod h1:uL/2UDr9GJlAQORsn+rmFc18YkBsZPymYJbwb+uTRig= +github.com/computersciencehouse/csh-auth/v2 v2.1.0 h1:L00q0la/2vNZgo6rceICruPYtvTbghl7B8xBCAxpvm0= +github.com/computersciencehouse/csh-auth/v2 v2.1.0/go.mod h1:uL/2UDr9GJlAQORsn+rmFc18YkBsZPymYJbwb+uTRig= github.com/coreos/go-oidc/v3 v3.18.0 h1:V9orjXynvu5wiC9SemFTWnG4F45v403aIcjWo0d41+A= github.com/coreos/go-oidc/v3 v3.18.0/go.mod h1:DYCf24+ncYi+XkIH97GY1+dqoRlbaSI26KVTCI9SrY4= +github.com/coreos/go-oidc/v3 v3.20.0 h1:EtE0WIBHk03N+DqGkY4+UONzzZHk7amKt6IyNd7OsZE= +github.com/coreos/go-oidc/v3 v3.20.0/go.mod h1:DYCf24+ncYi+XkIH97GY1+dqoRlbaSI26KVTCI9SrY4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM= github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= +github.com/gabriel-vasile/mimetype v1.4.15 h1:05iP/CYtZ/w455R/KZM6rZ5ieAdh99UPtd+d3YzLmaI= +github.com/gabriel-vasile/mimetype v1.4.15/go.mod h1:azpTcoLcDZRNgFou5j+APrqQx9HqVPWa6ijYQIIVswQ= github.com/gin-contrib/sse v1.1.1 h1:uGYpNwTacv5R68bSGMapo62iLTRa9l5zxGCps4hK6ko= github.com/gin-contrib/sse v1.1.1/go.mod h1:QXzuVkA0YO7o/gun03UI1Q+FTI8ZV/n5t03kIQAI89s= github.com/gin-gonic/gin v1.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8= @@ -29,6 +39,8 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.30.2 h1:JiFIMtSSHb2/XBUbWM4i/MpeQm9ZK2xqPNk8vgvu5JQ= github.com/go-playground/validator/v10 v10.30.2/go.mod h1:mAf2pIOVXjTEBrwUMGKkCWKKPs9NheYGabeB04txQSc= +github.com/go-playground/validator/v10 v10.30.3 h1:4MU6YkEwx7GbcPJOZxrtbu+QfF3pJLJuaYTeAH0DYy8= +github.com/go-playground/validator/v10 v10.30.3/go.mod h1:4Axh7oCNGcoGkqLoE4YWt6n20mcEIsPRlB7vPk3lpyc= github.com/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU= github.com/goccy/go-json v0.10.6/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM= @@ -38,16 +50,34 @@ github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArs github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.10.0 h1:VhSvgU2jSli8o3AqIEOTJr7rZwAEUVo4E4XhR94Zfr0= +github.com/jackc/pgx/v5 v5.10.0/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/klauspost/compress v1.18.6 h1:2jupLlAwFm95+YDR+NwD2MEfFO9d4z4Prjl1XXDjuao= github.com/klauspost/compress v1.18.6/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= +github.com/klauspost/compress v1.19.2 h1:hMRETovs/pu/dVWN7zIT1PGG8t509MwT6bO7XSi26R8= +github.com/klauspost/compress v1.19.2/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/klauspost/cpuid/v2 v2.4.0 h1:S6Hrbc7+ywsr0r+RLapfGBHfyefhCTwEh3A0tV913Dw= +github.com/klauspost/cpuid/v2 v2.4.0/go.mod h1:19jmZ9mjzoF//ddRSUsv0zfBTJWh3QJh9FNxZTMrGxU= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/leodido/go-urn v1.5.0 h1:pLqT2kq1zpHW/1D18QMjMpdtX7cekxqtJJjg5ANyWw0= +github.com/leodido/go-urn v1.5.0/go.mod h1:9BORnCDhdPBJNDEX+w1bJisa8yOKYi116VeO96s4ifE= github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4= github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4= +github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI= +github.com/mattn/go-isatty v0.0.24/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -55,29 +85,39 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/pelletier/go-toml/v2 v2.3.1 h1:MYEvvGnQjeNkRF1qUuGolNtNExTDwct51yp7olPtrEc= github.com/pelletier/go-toml/v2 v2.3.1/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/pelletier/go-toml/v2 v2.4.3 h1:GTRvJQutkOSftxIFD5xw9aepkYNuPWmVJpffdDPYVpY= +github.com/pelletier/go-toml/v2 v2.4.3/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII= github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw= github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU= +github.com/quic-go/quic-go v0.61.0 h1:ui88A53s8MSVYLC56en0KQ17HARk+9986Dn0SBfKNvA= +github.com/quic-go/quic-go v0.61.0/go.mod h1:9So2anK4Tp22URSQq00k+Vo2PNkle96ycDPDHL4s9vs= github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= +github.com/sirupsen/logrus v1.10.1 h1:xi4336Zh11WpU14fXR6I67V3yaTPQYwRx2WEtHbRg4Q= +github.com/sirupsen/logrus v1.10.1/go.mod h1:vsQHnG7xzNsxk3NrwboUiWPnIC3dmbjcGPykD7+tiHk= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/stretchr/testify v1.12.0 h1:K6Mr6jO9JICuend/5xzTM03ydSV3vdNRYAdPSukj8uI= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY= github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= +github.com/ugorji/go/codec v1.3.2 h1:zkEASHHyEClGeURfgNT9PJZVfAbs9oEX9QXggwWNJbc= +github.com/ugorji/go/codec v1.3.2/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/scram v1.2.0 h1:bYKF2AEwG5rqd1BumT4gAnvwU/M9nBp2pTSxeZw7Wvs= @@ -89,26 +129,36 @@ github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfS github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.mongodb.org/mongo-driver/v2 v2.6.0 h1:b9sJOYrkmt4l8bY43ZenFBcPlhYIjaOfYHLtbB/5qi8= go.mongodb.org/mongo-driver/v2 v2.6.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0= +go.mongodb.org/mongo-driver/v2 v2.8.0 h1:CxWDGQYY8QQwNjAl/aq2sfWakdnWZynnqJ9F4DhHbP8= +go.mongodb.org/mongo-driver/v2 v2.8.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0= go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= golang.org/x/arch v0.27.0 h1:0WNVcR8u9yFz8j5FvdHpgwNp3FS5U4guYdzHwEiGjoU= golang.org/x/arch v0.27.0/go.mod h1:0X+GdSIP+kL5wPmpK7sdkEVTt2XoYP0cSjQSbZBwOi8= +golang.org/x/arch v0.30.0 h1:sB9h+1gRGa2+LauFSV0tm8bK1J2yo1bx6/Uyi/P6DTU= +golang.org/x/arch v0.30.0/go.mod h1:0X+GdSIP+kL5wPmpK7sdkEVTt2XoYP0cSjQSbZBwOi8= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI= golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8= +golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M= +golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w= golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ= +golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= +golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU= golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -116,6 +166,8 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ= golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= +golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -124,12 +176,16 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= +google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/internal/counters/counters.go b/internal/counters/counters.go new file mode 100644 index 0000000..af73535 --- /dev/null +++ b/internal/counters/counters.go @@ -0,0 +1 @@ +package counters diff --git a/internal/database/counter.go b/internal/database/counter.go new file mode 100644 index 0000000..2b4f3f9 --- /dev/null +++ b/internal/database/counter.go @@ -0,0 +1,215 @@ +package database + +import ( + "context" + "time" + + "github.com/ComputerScienceHouse/counters/internal/models" + "github.com/jackc/pgx/v5" +) + +func CreateCounterWithReset(ctx context.Context, userId string, username string, cReq *models.CreateCounterInput, rReq *models.CreateResetInput) (string, error) { + tx, err := db.Begin(ctx) + if err != nil { + return "", err + } + defer tx.Rollback(ctx) + + counterId, err := GenerateUUID() + if err != nil { + return "", err + } + rReq.CounterID = counterId + + _, err = tx.Exec(ctx, ` + INSERT INTO counters (counter_id, user_id, username, title, description, last_reset) + VALUES ($1, $2, $3, $4, $5, $6) + `, counterId, userId, username, cReq.Title, cReq.Description, nil) + if err != nil { + return "", err + } + + resetID, err := CreateResetWithTransaction(ctx, tx, userId, username, rReq) + if err != nil { + return "", err + } + + _, err = tx.Exec(ctx, ` + UPDATE counters SET last_reset = $1 WHERE counter_id = $2 + `, resetID, counterId) + if err != nil { + return "", err + } + + tx.Commit(ctx) + return counterId, nil +} + +func GetCounters(ctx context.Context) ([]*models.CounterListPart, error) { + rows, err := db.Query(ctx, ` + SELECT c.counter_id, c.user_id, c.title, c.description, r.description, r.username, r.occured_at FROM counters c + JOIN resets r ON c.last_reset = r.reset_id + ORDER BY r.occured_at DESC + `) + if err != nil { + return nil, err + } + defer rows.Close() + + counters := make([]*models.CounterListPart, 0) + + for rows.Next() { + var ( + counterId string + counterOwnerID string + counterTitle string + counterDescription string + resetDescription string + resetUsername string + resetCreatedAt time.Time + ) + + if err := rows.Scan(&counterId, &counterOwnerID, &counterTitle, &counterDescription, &resetDescription, &resetUsername, &resetCreatedAt); err != nil { + return nil, err + } + + counters = append(counters, &models.CounterListPart{ + CounterID: counterId, + CounterOwner: counterOwnerID, + CounterTitle: counterTitle, + CounterDescription: counterDescription, + ResetDescription: resetDescription, + ResetUsername: resetUsername, + ResetOccuredAt: resetCreatedAt.Unix(), + }) + } + + return counters, nil +} + +func GetCounterFromId(ctx context.Context, rowId string) (*models.CounterListPart, error) { + var ( + counterId string + counterTitle string + resetDescription string + resetUsername string + resetOccuredAt time.Time + ) + + err := db.QueryRow(ctx, ` + SELECT c.counter_id, c.title, r.description, r.username, r.occured_at FROM counters c + JOIN resets r ON c.last_reset = r.reset_id + WHERE c.counter_id = $1 + `, rowId).Scan(&counterId, &counterTitle, &resetDescription, &resetUsername, &resetOccuredAt) + + if err != nil { + return nil, err + } + + counter := &models.CounterListPart{ + CounterID: counterId, + CounterTitle: counterTitle, + ResetDescription: resetDescription, + ResetUsername: resetUsername, + ResetOccuredAt: resetOccuredAt.Unix(), + } + + return counter, nil +} + +func GetCounterOwner(ctx context.Context, counterID string) (string, error) { + var ownerId string + + _, err := db.Exec(ctx, ` + SELECT user_id FROM counters WHERE counter_id = $1 + `, counterID) + + return ownerId, err +} + +func DeleteCounter(ctx context.Context, counterID string) error { + _, err := db.Exec(ctx, ` + DELETE FROM counters WHERE counter_id = $1 + `, counterID) + + return err +} + +func DeleteCounterWithTransaction(ctx context.Context, tx pgx.Tx, id string) error { + _, err := tx.Exec(ctx, ` + DELETE FROM counters WHERE id = $1 + `, id) + + return err +} + +func InitCounters(ctx context.Context) error { + if _, err := db.Exec(ctx, ` + CREATE TABLE IF NOT EXISTS counters ( + counter_id UUID PRIMARY KEY, + user_id UUID NOT NULL, + username TEXT NOT NULL, + title TEXT NOT NULL, + description TEXT NOT NULL, + last_reset UUID, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() + ) + `); err != nil { + return err + } + + return nil +} + +func UpdateCounterLastReset(ctx context.Context, counterId string) (bool, error) { + tx, err := db.Begin(ctx) + if err != nil { + return false, err + } + defer tx.Rollback(ctx) + + var lastReset string + + if err := tx.QueryRow(ctx, ` + SELECT r.reset_id FROM resets r + WHERE r.counter_id = $1 + ORDER BY r.occured_at DESC + LIMIT 1 + `, counterId).Scan(&lastReset); err != nil { + if err == pgx.ErrNoRows { + return false, DeleteCounter(ctx, counterId) + } + return false, err + } + + if _, err := tx.Exec(ctx, ` + UPDATE counters SET last_reset = $1 WHERE counter_id = $2 + `, lastReset, counterId); err != nil { + return false, err + } + + if err := tx.Commit(ctx); err != nil { + return false, err + } + + return true, nil +} + +// im doing bullshit. I don't feel like dealing with a migration so this will do +func AlterCounters(ctx context.Context) error { + if _, err := db.Exec(ctx, ` + DO $$ BEGIN + ALTER TABLE counters + ADD CONSTRAINT counters_last_reset_fk + FOREIGN KEY (last_reset) REFERENCES resets(reset_id) + ON DELETE SET NULL; + EXCEPTION + WHEN duplicate_object THEN NULL; + END $$; + + `); err != nil { + return err + } + + return nil +} diff --git a/internal/database/database.go b/internal/database/database.go new file mode 100644 index 0000000..6390d0b --- /dev/null +++ b/internal/database/database.go @@ -0,0 +1,64 @@ +// Package database contains modules for each struct in the database +package database + +import ( + "context" + "net/url" + "os" + "time" + + "github.com/ComputerScienceHouse/counters/internal/logging" + "github.com/jackc/pgx/v5/pgxpool" +) + +var db *pgxpool.Pool +var DatabaseInitialized bool = false + +func InitDatabase() error { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + addr := os.Getenv("POSTGRES_ADDR") + user := os.Getenv("POSTGRES_USER") + password := os.Getenv("POSTGRES_PASSWORD") + + connURL := &url.URL{ + Scheme: "postgresql", + User: url.UserPassword(user, password), + Host: addr, + Path: "/" + user, + } + + newDB, err := pgxpool.New( + ctx, + connURL.String(), + ) + + if err != nil { + return err + } + + err = newDB.Ping(ctx) + if err != nil { + return err + } + db = newDB + + DatabaseInitialized = true + + if err := InitCounters(ctx); err != nil { + return err + } + + if err := InitReset(ctx); err != nil { + return err + } + + if err := AlterCounters(ctx); err != nil { + return err + } + + logging.Logger.Info("Database has been initalized!") + + return nil +} diff --git a/internal/database/reset.go b/internal/database/reset.go new file mode 100644 index 0000000..1ece040 --- /dev/null +++ b/internal/database/reset.go @@ -0,0 +1,129 @@ +package database + +import ( + "context" + "time" + + "github.com/ComputerScienceHouse/counters/internal/models" + "github.com/jackc/pgx/v5" +) + +func CreateReset(ctx context.Context, userID string, username string, resetTime int64, req *models.CreateResetInput) (string, error) { + resetID, err := GenerateUUID() + if err != nil { + return "", err + } + + _, err = db.Exec(ctx, ` + INSERT INTO resets (reset_id, counter_id, user_id, username, description, occured_at) + VALUES ($1, $2, $3, $4, $5, $6) + `, resetID, req.CounterID, userID, username, req.Description, time.Unix(resetTime, 0)) + + return resetID, err +} + +func CreateResetWithTransaction(ctx context.Context, tx pgx.Tx, userID string, username string, req *models.CreateResetInput) (string, error) { + resetID, err := GenerateUUID() + if err != nil { + return "", err + } + + _, err = tx.Exec(ctx, ` + INSERT INTO resets (reset_id, counter_id, user_id, username, description, occured_at) + VALUES ($1, $2, $3, $4, $5, $6) + `, resetID, req.CounterID, userID, username, req.Description, req.ResetTime) + + return resetID, err +} + +func DeleteReset(ctx context.Context, counterid string, resetid string) error { + tx, err := db.Begin(ctx) + if err != nil { + return err + } + defer tx.Rollback(ctx) + + _, err = db.Exec(ctx, ` + DELETE FROM resets WHERE reset_id = $1 + `, resetid) + + return err +} + +func GetResetOwner(ctx context.Context, resetID string) (string, error) { + var ownerId string + + err := db.QueryRow(ctx, ` + SELECT user_id FROM resets WHERE reset_id = $1 + `, resetID).Scan(&ownerId) + + return ownerId, err +} + +func GetCounterFromReset(ctx context.Context, resetID string) (string, error) { + var counterID string + + if err := db.QueryRow(ctx, ` + SELECT r.counter_id FROM resets r WHERE r.reset_id = $1 + `, resetID).Scan(&counterID); err != nil { + return "", err + } + + return counterID, nil +} + +func GetResetsFromCounterId(ctx context.Context, counterID string) ([]*models.ResetListPart, error) { + rows, err := db.Query(ctx, ` + SELECT r.reset_id, r.description, r.user_id, r.username, r.occured_at FROM resets r + WHERE r.counter_id = $1 + ORDER BY r.occured_at DESC + `, counterID) + if err != nil { + return nil, err + } + defer rows.Close() + + resets := make([]*models.ResetListPart, 0) + + for rows.Next() { + var ( + resetId string + resetDescription string + resetOwner string + resetUsername string + resetOccuredAt time.Time + ) + + if err := rows.Scan(&resetId, &resetDescription, &resetOwner, &resetUsername, &resetOccuredAt); err != nil { + return nil, err + } + + resets = append(resets, &models.ResetListPart{ + ResetID: resetId, + ResetDescription: resetDescription, + ResetOwner: resetOwner, + ResetUsername: resetUsername, + ResetOccuredAt: resetOccuredAt.Unix(), + }) + } + + return resets, nil +} + +func InitReset(ctx context.Context) error { + if _, err := db.Exec(ctx, ` + CREATE TABLE IF NOT EXISTS resets ( + reset_id UUID PRIMARY KEY, + counter_id UUID REFERENCES counters(counter_id) ON DELETE CASCADE, + user_id UUID NOT NULL, + username TEXT NOT NULL, + description TEXT NOT NULL, + occured_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() + ) + `); err != nil { + return err + } + + return nil +} diff --git a/internal/database/util.go b/internal/database/util.go new file mode 100644 index 0000000..72d112f --- /dev/null +++ b/internal/database/util.go @@ -0,0 +1,14 @@ +package database + +import ( + "github.com/google/uuid" +) + +func GenerateUUID() (string, error) { + uuid, err := uuid.NewV7() + if err != nil { + return "", nil + } + + return uuid.String(), nil +} diff --git a/logging/logging.go b/internal/logging/logging.go similarity index 100% rename from logging/logging.go rename to internal/logging/logging.go diff --git a/internal/models/api.go b/internal/models/api.go new file mode 100644 index 0000000..7226897 --- /dev/null +++ b/internal/models/api.go @@ -0,0 +1,15 @@ +package models + +type ErrorResponse struct { + Error string `json:"error"` +} + +type ResetResponse struct { + Exists bool `json:"exists"` +} + +func NewErrorResponse() *ErrorResponse { + return &ErrorResponse{ + Error: "There was an error with this request", + } +} diff --git a/internal/models/api_counter.go b/internal/models/api_counter.go new file mode 100644 index 0000000..f729074 --- /dev/null +++ b/internal/models/api_counter.go @@ -0,0 +1,15 @@ +package models + +type CreateCounterWithResetInput struct { + Counter CreateCounterInput `json:"counter"` + Reset CreateResetInput `json:"reset"` +} + +type CreateCounterInput struct { + Title string `json:"title"` + Description string `json:"description"` +} + +type DeleteCounterInput struct { + RowID string `json:"counter_id"` +} diff --git a/internal/models/api_reset.go b/internal/models/api_reset.go new file mode 100644 index 0000000..a4e724e --- /dev/null +++ b/internal/models/api_reset.go @@ -0,0 +1,11 @@ +package models + +type CreateResetInput struct { + CounterID string `json:"counter_id"` + Description string `json:"description"` + ResetTime string `json:"reset_time"` +} + +type DeleteResetInput struct { + RowID string `json:"reset_id"` +} diff --git a/internal/models/counter.go b/internal/models/counter.go new file mode 100644 index 0000000..2816e78 --- /dev/null +++ b/internal/models/counter.go @@ -0,0 +1,21 @@ +package models + +type CounterListPart struct { + CounterID string `json:"counter_id"` + CounterOwner string `json:"counter_owner"` + CounterTitle string `json:"counter_title"` + CounterDescription string `json:"counter_description"` + ResetDescription string `json:"reset_description"` + ResetUsername string `json:"reset_username"` + ResetOccuredAt int64 `json:"reset_occured_at"` +} + +type Counter struct { + CounterID string `json:"reset_id"` + UserID string `json:"user_id"` + Username string `json:"username"` + Title string `json:"title"` + Description string `json:"description"` + Timestamp int64 `json:"timestamp"` + LastReset *Reset `json:"last_reset"` +} diff --git a/internal/models/reset.go b/internal/models/reset.go new file mode 100644 index 0000000..cf90581 --- /dev/null +++ b/internal/models/reset.go @@ -0,0 +1,17 @@ +package models + +type Reset struct { + ResetId string `json:"reset_id"` + UserID string `json:"user_id"` + Username string `json:"username"` + Description string `json:"description"` + Timestamp int64 `json:"timestamp"` +} + +type ResetListPart struct { + ResetID string `json:"reset_id"` + ResetDescription string `json:"reset_description"` + ResetOwner string `json:"reset_owner"` + ResetUsername string `json:"reset_username"` + ResetOccuredAt int64 `json:"reset_occured_at"` +} diff --git a/internal/routes/api/util.go b/internal/routes/api/util.go new file mode 100644 index 0000000..6f9708b --- /dev/null +++ b/internal/routes/api/util.go @@ -0,0 +1,15 @@ +package api + +import "github.com/gin-gonic/gin" + +// thanks swizzle! +func CookieToHeaderAuth() gin.HandlerFunc { + return func(c *gin.Context) { + cookie, err := c.Cookie("Auth") + if err == nil && cookie != "" { + c.Request.Header.Set("Authorization", "Bearer "+cookie) + } + + c.Next() + } +} diff --git a/internal/routes/api/v1/counters/counter.go b/internal/routes/api/v1/counters/counter.go new file mode 100644 index 0000000..fc88b0f --- /dev/null +++ b/internal/routes/api/v1/counters/counter.go @@ -0,0 +1,124 @@ +package counters + +import ( + "net/http" + + "github.com/ComputerScienceHouse/counters/internal/database" + "github.com/ComputerScienceHouse/counters/internal/logging" + "github.com/ComputerScienceHouse/counters/internal/models" + "github.com/ComputerScienceHouse/counters/internal/users" + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" + "github.com/sirupsen/logrus" +) + +func GetCounter(c *gin.Context) { + user, err := users.GetCSHAuth(c) + if err != nil { + c.Status(http.StatusUnauthorized) + return + } + idToUse := c.Param("id") + + counter, err := database.GetCounterFromId(c.Request.Context(), idToUse) + if err != nil { + c.Status(http.StatusBadRequest) + return + } + + resets, err := database.GetResetsFromCounterId(c.Request.Context(), idToUse) + if err != nil { + c.Status(http.StatusBadRequest) + return + } + + c.HTML(200, "counter.tmpl", gin.H{ + "Id": counter.CounterID, + "CounterID": counter.CounterOwner, + "Title": counter.CounterTitle, + "Description": counter.CounterDescription, + "Timestamp": counter.ResetOccuredAt, + "History": resets, + "Username": user.Username, + "FullName": user.FullName, + "UserID": user.Uuid, + "EBoard": users.IsEboard(user), + "RTP": users.IsActiveRTP(user), + }) +} + +func DeleteCounter(c *gin.Context) { + user, err := users.GetCSHAuth(c) + if err != nil { + logging.Logger.WithFields(logrus.Fields{"module": "counter", "error": err}).Warning("Error getting CSH auth last reset") + c.Status(http.StatusUnauthorized) + return + } + + var req models.DeleteCounterInput + if err := c.ShouldBindJSON(&req); err != nil { + logging.Logger.WithFields(logrus.Fields{"module": "counter", "error": err}).Warning("Error casting request") + c.Status(http.StatusBadRequest) + return + } + + counterOwner, err := database.GetCounterOwner(c.Request.Context(), req.RowID) + if err != nil { + logging.Logger.WithFields(logrus.Fields{"module": "counter", "error": err}).Warning("Error getting counter owner") + c.Status(http.StatusBadRequest) + return + } + + sameUser := counterOwner == user.Uuid + if !users.IsEboard(user) && !users.IsActiveRTP(user) && !sameUser { + logging.Logger.WithFields(logrus.Fields{"module": "counter", "error": err}).Warning("Error getting user permissions") + c.Status(http.StatusUnauthorized) + return + } + + if err := database.DeleteCounter(c.Request.Context(), req.RowID); err != nil { + logging.Logger.WithFields(logrus.Fields{"module": "counter", "error": err}).Warning("Error deleting counter") + c.Status(http.StatusInternalServerError) + return + } + c.Status(http.StatusNoContent) +} + +func CreateCounter(c *gin.Context) { + user, err := users.GetCSHAuth(c) + if err != nil { + c.Status(http.StatusUnauthorized) + return + } + + var req models.CreateCounterWithResetInput + + if err := c.ShouldBindBodyWith(&req, binding.JSON); err != nil { + body, _ := c.Get(gin.BodyBytesKey) + raw, _ := body.([]byte) + + logging.Logger.Warnf("bad request for creating counter! %s %s", + err, + string(raw), + ) + c.Status(http.StatusBadRequest) + return + } + + counterID, err := database.CreateCounterWithReset(c.Request.Context(), user.Uuid, user.Username, &req.Counter, &req.Reset) + if err != nil { + logging.Logger.WithFields(logrus.Fields{"module": "counters", "error": err}).Warning("Error creating new database") + c.Status(http.StatusBadRequest) + return + } + + c.JSON(http.StatusOK, counterID) +} + +func Routes(r *gin.RouterGroup) { + cGroup := r.Group("/counter") + + cGroup.GET("/:id") + cGroup.POST("", CreateCounter) + cGroup.DELETE("", DeleteCounter) +} diff --git a/internal/routes/api/v1/reset/reset.go b/internal/routes/api/v1/reset/reset.go new file mode 100644 index 0000000..4f9c976 --- /dev/null +++ b/internal/routes/api/v1/reset/reset.go @@ -0,0 +1,112 @@ +package reset + +import ( + "net/http" + + "github.com/ComputerScienceHouse/counters/internal/database" + "github.com/ComputerScienceHouse/counters/internal/logging" + "github.com/ComputerScienceHouse/counters/internal/models" + "github.com/ComputerScienceHouse/counters/internal/users" + "github.com/ComputerScienceHouse/counters/internal/util" + "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" +) + +func CreateReset(c *gin.Context) { + user, err := users.GetCSHAuth(c) + if err != nil { + c.Status(http.StatusUnauthorized) + return + } + + var req models.CreateResetInput + if err := c.ShouldBindJSON(&req); err != nil { + logging.Logger.Warnf("failed to cast error %s", err) + c.Status(http.StatusBadRequest) + return + } + + patchedResetTime := util.TranslateTime(req.ResetTime) + + if _, err := database.CreateReset(c.Request.Context(), user.Uuid, user.Username, patchedResetTime, &req); err != nil { + logging.Logger.Warnf("failed to create error %s", err) + c.Status(http.StatusBadRequest) + return + } + + if _, err := database.UpdateCounterLastReset(c.Request.Context(), req.CounterID); err != nil { + logging.Logger.WithFields(logrus.Fields{"module": "reset", "error": err}).Warning("Error updating last reset") + c.Status(http.StatusInternalServerError) + return + } + + c.Status(http.StatusNoContent) +} + +func DeleteReset(c *gin.Context) { + user, err := users.GetCSHAuth(c) + if err != nil { + logging.Logger.WithFields(logrus.Fields{"module": "reset", "error": err}).Warning("Error getting CSHAuth reset") + c.Status(http.StatusUnauthorized) + return + } + + var req models.DeleteResetInput + if err := c.ShouldBindJSON(&req); err != nil { + logging.Logger.WithFields(logrus.Fields{"module": "reset", "error": err}).Warning("Error compiling reset") + c.Status(http.StatusBadRequest) + return + } + + counterID, err := database.GetCounterFromReset(c.Request.Context(), req.RowID) + if err != nil { + logging.Logger.WithFields(logrus.Fields{"module": "reset", "error": err}).Warning("Error getting counter from reset") + c.Status(http.StatusBadRequest) + return + } + + counterOwner, err := database.GetCounterOwner(c.Request.Context(), counterID) + if err != nil { + logging.Logger.WithFields(logrus.Fields{"module": "reset", "error": err}).Warning("Error getting counter owner") + c.Status(http.StatusBadRequest) + return + } + + resetOwner, err := database.GetResetOwner(c.Request.Context(), req.RowID) + if err != nil { + logging.Logger.WithFields(logrus.Fields{"module": "reset", "error": err}).Warning("Error deleting reset") + c.Status(http.StatusBadRequest) + return + } + + isCounterOwner := counterOwner == user.Uuid + isResetOwner := resetOwner == user.Uuid + + if !users.IsEboard(user) && !users.IsActiveRTP(user) && !isCounterOwner && !isResetOwner { + c.Status(http.StatusUnauthorized) + return + } + + if err := database.DeleteReset(c.Request.Context(), counterID, req.RowID); err != nil { + logging.Logger.WithFields(logrus.Fields{"module": "reset", "error": err}).Warning("Error deleting reset") + c.Status(http.StatusInternalServerError) + return + } + + counterExists, err := database.UpdateCounterLastReset(c.Request.Context(), counterID) + if err != nil { + logging.Logger.WithFields(logrus.Fields{"module": "reset", "error": err}).Warning("Error updating last reset") + c.Status(http.StatusInternalServerError) + return + } + + c.JSON(http.StatusOK, counterExists) +} + +func Routes(r *gin.RouterGroup) { + rGroup := r.Group("/reset") + + rGroup.GET("/:id") + rGroup.POST("", CreateReset) // create reset + rGroup.DELETE("", DeleteReset) +} diff --git a/internal/routes/api/v1/routes.go b/internal/routes/api/v1/routes.go new file mode 100644 index 0000000..84b2f59 --- /dev/null +++ b/internal/routes/api/v1/routes.go @@ -0,0 +1,15 @@ +package v1 + +import ( + "github.com/ComputerScienceHouse/counters/internal/routes/api" + "github.com/ComputerScienceHouse/counters/internal/routes/api/v1/counters" + "github.com/ComputerScienceHouse/counters/internal/routes/api/v1/reset" + "github.com/gin-gonic/gin" +) + +func Routes(r *gin.RouterGroup) { + v1Group := r.Group("/api/v1", api.CookieToHeaderAuth()) + + counters.Routes(v1Group) + reset.Routes(v1Group) +} diff --git a/internal/routes/web/pages/counter.go b/internal/routes/web/pages/counter.go new file mode 100644 index 0000000..7138f20 --- /dev/null +++ b/internal/routes/web/pages/counter.go @@ -0,0 +1,55 @@ +package pages + +import ( + "net/http" + + "github.com/ComputerScienceHouse/counters/internal/database" + "github.com/ComputerScienceHouse/counters/internal/logging" + "github.com/ComputerScienceHouse/counters/internal/models" + "github.com/ComputerScienceHouse/counters/internal/users" + "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" +) + +func GetCounterPage(c *gin.Context) { + user, err := users.GetCSHAuth(c) + if err != nil { + c.Status(http.StatusUnauthorized) + } + + idToUse := c.Param("id") + + counter, err := database.GetCounterFromId(c.Request.Context(), idToUse) + if err != nil { + logging.Logger.WithFields(logrus.Fields{"module": "counter page", "error": err}).Warning("Error geting counter") + c.JSON(http.StatusBadRequest, models.NewErrorResponse()) + return + } + + if counter == nil { + c.Redirect(http.StatusNotFound, "/") + } + + history, err := database.GetResetsFromCounterId(c.Request.Context(), idToUse) + if err != nil { + logging.Logger.WithFields(logrus.Fields{"module": "reset", "error": err}).Warning("Error getting history for counter") + c.JSON(http.StatusBadRequest, models.NewErrorResponse()) + return + } + + c.HTML(http.StatusOK, "counter.tmpl", gin.H{ + "Id": counter.CounterID, + "CounterID": counter.CounterOwner, + "Title": counter.CounterTitle, + "Description": counter.CounterDescription, + "Timestamp": counter.ResetOccuredAt, + + "History": history, + "Username": user.Username, + "FullName": user.FullName, + "UserID": user.Uuid, + + "EBoard": users.IsEboard(user), + "RTP": users.IsActiveRTP(user), + }) +} diff --git a/internal/routes/web/pages/create.go b/internal/routes/web/pages/create.go new file mode 100644 index 0000000..1264c3e --- /dev/null +++ b/internal/routes/web/pages/create.go @@ -0,0 +1,22 @@ +package pages + +import ( + "net/http" + + "github.com/ComputerScienceHouse/counters/internal/users" + "github.com/gin-gonic/gin" +) + +func GetCreatePage(c *gin.Context) { + user, err := users.GetCSHAuth(c) + if err != nil { + c.Status(http.StatusUnauthorized) + } + + c.HTML(http.StatusOK, "create.tmpl", gin.H{ + "Username": user.Username, + "FullName": user.FullName, + "EBoard": users.IsEboard(user), + "RTP": users.IsActiveRTP(user), + }) +} diff --git a/internal/routes/web/pages/home.go b/internal/routes/web/pages/home.go new file mode 100644 index 0000000..be28ac9 --- /dev/null +++ b/internal/routes/web/pages/home.go @@ -0,0 +1,39 @@ +package pages + +import ( + "net/http" + "sort" + + "github.com/ComputerScienceHouse/counters/internal/database" + "github.com/ComputerScienceHouse/counters/internal/logging" + "github.com/ComputerScienceHouse/counters/internal/users" + "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" +) + +func GetHomePage(c *gin.Context) { + user, err := users.GetCSHAuth(c) + if err != nil { + c.Status(http.StatusUnauthorized) + } + + counters, err := database.GetCounters(c.Request.Context()) + + if err != nil { + logging.Logger.WithFields(logrus.Fields{"error": err, "module": "api", "method": "GetHomePage"}).Warn("Unable to load counters!") + c.JSON(http.StatusInternalServerError, gin.H{"error": "Unable to load counters!"}) + return + } + + sort.Slice(counters, func(i, j int) bool { + return counters[i].ResetOccuredAt > counters[j].ResetOccuredAt + }) + + c.HTML(http.StatusOK, "index.html", gin.H{ + "Counters": counters, + "Username": user.Username, + "FullName": user.FullName, + "EBoard": users.IsEboard(user), + "RTP": users.IsActiveRTP(user), + }) +} diff --git a/internal/routes/web/pages/reset.go b/internal/routes/web/pages/reset.go new file mode 100644 index 0000000..eafdc17 --- /dev/null +++ b/internal/routes/web/pages/reset.go @@ -0,0 +1,24 @@ +package pages + +import ( + "net/http" + + "github.com/ComputerScienceHouse/counters/internal/users" + "github.com/gin-gonic/gin" +) + +func GetResetPage(c *gin.Context) { + user, err := users.GetCSHAuth(c) + if err != nil { + c.Status(http.StatusUnauthorized) + } + + idToUse := c.Param("id") + c.HTML(http.StatusOK, "reset.tmpl", gin.H{ + "Id": idToUse, + "Username": user.Username, + "FullName": user.FullName, + "EBoard": users.IsEboard(user), + "RTP": users.IsActiveRTP(user), + }) +} diff --git a/internal/routes/web/routes.go b/internal/routes/web/routes.go new file mode 100644 index 0000000..32be5de --- /dev/null +++ b/internal/routes/web/routes.go @@ -0,0 +1,13 @@ +package web + +import ( + "github.com/ComputerScienceHouse/counters/internal/routes/web/pages" + "github.com/gin-gonic/gin" +) + +func Routes(r *gin.RouterGroup) { + r.GET("/", pages.GetHomePage) + r.GET("/create", pages.GetCreatePage) + r.GET("/reset/:id", pages.GetResetPage) + r.GET("/counter/:id", pages.GetCounterPage) +} diff --git a/internal/users/users.go b/internal/users/users.go new file mode 100644 index 0000000..ac647ca --- /dev/null +++ b/internal/users/users.go @@ -0,0 +1,64 @@ +package users + +import ( + "errors" + "os" + "slices" + "strconv" + + "github.com/ComputerScienceHouse/counters/internal/logging" + "github.com/gin-gonic/gin" + + csh_auth "github.com/computersciencehouse/csh-auth/v2" +) + +func IsEboard(user *csh_auth.UserInfo) bool { + val, set := os.LookupEnv("DEV_FORCE_IS_EBOARD") + if set { + forced, err := strconv.ParseBool(val) + if err == nil { + logging.Logger.Info("Forced Override for Eboard") + return forced + } + logging.Logger.Warn("FORCED EBOARD WAS MISTYPED, MAKE SURE IT'S EITHER true OR false") + } + + return slices.Contains(user.Groups, "eboard") +} + +func IsActiveRTP(user *csh_auth.UserInfo) bool { + val, set := os.LookupEnv("DEV_FORCE_IS_RTP") + if set { + forced, err := strconv.ParseBool(val) + if err == nil { + logging.Logger.Info("Forced Override for RTP") + return forced + } + logging.Logger.Warn("FORCED RTP WAS MISTYPED, MAKE SURE IT'S EITHER true OR false") + } + + return slices.Contains(user.Groups, "active-rtp") +} + +func GetCSHAuth(c *gin.Context) (*csh_auth.UserInfo, error) { + userAny, exists := c.Get("cshauth") + + if !exists { + return nil, errors.New("unable to load csh auth") + } + + userClaims, ok := userAny.(*csh_auth.Claims) + if !ok { + return nil, errors.New("unable to cast csh auth") + } + + user := &csh_auth.UserInfo{ + Uuid: userClaims.Uuid, + Email: userClaims.Email, + Username: userClaims.Username, + FullName: userClaims.FullName, + Groups: userClaims.Groups, + } + + return user, nil +} diff --git a/internal/util/util.go b/internal/util/util.go new file mode 100644 index 0000000..36fb2b4 --- /dev/null +++ b/internal/util/util.go @@ -0,0 +1,26 @@ +package util + +import ( + "time" +) + +func TranslateTime(inputTime string) int64 { + timeZone, _ := time.LoadLocation("America/New_York") + + var finalTime int64 = 0 + currentTime := time.Now().Unix() + + timeConverted, err := time.ParseInLocation("2006-01-02T15:04", inputTime, timeZone) + + finalTime = timeConverted.Unix() + + if err != nil { + finalTime = currentTime + } + + if finalTime > currentTime { + finalTime = currentTime + } + + return finalTime +} diff --git a/main.go b/main.go index 3f02c92..7029998 100644 --- a/main.go +++ b/main.go @@ -4,10 +4,12 @@ import ( "net/http" "os" - "github.com/WeatherGod3218/counters/database" + "github.com/ComputerScienceHouse/counters/internal/database" + v1 "github.com/ComputerScienceHouse/counters/internal/routes/api/v1" + "github.com/ComputerScienceHouse/counters/internal/routes/web" "github.com/gin-gonic/gin" - "github.com/WeatherGod3218/counters/logging" + "github.com/ComputerScienceHouse/counters/internal/logging" "github.com/sirupsen/logrus" cshAuth "github.com/computersciencehouse/csh-auth/v2" @@ -16,7 +18,6 @@ import ( var DEV_FORCE_IS_EBOARD bool = os.Getenv("DEV_FORCE_IS_EBOARD") == "true" func main() { - database.Client = database.Connect() hostUrl := os.Getenv("SERVER_HOST") auth, err := cshAuth.Init( @@ -32,6 +33,9 @@ func main() { logging.Logger.WithFields(logrus.Fields{"error": err, "module": "main", "method": "main"}).Fatal("error initializing csh-auth") } + if err := database.InitDatabase(); err != nil { + logging.Logger.WithFields(logrus.Fields{"error": err, "module": "main", "method": "main"}).Fatal("error initializing database") + } router := gin.Default() router.StaticFS("/static", http.Dir("static")) @@ -43,17 +47,8 @@ func main() { router.Use(auth.CookieMiddleware()) - router.GET("/", GetHomePage) - router.GET("/counters/:id", LoadCounter) - - router.GET("/create", GetCreatePage) - router.POST("/create", CreateCounter) - - router.GET("/reset/:id", GetResetPage) - router.POST("/reset/:id", ResetCounter) - - router.POST("/delete/counter", DeleteCounter) - router.POST("/delete/reset", DeleteReset) + web.Routes(router.Group("")) + v1.Routes(router.Group("")) router.Run(":8080") } diff --git a/static/material_lock.svg b/static/material_lock.svg deleted file mode 100644 index f72f7b5..0000000 --- a/static/material_lock.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - diff --git a/templates/counter.tmpl b/templates/counter.tmpl index a92e345..9bd7469 100644 --- a/templates/counter.tmpl +++ b/templates/counter.tmpl @@ -11,10 +11,9 @@
Are you sure you want to delete this reset? You can always create a Reset with identical traits later.