diff --git a/.mockery.yaml b/.mockery.yaml deleted file mode 100644 index f9b81680..00000000 --- a/.mockery.yaml +++ /dev/null @@ -1,8 +0,0 @@ -with-expecter: True -mockname: "{{.InterfaceName}}" -filename: "{{.MockName}}.go" -packages: - github.com/mraron/njudge/pkg/problems: - interfaces: - Judgeable: - TaskType: \ No newline at end of file diff --git a/go.mod b/go.mod index e4ee37fe..d62ab082 100644 --- a/go.mod +++ b/go.mod @@ -7,13 +7,13 @@ require ( github.com/DATA-DOG/go-sqlmock v1.5.0 // indirect github.com/friendsofgo/errors v0.9.2 github.com/gofrs/uuid v4.2.0+incompatible // indirect - github.com/golang-jwt/jwt v3.2.2+incompatible + github.com/golang-jwt/jwt v3.2.2+incompatible // indirect github.com/golang-migrate/migrate/v4 v4.15.1 github.com/gorilla/sessions v1.2.1 github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/labstack/echo-contrib v0.12.0 - github.com/labstack/echo/v4 v4.10.0 - github.com/labstack/gommon v0.4.0 // indirect + github.com/labstack/echo/v4 v4.11.4 + github.com/labstack/gommon v0.4.2 // indirect github.com/lib/pq v1.10.6 github.com/markbates/goth v1.69.0 github.com/mattn/go-colorable v0.1.13 // indirect @@ -26,59 +26,74 @@ require ( github.com/volatiletech/randomize v0.0.1 // indirect github.com/volatiletech/sqlboiler/v4 v4.15.0 github.com/volatiletech/strmangle v0.0.6 - golang.org/x/crypto v0.17.0 + golang.org/x/crypto v0.22.0 golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) require ( + github.com/a-h/templ v0.2.697 github.com/antonlindstrom/pgstore v0.0.0-20220421113606-e3a6e3fed12a + github.com/deepmap/oapi-codegen/v2 v2.1.0 github.com/erni27/imcache v1.2.0 - github.com/fsnotify/fsnotify v1.6.0 + github.com/fsnotify/fsnotify v1.7.0 + github.com/getkin/kin-openapi v0.122.0 github.com/karrick/gobls v1.3.5 + github.com/oapi-codegen/echo-middleware v1.0.1 + github.com/oapi-codegen/runtime v1.1.1 github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b github.com/samber/slog-echo v1.12.3 github.com/spf13/pflag v1.0.5 - github.com/stretchr/testify v1.8.4 + github.com/stretchr/testify v1.9.0 github.com/yuin/goldmark v1.4.13 - golang.org/x/net v0.17.0 - golang.org/x/sync v0.1.0 + golang.org/x/net v0.24.0 + golang.org/x/sync v0.3.0 golang.org/x/text v0.14.0 - golang.org/x/time v0.2.0 + golang.org/x/time v0.5.0 gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df gopkg.in/yaml.v3 v3.0.1 ) require ( + github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/ericlagergren/decimal v0.0.0-20190420051523-6335edbaa640 // indirect + github.com/go-openapi/jsonpointer v0.19.6 // indirect + github.com/go-openapi/swag v0.22.4 // indirect github.com/golang/protobuf v1.5.2 // indirect + github.com/google/uuid v1.5.0 // indirect github.com/gorilla/context v1.1.1 // indirect github.com/gorilla/mux v1.8.0 // indirect github.com/gorilla/securecookie v1.1.1 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.0.1 // indirect + github.com/invopop/yaml v0.2.0 // indirect + github.com/josharian/intern v1.0.0 // indirect github.com/magiconair/properties v1.8.6 // indirect - github.com/mattn/go-isatty v0.0.19 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/pelletier/go-toml v1.9.5 // indirect - github.com/pelletier/go-toml/v2 v2.0.5 // indirect + github.com/pelletier/go-toml/v2 v2.0.9 // indirect + github.com/perimeterx/marshmallow v1.1.5 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/samber/lo v1.38.1 // indirect github.com/spf13/cast v1.5.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/subosito/gotenv v1.4.1 // indirect + github.com/ugorji/go/codec v1.2.12 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect github.com/volatiletech/inflect v0.0.1 // indirect - go.opentelemetry.io/otel v1.19.0 // indirect - go.opentelemetry.io/otel/trace v1.19.0 // indirect - go.uber.org/atomic v1.9.0 // indirect + go.opentelemetry.io/otel v1.26.0 // indirect + go.opentelemetry.io/otel/trace v1.26.0 // indirect + go.uber.org/atomic v1.11.0 // indirect golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect - golang.org/x/mod v0.8.0 // indirect - golang.org/x/sys v0.15.0 // indirect - golang.org/x/tools v0.6.0 // indirect + golang.org/x/mod v0.17.0 // indirect + golang.org/x/sys v0.19.0 // indirect + golang.org/x/tools v0.13.0 // indirect golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.33.0 // indirect diff --git a/go.sum b/go.sum index b0604d2f..3f48f804 100644 --- a/go.sum +++ b/go.sum @@ -117,7 +117,12 @@ github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb0 github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= +github.com/a-h/templ v0.2.680 h1:TflYFucxp5rmOxAXB9Xy3+QHTk8s8xG9+nCT/cLzjeE= +github.com/a-h/templ v0.2.680/go.mod h1:NQGQOycaPKBxRB14DmAaeIpcGC1AOBPJEMO4ozS7m90= +github.com/a-h/templ v0.2.697 h1:OILxtWvD0NRJaoCOiZCopRDPW8paroKlGsrAiHLykNE= +github.com/a-h/templ v0.2.697/go.mod h1:5cqsugkq9IerRNucNsI4DEamdHPsoGMQy99DzydLhM8= github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -130,6 +135,8 @@ github.com/antonlindstrom/pgstore v0.0.0-20220421113606-e3a6e3fed12a h1:dIdcLbck github.com/antonlindstrom/pgstore v0.0.0-20220421113606-e3a6e3fed12a/go.mod h1:Sdr/tmSOLEnncCuXS5TwZRxuk7deH1WXVY8cve3eVBM= github.com/apache/arrow/go/arrow v0.0.0-20210818145353-234c94e4ce64/go.mod h1:2qMFB56yOP3KzkB3PbYZ4AlUFg3a88F67TIx5lB/WwY= github.com/apache/arrow/go/arrow v0.0.0-20211013220434-5962184e7a30/go.mod h1:Q7yQnSMnLvcXlZ8RV+jwz/6y1rQTqbX6C82SndT52Zs= +github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ= +github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= github.com/apmckinlay/gsuneido v0.0.0-20190404155041-0b6cd442a18f/go.mod h1:JU2DOj5Fc6rol0yaT79Csr47QR0vONGwJtBNGRD7jmc= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= @@ -177,6 +184,7 @@ github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edY github.com/bkaradzic/go-lz4 v1.0.0/go.mod h1:0YdlkowM3VswSROI7qDxhRvJ3sLhlFrRRwjwegp5jy4= github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= @@ -328,6 +336,8 @@ github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4/go.mod h1:bMl4RjI 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/deepmap/oapi-codegen/v2 v2.1.0 h1:I/NMVhJCtuvL9x+S2QzZKpSjGi33oDZwPRdemvOZWyQ= +github.com/deepmap/oapi-codegen/v2 v2.1.0/go.mod h1:R1wL226vc5VmCNJUvMyYr3hJMm5reyv25j952zAVXZ8= github.com/denisenkom/go-mssqldb v0.10.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0= github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= @@ -391,13 +401,15 @@ github.com/friendsofgo/errors v0.9.2/go.mod h1:yCvFW5AkDIL9qn7suHVLiI/gH228n7PC4 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= -github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= -github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/fsouza/fake-gcs-server v1.17.0/go.mod h1:D1rTE4YCyHFNa99oyJJ5HyclvN/0uQR+pM/VdlL83bw= github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA= github.com/gabriel-vasile/mimetype v1.3.1/go.mod h1:fA8fi6KUiG7MgQQ+mEWotXoEOvmxRtOJlERCzSmRvr8= github.com/gabriel-vasile/mimetype v1.4.0/go.mod h1:fA8fi6KUiG7MgQQ+mEWotXoEOvmxRtOJlERCzSmRvr8= github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= +github.com/getkin/kin-openapi v0.122.0 h1:WB9Jbl0Hp/T79/JF9xlSW5Kl9uYdk/AWD0yAd9HOM10= +github.com/getkin/kin-openapi v0.122.0/go.mod h1:PCWw/lfBrJY4HcdqE3jj+QFkaFK8ABoqo7PvqVhXXqw= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g= @@ -419,15 +431,22 @@ github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7 github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= +github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= +github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY= github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg= @@ -538,8 +557,8 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-github/v35 v35.2.0/go.mod h1:s0515YVTI+IMrDoy9Y4pHt9ShGpzHvHO8rZ7L7acgvs= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -571,6 +590,8 @@ github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= +github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= 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= @@ -657,6 +678,8 @@ github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/invopop/yaml v0.2.0 h1:7zky/qH+O0DwAyoobXUqvVBwgBFRxKoQ/3FjcVpjTMY= +github.com/invopop/yaml v0.2.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q= github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA= github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= @@ -713,6 +736,8 @@ github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhB github.com/jmoiron/sqlx v1.3.1/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -723,6 +748,7 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= @@ -763,10 +789,10 @@ github.com/ktrysmt/go-bitbucket v0.6.4/go.mod h1:9u0v3hsd2rqCHRIpbir1oP7F58uo5dq github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/labstack/echo-contrib v0.12.0 h1:NPr1ez+XUa5s/4LujEon+32Bxg5DO6EKSW/va06pmLc= github.com/labstack/echo-contrib v0.12.0/go.mod h1:kR62TbwsBgmpV2HVab5iQRsQtLuhPyGqCBee88XRc4M= -github.com/labstack/echo/v4 v4.10.0 h1:5CiyngihEO4HXsz3vVsJn7f8xAlWwRr3aY6Ih280ZKA= -github.com/labstack/echo/v4 v4.10.0/go.mod h1:S/T/5fy/GigaXnHTkh0ZGe4LpkkQysvRjFMSUTkDRNQ= -github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8= -github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= +github.com/labstack/echo/v4 v4.11.4 h1:vDZmA+qNeh1pd/cCkEicDMrjtrnMGQ1QFI9gWN1zGq8= +github.com/labstack/echo/v4 v4.11.4/go.mod h1:noh7EvLwqDsmh/X/HWKPUl1AjzJrhyptRyEbQJfxen8= +github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= +github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= github.com/lestrrat-go/jwx v0.9.0/go.mod h1:iEoxlYfZjvoGpuWwxUz+eR5e6KTJGsaRcy/YNA/UnBk= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= @@ -782,6 +808,8 @@ github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPK github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/markbates/going v1.0.0/go.mod h1:I6mnB4BPnEeqo85ynXIx1ZFLLbtiLHNXVgWeFO9OGOA= github.com/markbates/goth v1.69.0 h1:HoXdRES8Hfx4H4ICM27Im+IuVubflaAX7mXCmYHiWIw= github.com/markbates/goth v1.69.0/go.mod h1:uk3KIdtCKdmyNABgOSmHFNHN0AcKqkLs8j5Ak3Ioe1Q= @@ -795,7 +823,6 @@ github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVc github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= @@ -811,8 +838,8 @@ github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOA github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= @@ -852,6 +879,8 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/montanaflynn/stats v0.6.6/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= @@ -870,6 +899,10 @@ github.com/nakagami/firebirdsql v0.0.0-20190310045651-3c02a58cfed8/go.mod h1:86w github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= github.com/neo4j/neo4j-go-driver v1.8.1-0.20200803113522-b626aa943eba/go.mod h1:ncO5VaFWh0Nrt+4KT4mOZboaczBZcLuHrG+/sUeP8gI= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/oapi-codegen/echo-middleware v1.0.1 h1:edYGScq1phCcuDoz9AqA9eHX+tEI1LNL5PL1lkkQh1k= +github.com/oapi-codegen/echo-middleware v1.0.1/go.mod h1:DBQKRn+D/vfXOFbaX5GRwFttoJY64JH6yu+pdt7wU3o= +github.com/oapi-codegen/runtime v1.1.1 h1:EXLHh0DXIJnWhdRPN2w4MXAzFyE4CskzhNLUmtpMYro= +github.com/oapi-codegen/runtime v1.1.1/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -919,8 +952,11 @@ github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrap github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= -github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg= github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas= +github.com/pelletier/go-toml/v2 v2.0.9 h1:uH2qQXheeefCCkuBBSLi7jCiSmj3VRh2+Goq2N7Xxu0= +github.com/pelletier/go-toml/v2 v2.0.9/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= +github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY= github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= @@ -1050,12 +1086,14 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/spf13/viper v1.12.0 h1:CZ7eSOd3kZoaYDLbXnmzgQI5RlciuXBMA+18HwHRfZQ= github.com/spf13/viper v1.12.0/go.mod h1:b6COn30jlNxbm/V2IqWiNWkJ+vZNiMNksliPCiuKtSI= +github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8= github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= 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/testify v0.0.0-20180303142811-b89eecf5ca5d/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= @@ -1066,8 +1104,10 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.3.0/go.mod h1:YzJjq/33h7nrwdY+iHMhEOEEbW0ovIz0tB6t6PwAXzs= github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= @@ -1080,13 +1120,14 @@ github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1 github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= +github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= @@ -1148,17 +1189,17 @@ go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= -go.opentelemetry.io/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs= -go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY= -go.opentelemetry.io/otel/trace v1.19.0 h1:DFVQmlVbfVeOuBRrwdtaehRrWiL1JoVs9CPIQ1Dzxpg= -go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo= +go.opentelemetry.io/otel v1.26.0 h1:LQwgL5s/1W7YiiRwxf03QGnWLb2HW4pLiAhaA5cZXBs= +go.opentelemetry.io/otel v1.26.0/go.mod h1:UmLkJHUAidDval2EICqBMbnAd0/m2vmpf/dAM+fvFs4= +go.opentelemetry.io/otel/trace v1.26.0 h1:1ieeAUb4y0TE26jUFrCIXKpTuVK7uJGN9/Z/2LP5sQA= +go.opentelemetry.io/otel/trace v1.26.0/go.mod h1:4iDxvGDQuUkHve82hJJ8UqrwswHYsZuWCBllGV2U2y0= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= -go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= @@ -1195,8 +1236,8 @@ golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220511200225-c6db032c6c88/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= -golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= +golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1233,6 +1274,7 @@ golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRu golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= @@ -1246,8 +1288,8 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1316,8 +1358,8 @@ golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= +golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= golang.org/x/oauth2 v0.0.0-20180227000427-d7d64896b5ff/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -1355,8 +1397,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220513210516-0976fa681c29/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.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20180224232135-f6cff0780e54/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1467,7 +1509,6 @@ golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1482,10 +1523,9 @@ 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.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1504,8 +1544,8 @@ golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.2.0 h1:52I/1L54xyEQAYdtcSuxtiT84KGYTBGXwayxmIpNJhE= -golang.org/x/time v0.2.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -1580,8 +1620,8 @@ golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/gulpfile.js b/gulpfile.js index a217b047..ff5fe8e2 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -20,18 +20,40 @@ function mainCSS() { safelist: { deep: [/^modal/] } - })).pipe(cleanCSS({compatibility: 'ie8'})).pipe(concat('main.min.css')).pipe(dest("static/css")) + })).pipe(src('node_modules/select2/dist/css/select2.min.css')) + .pipe(src('node_modules/@ttskch/select2-bootstrap4-theme/dist/select2-bootstrap4.min.css')) + .pipe(cleanCSS({compatibility: 'ie8'})).pipe(concat('main.min.css')).pipe(dest("static/css")) } function mainJS() { return src("src/js/*.js").pipe(dest("static/js")) } +function bootstrapJS() { + return src('node_modules/bootstrap/dist/js/bootstrap.min.js', {sourcemaps: true}).pipe(dest('static/js', {sourcemaps: '.'})) +} + +function bootstrapIcons() { + return src('node_modules/bootstrap-icons/**/*').pipe(dest('static/bootstrap-icons')) +} + +function selectJS() { + return src('node_modules/select2/dist/js/select2.min.js', {sourcemaps: true}).pipe(dest('static/js', {sourcemaps: '.'})) +} + +function jqueryJS() { + return src('node_modules/jquery/dist/jquery.slim.min.js', {sourcemaps: true}).pipe(dest('static/js', {sourcemaps: '.'})) +} + +function katex() { + return src('node_modules/katex/dist/**/*').pipe(dest('static/katex')) +} + function mainFavicon() { return src("src/favicon.ico").pipe(src("src/*.png")).pipe(src("src/site.webmanifest")).pipe(dest("static")) } const admin = parallel(adminCSS, adminJS) -const main = parallel(mainCSS, mainJS, mainFavicon) +const main = parallel(mainCSS, mainJS, mainFavicon, bootstrapJS, selectJS, jqueryJS, katex, bootstrapIcons) exports.default = parallel(admin, main) \ No newline at end of file diff --git a/internal/njudge/problemlist.go b/internal/njudge/problemlist.go index ba0655e2..db70e7fe 100644 --- a/internal/njudge/problemlist.go +++ b/internal/njudge/problemlist.go @@ -6,7 +6,7 @@ import ( type SortDirection string -var ( +const ( SortASC SortDirection = "ASC" SortDESC SortDirection = "DESC" ) diff --git a/internal/web/env.go b/internal/web/env.go index 1755ba74..92303c67 100644 --- a/internal/web/env.go +++ b/internal/web/env.go @@ -5,6 +5,7 @@ import ( "database/sql" "fmt" "github.com/mraron/njudge/internal/njudge/cached" + templates2 "github.com/mraron/njudge/internal/web/templates" "github.com/mraron/njudge/pkg/language/langs/cpp" "log" "net/http" @@ -23,7 +24,6 @@ import ( "github.com/mraron/njudge/internal/njudge/email" "github.com/mraron/njudge/internal/njudge/memory" "github.com/mraron/njudge/internal/web/helpers/templates" - "github.com/mraron/njudge/internal/web/helpers/templates/partials" "github.com/mraron/njudge/pkg/problems" "github.com/quasoft/memstore" "github.com/volatiletech/sqlboiler/v4/boil" @@ -34,7 +34,7 @@ func (s *Server) SetupDataAccess() { _ = s.ProblemStore.UpdateProblems() if s.Mode == "demo" { - s.PartialsStore = partials.Empty{} + s.PartialsStore = templates2.Empty{} s.Categories = memory.NewCategories() s.Tags = memory.NewTags() @@ -79,10 +79,11 @@ func (s *Server) SetupDataAccess() { sub.Verdict = njudge.VerdictAC sdata, _ := prob.WithStoredData(s.ProblemStore) ss, _ := sdata.StatusSkeleton("") + ss.Compiled = true sub.Status = *ss sub, _ = s.Submissions.Insert(context.Background(), *sub) } else { - s.PartialsStore = partials.NewCached(s.DB, 1*time.Minute) + s.PartialsStore = templates2.NewCached(s.DB, 1*time.Minute) s.Categories = db.NewCategories(s.DB) s.Tags = db.NewTags(s.DB) @@ -128,7 +129,6 @@ func (s *Server) SetupEnvironment() { } s.SetupDataAccess() - s.Keys.MustParse() if s.SMTP.Enabled { port, err := strconv.Atoi(s.SMTP.MailServerPort) @@ -228,6 +228,9 @@ func (s *Server) setupEcho() { s.e.Use(middleware.Logger()) s.e.Use(middleware.Recover()) s.e.Use(session.Middleware(store)) + s.e.Use(middleware.GzipWithConfig(middleware.GzipConfig{ + Level: 5, + })) s.prepareRoutes(s.e) diff --git a/internal/web/handlers/api/judges.go b/internal/web/handlers/api/judges.go index 12dd200b..10e2ab7a 100644 --- a/internal/web/handlers/api/judges.go +++ b/internal/web/handlers/api/judges.go @@ -3,10 +3,9 @@ package api import ( "context" "database/sql" + "github.com/mraron/njudge/internal/web/templates" "github.com/mraron/njudge/internal/njudge/db/models" - "github.com/mraron/njudge/internal/web/helpers/pagination" - "github.com/volatiletech/sqlboiler/v4/boil" . "github.com/volatiletech/sqlboiler/v4/queries/qm" ) @@ -23,7 +22,7 @@ func (JudgeDataProvider) Identifier() string { return "id" } -func (dp JudgeDataProvider) List(data *pagination.Data) ([]*models.Judge, error) { +func (dp JudgeDataProvider) List(data *templates.PaginationData) ([]*models.Judge, error) { qms := make([]QueryMod, 0) if data.SortField != "" { qms = append(qms, OrderBy(data.SortField+" "+data.SortDir)) diff --git a/internal/web/handlers/api/methods.go b/internal/web/handlers/api/methods.go index 9513a41a..ce7d2365 100644 --- a/internal/web/handlers/api/methods.go +++ b/internal/web/handlers/api/methods.go @@ -1,13 +1,12 @@ package api import ( + "github.com/mraron/njudge/internal/web/templates" "net/http" "net/url" "strconv" "github.com/mraron/njudge/internal/njudge" - "github.com/mraron/njudge/internal/web/helpers" - "github.com/mraron/njudge/internal/web/helpers/pagination" "github.com/mraron/njudge/internal/web/helpers/roles" "github.com/labstack/echo/v4" @@ -18,7 +17,7 @@ type Provider[T any] interface { Identifier() string - List(*pagination.Data) ([]*T, error) + List(*templates.PaginationData) ([]*T, error) Count() (int64, error) Get(string) (*T, error) } @@ -36,10 +35,10 @@ func GetList[T any](dp Provider[T]) echo.HandlerFunc { u := c.Get("user").(*njudge.User) if !roles.Can(roles.Role(u.Role), roles.ActionView, roles.Entity(dp.EndpointURL())) { - return helpers.UnauthorizedError(c) + return c.NoContent(http.StatusUnauthorized) } - data, err := pagination.Parse(c) + data, err := templates.ParsePaginationData(c) if err != nil { return err } @@ -67,7 +66,7 @@ func Get[T any](dp Provider[T]) echo.HandlerFunc { return func(c echo.Context) error { u := c.Get("user").(*njudge.User) if !roles.Can(roles.Role(u.Role), roles.ActionView, roles.Entity(dp.EndpointURL())) { - return helpers.UnauthorizedError(c) + return c.NoContent(http.StatusUnauthorized) } id, err := url.QueryUnescape(c.Param(dp.Identifier())) @@ -88,7 +87,7 @@ func Post[T any](dp WritableProvider[T]) echo.HandlerFunc { return func(c echo.Context) error { u := c.Get("user").(*njudge.User) if !roles.Can(roles.Role(u.Role), roles.ActionCreate, roles.Entity(dp.EndpointURL())) { - return helpers.UnauthorizedError(c) + return c.NoContent(http.StatusUnauthorized) } elem := new(T) @@ -108,7 +107,7 @@ func Put[T any](dp WritableProvider[T]) echo.HandlerFunc { return func(c echo.Context) error { u := c.Get("user").(*njudge.User) if !roles.Can(roles.Role(u.Role), roles.ActionEdit, roles.Entity(dp.EndpointURL())) { - return helpers.UnauthorizedError(c) + return c.NoContent(http.StatusUnauthorized) } id, err := url.QueryUnescape(c.Param(dp.Identifier())) @@ -136,7 +135,7 @@ func Delete[T any](dp WritableProvider[T]) echo.HandlerFunc { return func(c echo.Context) error { u := c.Get("user").(*njudge.User) if !roles.Can(roles.Role(u.Role), roles.ActionDelete, roles.Entity(dp.EndpointURL())) { - return helpers.UnauthorizedError(c) + return c.NoContent(http.StatusUnauthorized) } id, err := url.QueryUnescape(c.Param(dp.Identifier())) diff --git a/internal/web/handlers/api/partials.go b/internal/web/handlers/api/partials.go index 257d5ff3..265b6625 100644 --- a/internal/web/handlers/api/partials.go +++ b/internal/web/handlers/api/partials.go @@ -3,10 +3,9 @@ package api import ( "context" "database/sql" + "github.com/mraron/njudge/internal/web/templates" "github.com/mraron/njudge/internal/njudge/db/models" - "github.com/mraron/njudge/internal/web/helpers/pagination" - "github.com/volatiletech/sqlboiler/v4/boil" . "github.com/volatiletech/sqlboiler/v4/queries/qm" ) @@ -23,7 +22,7 @@ func (PartialDataProvider) Identifier() string { return "name" } -func (dp PartialDataProvider) List(data *pagination.Data) ([]*models.Partial, error) { +func (dp PartialDataProvider) List(data *templates.PaginationData) ([]*models.Partial, error) { qms := make([]QueryMod, 0) if data.SortField != "" { qms = append(qms, OrderBy("name "+data.SortDir)) diff --git a/internal/web/handlers/api/problem_rels.go b/internal/web/handlers/api/problem_rels.go index e2c3d246..cdec4e0b 100644 --- a/internal/web/handlers/api/problem_rels.go +++ b/internal/web/handlers/api/problem_rels.go @@ -3,11 +3,10 @@ package api import ( "context" "database/sql" + "github.com/mraron/njudge/internal/web/templates" "strconv" "github.com/mraron/njudge/internal/njudge/db/models" - "github.com/mraron/njudge/internal/web/helpers/pagination" - "github.com/volatiletech/sqlboiler/v4/boil" . "github.com/volatiletech/sqlboiler/v4/queries/qm" ) @@ -24,7 +23,7 @@ func (ProblemRelDataProvider) Identifier() string { return "id" } -func (dp ProblemRelDataProvider) List(data *pagination.Data) ([]*models.ProblemRel, error) { +func (dp ProblemRelDataProvider) List(data *templates.PaginationData) ([]*models.ProblemRel, error) { qms := make([]QueryMod, 0) if data.SortField != "" { qms = append(qms, OrderBy(data.SortField+" "+data.SortDir)) diff --git a/internal/web/handlers/api/submissions.go b/internal/web/handlers/api/submissions.go index 7b0fed4b..04103378 100644 --- a/internal/web/handlers/api/submissions.go +++ b/internal/web/handlers/api/submissions.go @@ -3,11 +3,10 @@ package api import ( "context" "database/sql" + "github.com/mraron/njudge/internal/web/templates" "strconv" "github.com/mraron/njudge/internal/njudge/db/models" - "github.com/mraron/njudge/internal/web/helpers/pagination" - "github.com/volatiletech/sqlboiler/v4/boil" . "github.com/volatiletech/sqlboiler/v4/queries/qm" ) @@ -24,7 +23,7 @@ func (SubmissionDataProvider) Identifier() string { return "id" } -func (dp SubmissionDataProvider) List(data *pagination.Data) ([]*models.Submission, error) { +func (dp SubmissionDataProvider) List(data *templates.PaginationData) ([]*models.Submission, error) { qms := make([]QueryMod, 0) if data.SortField != "" { qms = append(qms, OrderBy(data.SortField+" "+data.SortDir)) diff --git a/internal/web/handlers/api/users.go b/internal/web/handlers/api/users.go index b28e6e81..48649bb9 100644 --- a/internal/web/handlers/api/users.go +++ b/internal/web/handlers/api/users.go @@ -3,12 +3,11 @@ package api import ( "context" "database/sql" + "github.com/mraron/njudge/internal/web/templates" "strconv" "github.com/mraron/njudge/internal/njudge/db/models" "github.com/mraron/njudge/internal/web/helpers" - "github.com/mraron/njudge/internal/web/helpers/pagination" - "github.com/volatiletech/sqlboiler/v4/boil" . "github.com/volatiletech/sqlboiler/v4/queries/qm" ) @@ -25,7 +24,7 @@ func (UserDataProvider) Identifier() string { return "id" } -func (dp UserDataProvider) List(data *pagination.Data) ([]*models.User, error) { +func (dp UserDataProvider) List(data *templates.PaginationData) ([]*models.User, error) { qms := make([]QueryMod, 0) if data.SortField != "" { qms = append(qms, OrderBy(data.SortField+" "+data.SortDir)) diff --git a/internal/web/handlers/pages.go b/internal/web/handlers/pages.go index 016a0314..fc14b317 100644 --- a/internal/web/handlers/pages.go +++ b/internal/web/handlers/pages.go @@ -1,36 +1,35 @@ package handlers import ( + "github.com/mraron/njudge/internal/web/templates" "html/template" "net/http" "github.com/labstack/echo/v4" "github.com/mraron/njudge/internal/njudge" - "github.com/mraron/njudge/internal/web/helpers/config" "github.com/mraron/njudge/internal/web/helpers/roles" - "github.com/mraron/njudge/internal/web/helpers/templates/partials" ) -func GetHome() echo.HandlerFunc { +func GetHome(store templates.Store) echo.HandlerFunc { return func(c echo.Context) error { - return c.Render(http.StatusOK, "home.gohtml", nil) + c.Set(templates.TitleContextKey, "") + res, _ := store.Get("home") + return templates.Render(c, http.StatusOK, templates.Home(res)) } } -func GetAdmin(cfg config.Server) echo.HandlerFunc { +func GetAdmin() echo.HandlerFunc { return func(c echo.Context) error { u := c.Get("user").(*njudge.User) if !roles.Can(roles.Role(u.Role), roles.ActionView, "admin_panel") { - return c.Render(http.StatusUnauthorized, "error.gohtml", "Engedély megtagadva.") + return echo.NotFoundHandler(c) } - return c.Render(http.StatusOK, "admin.gohtml", struct { - Url string - }{cfg.Url}) + return templates.Render(c, http.StatusOK, templates.Admin()) } } -func GetPage(store partials.Store) echo.HandlerFunc { +func GetPage(store templates.Store) echo.HandlerFunc { return func(c echo.Context) error { contents, err := store.Get("page_" + c.Param("page")) if err != nil { diff --git a/internal/web/handlers/problemset/problem.go b/internal/web/handlers/problemset/problem.go index 8c8fec83..bb848ec8 100644 --- a/internal/web/handlers/problemset/problem.go +++ b/internal/web/handlers/problemset/problem.go @@ -3,6 +3,7 @@ package problemset import ( "bytes" "errors" + "github.com/mraron/njudge/internal/web/templates" "io" "mime" "net/http" @@ -11,9 +12,7 @@ import ( "github.com/labstack/echo/v4" "github.com/mraron/njudge/internal/njudge" - "github.com/mraron/njudge/internal/web/helpers" "github.com/mraron/njudge/internal/web/helpers/i18n" - "github.com/mraron/njudge/internal/web/helpers/pagination" "github.com/mraron/njudge/pkg/problems" ) @@ -22,26 +21,26 @@ func GetProblem() echo.HandlerFunc { tr := c.Get(i18n.TranslatorContextKey).(i18n.Translator) prob := c.Get("problem").(njudge.Problem) info := c.Get("problemInfo").(njudge.ProblemInfo) - sdata := c.Get("problemStoredData").(njudge.ProblemStoredData) + storedData := c.Get("problemStoredData").(njudge.ProblemStoredData) c.Set("title", tr.Translate("Statement - %s (%s)", - tr.TranslateContent(sdata.Titles()).String(), sdata.Name())) + tr.TranslateContent(storedData.Titles()).String(), storedData.Name())) return c.Render(http.StatusOK, "problemset/problem/problem", struct { njudge.Problem njudge.ProblemStoredData njudge.ProblemInfo - }{Problem: prob, ProblemStoredData: sdata, ProblemInfo: info}) + }{Problem: prob, ProblemStoredData: storedData, ProblemInfo: info}) } } func GetProblemPDF() echo.HandlerFunc { return func(c echo.Context) error { - sdata := c.Get("problemStoredData").(njudge.ProblemStoredData) + storedData := c.Get("problemStoredData").(njudge.ProblemStoredData) lang := c.Param("language") - r, err := sdata.GetPDF(njudge.Language(lang)) + r, err := storedData.GetPDF(njudge.Language(lang)) if err != nil { return err } @@ -57,9 +56,9 @@ func GetProblemPDF() echo.HandlerFunc { func GetProblemFile() echo.HandlerFunc { return func(c echo.Context) error { - sdata := c.Get("problemStoredData").(njudge.ProblemStoredData) + storedData := c.Get("problemStoredData").(njudge.ProblemStoredData) - fileLoc, err := sdata.GetFile(c.Param("file")) + fileLoc, err := storedData.GetFile(c.Param("file")) if errors.Is(err, njudge.ErrorFileNotFound) { return echo.NewHTTPError(http.StatusNotFound, err) } else if err != nil { @@ -72,10 +71,10 @@ func GetProblemFile() echo.HandlerFunc { func GetProblemAttachment() echo.HandlerFunc { return func(c echo.Context) error { - sdata := c.Get("problemStoredData").(njudge.ProblemStoredData) + storedData := c.Get("problemStoredData").(njudge.ProblemStoredData) attachment := c.Param("attachment") - val, err := sdata.GetAttachment(attachment) + val, err := storedData.GetAttachment(attachment) if errors.Is(err, njudge.ErrorFileNotFound) { return echo.NewHTTPError(http.StatusNotFound, err) } else if err != nil { @@ -100,15 +99,15 @@ func GetProblemAttachment() echo.HandlerFunc { } } -func GetProblemRanklist(slist njudge.SubmissionListQuery) echo.HandlerFunc { +func GetProblemRanklist(subList njudge.SubmissionListQuery) echo.HandlerFunc { return func(c echo.Context) error { tr := c.Get(i18n.TranslatorContextKey).(i18n.Translator) problemset, problemName := c.Param("name"), c.Param("problem") prob := c.Get("problem").(njudge.Problem) - sdata := c.Get("problemStoredData").(njudge.ProblemStoredData) + storedData := c.Get("problemStoredData").(njudge.ProblemStoredData) - submissions, err := slist.GetSubmissionList(c.Request().Context(), njudge.SubmissionListRequest{ + submissions, err := subList.GetSubmissionList(c.Request().Context(), njudge.SubmissionListRequest{ Problemset: problemset, Problem: problemName, SortDir: njudge.SortDESC, @@ -129,12 +128,12 @@ func GetProblemRanklist(slist njudge.SubmissionListQuery) echo.HandlerFunc { hadUser[submissions.Submissions[ind].UserID] = true } - c.Set("title", tr.Translate("Results - %s (%s)", tr.TranslateContent(sdata.Titles()).String(), sdata.Name())) + c.Set("title", tr.Translate("Results - %s (%s)", tr.TranslateContent(storedData.Titles()).String(), storedData.Name())) return c.Render(http.StatusOK, "problemset/problem/ranklist", struct { Problem njudge.Problem ProblemStoredData njudge.ProblemStoredData Submissions []njudge.Submission - }{prob, sdata, res}) + }{prob, storedData, res}) } } @@ -155,7 +154,7 @@ func GetProblemSubmit() echo.HandlerFunc { } } -func GetProblemStatus(slist njudge.SubmissionListQuery, pstore problems.Store) echo.HandlerFunc { +func GetProblemStatus(subList njudge.SubmissionListQuery, probList problems.Store) echo.HandlerFunc { type request struct { AC string `query:"ac"` UserID int `query:"user_id"` @@ -168,7 +167,7 @@ func GetProblemStatus(slist njudge.SubmissionListQuery, pstore problems.Store) e tr := c.Get(i18n.TranslatorContextKey).(i18n.Translator) prob := c.Get("problem").(njudge.Problem) - sdata, err := prob.WithStoredData(pstore) + storedData, err := prob.WithStoredData(probList) if err != nil { return err } @@ -200,23 +199,23 @@ func GetProblemStatus(slist njudge.SubmissionListQuery, pstore problems.Store) e statusReq.Verdict = &ac } - submissionList, err := slist.GetPagedSubmissionList(c.Request().Context(), statusReq) + submissionList, err := subList.GetPagedSubmissionList(c.Request().Context(), statusReq) if err != nil { return err } qu := (*c.Request().URL).Query() - links, err := pagination.LinksWithCountLimit(submissionList.PaginationData.Page, submissionList.PaginationData.PerPage, int64(submissionList.PaginationData.Count), qu, 5) + links, err := templates.LinksWithCountLimit(submissionList.PaginationData.Page, submissionList.PaginationData.PerPage, int64(submissionList.PaginationData.Count), qu, 5) if err != nil { return err } - result := StatusPage{ + result := templates.SubmissionsViewModel{ Submissions: submissionList.Submissions, Pages: links, } - c.Set("title", tr.Translate("Submissions - %s (%s)", tr.TranslateContent(sdata.Titles()).String(), sdata.Name())) + c.Set("title", tr.Translate("Submissions - %s (%s)", tr.TranslateContent(storedData.Titles()).String(), storedData.Name())) return c.Render(http.StatusOK, "problemset/problem/status", result) } } @@ -233,7 +232,7 @@ func PostProblemTag(tgs njudge.TagsService) echo.HandlerFunc { u := c.Get("user").(*njudge.User) if u == nil { - return helpers.UnauthorizedError(c) + return c.NoContent(http.StatusUnauthorized) } pr := c.Get("problem").(njudge.Problem) @@ -257,7 +256,7 @@ func DeleteProblemTag(tgs njudge.TagsService) echo.HandlerFunc { u := c.Get("user").(*njudge.User) if u == nil { - return helpers.UnauthorizedError(c) + return c.NoContent(http.StatusUnauthorized) } pr := c.Get("problem").(njudge.Problem) diff --git a/internal/web/handlers/problemset/problemset.go b/internal/web/handlers/problemset/problemset.go index 7bcf473e..3df3867a 100644 --- a/internal/web/handlers/problemset/problemset.go +++ b/internal/web/handlers/problemset/problemset.go @@ -2,6 +2,8 @@ package problemset import ( "fmt" + "github.com/a-h/templ" + "github.com/mraron/njudge/internal/web/templates" "io" "net/http" "sort" @@ -11,67 +13,50 @@ import ( "github.com/labstack/echo/v4" "github.com/mraron/njudge/internal/njudge" "github.com/mraron/njudge/internal/web/helpers/i18n" - "github.com/mraron/njudge/internal/web/helpers/pagination" - "github.com/mraron/njudge/internal/web/helpers/ui" "github.com/mraron/njudge/pkg/problems" ) -type CategoryFilterOption struct { - Name string - Value string - Selected bool -} +type ProblemListRequest struct { + Page int `query:"page"` + Order_ string `query:"order"` + Order njudge.SortDirection + By_ string `query:"by"` + By njudge.ProblemSortField -type Problem struct { - njudge.Problem - njudge.ProblemStoredData - njudge.ProblemInfo + TitleFilter string `query:"title"` + CategoryFilter int `query:"category"` + TagFilter string `query:"tags"` - CategoryLink ui.Link + Problemset string `param:"name"` } -type ProblemList struct { - Pages []pagination.Link - Problems []Problem - SolverSorter ui.SortColumn - - Filtered bool +func NewProblemListRequest(c echo.Context) (*ProblemListRequest, error) { + data := ProblemListRequest{} + if err := c.Bind(&data); err != nil { + return nil, err + } + if data.Page <= 0 { + data.Page = 1 + } - TitleFilter string - TagsFilter string - CategoryFilterOptions []CategoryFilterOption + data.Order, data.By = njudge.SortDESC, njudge.ProblemSortFieldID + if c.QueryParam("by") == "solver_count" { + data.By = njudge.ProblemSortFieldSolverCount + } + if c.QueryParam("order") == "ASC" { + data.Order = njudge.SortASC + } + return &data, nil } -func GetProblemList(store problems.Store, ps njudge.Problems, cs njudge.Categories, problemListQuery njudge.ProblemListQuery, pinfo njudge.ProblemInfoQuery) echo.HandlerFunc { - type request struct { - Page int `query:"page"` - Order njudge.SortDirection - By njudge.ProblemSortField - - TitleFilter string `query:"title"` - CategoryFilter int `query:"category"` - TagFilter string `query:"tags"` +func GetProblemList(store problems.Store, ps njudge.Problems, cs njudge.Categories, problemListQuery njudge.ProblemListQuery, pinfo njudge.ProblemInfoQuery, tags njudge.Tags) echo.HandlerFunc { - Problemset string `param:"name"` - } return func(c echo.Context) error { tr := c.Get(i18n.TranslatorContextKey).(i18n.Translator) - - data := request{} - if err := c.Bind(&data); err != nil { + data, err := NewProblemListRequest(c) + if err != nil { return err } - if data.Page <= 0 { - data.Page = 1 - } - - data.Order, data.By = njudge.SortDESC, njudge.ProblemSortFieldID - if c.QueryParam("by") == "solver_count" { - data.By = njudge.ProblemSortFieldSolverCount - } - if c.QueryParam("order") == "ASC" { - data.Order = njudge.SortASC - } listRequest := njudge.ProblemListRequest{ Problemset: data.Problemset, @@ -101,12 +86,44 @@ func GetProblemList(store problems.Store, ps njudge.Problems, cs njudge.Categori } u := *c.Request().URL - links, err := pagination.Links(problemList.PaginationData.Page, problemList.PaginationData.PerPage, int64(problemList.PaginationData.Count), u.Query()) + links, err := templates.Links(problemList.PaginationData.Page, problemList.PaginationData.PerPage, int64(problemList.PaginationData.Count), u.Query()) + if err != nil { + return err + } + + tagsList, err := tags.GetAll(c.Request().Context()) if err != nil { return err } - result := ProblemList{ + result := templates.ProblemListViewModel{ Pages: links, + Tags: tagsList, + } + + categories, err := cs.GetAll(c.Request().Context()) + if err != nil { + return err + } + + par := make(map[int]int) + for ind := range categories { + if categories[ind].ParentID.Valid { + par[categories[ind].ID] = categories[ind].ParentID.Int + } + } + + categoryNameByID := make(map[int]string) + for ind := range categories { + categoryNameByID[categories[ind].ID] = categories[ind].Name + } + + var getCategoryNameRec func(int) string + getCategoryNameRec = func(id int) string { + if _, ok := par[id]; !ok { + return categoryNameByID[id] + } else { + return getCategoryNameRec(par[id]) + " -- " + categoryNameByID[id] + } } for ind := range problemList.Problems { @@ -123,17 +140,48 @@ func GetProblemList(store problems.Store, ps njudge.Problems, cs njudge.Categori return err } - result.Problems = append(result.Problems, Problem{ - Problem: *p, - ProblemInfo: *info, - ProblemStoredData: data, - }) + curr := templates.ProblemListProblem{ + Name: p.Problem, + Titles: data.Titles(), + Visible: p.Visible, + UserInfo: info.UserInfo, + ShowTags: true, + Tags: nil, + SolverCount: p.SolverCount, + } + + for _, tag := range p.Tags { + curr.Tags = append(curr.Tags, tag.Tag) + } + + if u := c.Get(templates.UserContextKey).(*njudge.User); u != nil { + if info.UserInfo.SolvedStatus != njudge.Solved && !u.Settings.ShowUnsolvedTags { + curr.ShowTags = false + } + } + + if p.Category != nil { + cid := p.Category.ID + for { + if _, ok := par[cid]; ok { + cid = par[cid] + } else { + break + } + } + + curr.CategoryLink = templates.Link{ + Text: categoryNameByID[cid], + Href: templ.SafeURL(fmt.Sprintf("/task_archive#category%d", p.Category.ID)), + } + } + + result.Problems = append(result.Problems, curr) } - sortOrder, u := "", *c.Request().URL + u = *c.Request().URL qu := u.Query() - if qu.Get("by") == "solver_count" { - sortOrder = qu.Get("order") + if data.By == njudge.ProblemSortFieldSolverCount { if qu.Get("order") == "DESC" { qu.Set("order", "ASC") } else { @@ -144,15 +192,15 @@ func GetProblemList(store problems.Store, ps njudge.Problems, cs njudge.Categori qu.Set("by", "solver_count") qu.Set("order", "DESC") } - result.SolverSorter = ui.SortColumn{ - Order: sortOrder, + result.SolverSorter = templates.SortColumn{ + Order: data.Order, Href: "?" + qu.Encode(), } result.Filtered = listRequest.IsFiltered() result.TitleFilter = data.TitleFilter result.TagsFilter = data.TagFilter - result.CategoryFilterOptions = []CategoryFilterOption{ + result.CategoryFilterOptions = []templates.CategoryFilterOption{ {Name: "-"}, } @@ -160,40 +208,14 @@ func GetProblemList(store problems.Store, ps njudge.Problems, cs njudge.Categori if data.CategoryFilter == -1 { emptySelected = true } - result.CategoryFilterOptions = append(result.CategoryFilterOptions, CategoryFilterOption{ + result.CategoryFilterOptions = append(result.CategoryFilterOptions, templates.CategoryFilterOption{ Name: tr.Translate("No category"), Value: "-1", Selected: emptySelected, }) - categories, err := cs.GetAll(c.Request().Context()) - if err != nil { - return err - } - - par := make(map[int]int) - for ind := range categories { - if categories[ind].ParentID.Valid { - par[categories[ind].ID] = categories[ind].ParentID.Int - } - } - - categoryNameByID := make(map[int]string) for ind := range categories { - categoryNameByID[categories[ind].ID] = categories[ind].Name - } - - var getCategoryNameRec func(int) string - getCategoryNameRec = func(id int) string { - if _, ok := par[id]; !ok { - return categoryNameByID[id] - } else { - return getCategoryNameRec(par[id]) + " -- " + categoryNameByID[id] - } - } - - for ind := range categories { - curr := CategoryFilterOption{ + curr := templates.CategoryFilterOption{ Name: getCategoryNameRec(categories[ind].ID), Value: strconv.Itoa(categories[ind].ID), Selected: false, @@ -210,46 +232,24 @@ func GetProblemList(store problems.Store, ps njudge.Problems, cs njudge.Categori return result.CategoryFilterOptions[i].Name < result.CategoryFilterOptions[j].Name }) - for ind := range result.Problems { - if result.Problems[ind].Category != nil { - cid := result.Problems[ind].Category.ID - for { - if _, ok := par[cid]; ok { - cid = par[cid] - } else { - break - } - } - - result.Problems[ind].CategoryLink = ui.Link{ - Text: categoryNameByID[cid], - Href: fmt.Sprintf("/task_archive#category%d", result.Problems[ind].Category.ID), - } - } - } - c.Set("title", tr.Translate("Problems")) - return c.Render(http.StatusOK, "problemset/list", result) + return templates.Render(c, http.StatusOK, templates.ProblemList(result)) } } -type StatusPage struct { - Pages []pagination.Link - Submissions []njudge.Submission +type GetStatusRequest struct { + AC string `query:"ac"` + UserID int `query:"user_id"` + Problemset string `query:"problem_set"` + Problem string `query:"problem"` + Page int `query:"page"` } -func GetStatus(slist njudge.SubmissionListQuery) echo.HandlerFunc { - type request struct { - AC string `query:"ac"` - UserID int `query:"user_id"` - Problemset string `query:"problem_set"` - Problem string `query:"problem"` - Page int `query:"page"` - } +func GetStatus(subList njudge.SubmissionListQuery) echo.HandlerFunc { return func(c echo.Context) error { tr := c.Get(i18n.TranslatorContextKey).(i18n.Translator) - data := request{} + data := GetStatusRequest{} if err := c.Bind(&data); err != nil { return err } @@ -273,24 +273,24 @@ func GetStatus(slist njudge.SubmissionListQuery) echo.HandlerFunc { statusReq.Verdict = &ac } - submissionList, err := slist.GetPagedSubmissionList(c.Request().Context(), statusReq) + submissionList, err := subList.GetPagedSubmissionList(c.Request().Context(), statusReq) if err != nil { return err } qu := (*c.Request().URL).Query() - links, err := pagination.LinksWithCountLimit(submissionList.PaginationData.Page, submissionList.PaginationData.PerPage, int64(submissionList.PaginationData.Count), qu, 5) + links, err := templates.LinksWithCountLimit(submissionList.PaginationData.Page, submissionList.PaginationData.PerPage, int64(submissionList.PaginationData.Count), qu, 5) if err != nil { return err } - result := StatusPage{ + result := templates.SubmissionsViewModel{ Submissions: submissionList.Submissions, Pages: links, } c.Set("title", tr.Translate("Submissions")) - return c.Render(http.StatusOK, "status.gohtml", result) + return templates.Render(c, http.StatusOK, templates.Status(result)) } } diff --git a/internal/web/handlers/submission.go b/internal/web/handlers/submission.go index 62647be3..7c006f46 100644 --- a/internal/web/handlers/submission.go +++ b/internal/web/handlers/submission.go @@ -1,24 +1,25 @@ package handlers import ( + "github.com/mraron/njudge/internal/web/templates" "net/http" "github.com/labstack/echo/v4" "github.com/mraron/njudge/internal/njudge" - "github.com/mraron/njudge/internal/web/helpers" "github.com/mraron/njudge/internal/web/helpers/i18n" "github.com/mraron/njudge/internal/web/helpers/roles" ) -func GetSubmission(s njudge.Submissions) echo.HandlerFunc { - type request struct { - ID int `param:"id"` - } +type GetSubmissionRequest struct { + ID int `param:"id"` +} +func GetSubmission(s njudge.Submissions) echo.HandlerFunc { return func(c echo.Context) error { + u := c.Get("user").(*njudge.User) tr := c.Get(i18n.TranslatorContextKey).(i18n.Translator) - data := &request{} + data := &GetSubmissionRequest{} if err := c.Bind(data); err != nil { return err } @@ -28,8 +29,18 @@ func GetSubmission(s njudge.Submissions) echo.HandlerFunc { return err } - c.Set("title", tr.Translate("Submission #%d", data.ID)) - return c.Render(http.StatusOK, "submission.gohtml", sub) + vm := templates.SubmissionViewModel{ + Submission: *sub, + } + if u != nil && roles.Can(roles.Role(u.Role), roles.ActionCreate, "submissions/rejudge") { + vm.CanRejudge = true + } + if sub.Language != "zip" { + vm.DisplaySource = true + } + + c.Set(templates.TitleContextKey, tr.Translate("Submission #%d", data.ID)) + return templates.Render(c, http.StatusOK, templates.Submission(vm)) } } @@ -41,7 +52,7 @@ func RejudgeSubmission(s njudge.Submissions) echo.HandlerFunc { return func(c echo.Context) error { u := c.Get("user").(*njudge.User) if !roles.Can(roles.Role(u.Role), roles.ActionCreate, "submissions/rejudge") { - return helpers.UnauthorizedError(c) + return c.NoContent(http.StatusUnauthorized) } data := &request{} diff --git a/internal/web/handlers/user/forgottenpassword.go b/internal/web/handlers/user/forgottenpassword.go index a0b396fb..6c68b7a7 100644 --- a/internal/web/handlers/user/forgottenpassword.go +++ b/internal/web/handlers/user/forgottenpassword.go @@ -3,13 +3,13 @@ package user import ( "bytes" "errors" + "github.com/mraron/njudge/internal/web/templates" "net/http" "time" "github.com/labstack/echo/v4" "github.com/mraron/njudge/internal/njudge" "github.com/mraron/njudge/internal/njudge/email" - "github.com/mraron/njudge/internal/web/helpers" "github.com/mraron/njudge/internal/web/helpers/config" "github.com/mraron/njudge/internal/web/helpers/i18n" ) @@ -20,10 +20,10 @@ func GetForgottenPassword() echo.HandlerFunc { tr := c.Get(i18n.TranslatorContextKey).(i18n.Translator) if u := c.Get("user").(*njudge.User); u != nil { - return c.Render(http.StatusOK, "error.gohtml", tr.Translate(alreadyLoggedInMessage)) + return templates.Render(c, http.StatusOK, templates.Error(tr.Translate(alreadyLoggedInMessage))) } - helpers.DeleteFlash(c, "ForgottenPasswordMessage") + templates.DeleteFlash(c, "ForgottenPasswordMessage") return c.Render(http.StatusOK, "user/forgotten_password", nil) } @@ -37,7 +37,7 @@ func PostForgottenPassword(cfg config.Server, users njudge.Users, mailService em tr := c.Get(i18n.TranslatorContextKey).(i18n.Translator) if u := c.Get("user").(*njudge.User); u != nil { - return c.Render(http.StatusOK, "error.gohtml", tr.Translate(alreadyLoggedInMessage)) + return templates.Render(c, http.StatusOK, templates.Error(tr.Translate(alreadyLoggedInMessage))) } data := request{} @@ -81,7 +81,7 @@ func PostForgottenPassword(cfg config.Server, users njudge.Users, mailService em } - helpers.SetFlash(c, "ForgottenPasswordMessage", tr.Translate("An email with further instructions was sent to the given address (if it's registered in our system).")) + templates.SetFlash(c, "ForgottenPasswordMessage", tr.Translate("An email with further instructions was sent to the given address (if it's registered in our system).")) return c.Redirect(http.StatusFound, c.Echo().Reverse("GetForgottenPassword")) } @@ -97,7 +97,7 @@ func GetForgottenPasswordForm() echo.HandlerFunc { tr := c.Get(i18n.TranslatorContextKey).(i18n.Translator) if u := c.Get("user").(*njudge.User); u != nil { - return c.Render(http.StatusOK, "error.gohtml", tr.Translate(alreadyLoggedInMessage)) + return templates.Render(c, http.StatusOK, templates.Error(tr.Translate(alreadyLoggedInMessage))) } data := request{} @@ -105,7 +105,7 @@ func GetForgottenPasswordForm() echo.HandlerFunc { return err } - helpers.DeleteFlash(c, "ForgottenPasswordFormMessage") + templates.DeleteFlash(c, "ForgottenPasswordFormMessage") return c.Render(http.StatusOK, "user/forgotten_password_form", struct { Name string @@ -126,7 +126,7 @@ func PostForgottenPasswordForm(users njudge.Users) echo.HandlerFunc { tr := c.Get(i18n.TranslatorContextKey).(i18n.Translator) if u := c.Get("user").(*njudge.User); u != nil { - return c.Render(http.StatusOK, "error.gohtml", tr.Translate(alreadyLoggedInMessage)) + return templates.Render(c, http.StatusOK, templates.Error(tr.Translate(alreadyLoggedInMessage))) } data := request{} @@ -140,16 +140,16 @@ func PostForgottenPasswordForm(users njudge.Users) echo.HandlerFunc { } if u.ForgottenPasswordKey == nil || u.ForgottenPasswordKey.Key != data.Key || !u.ForgottenPasswordKey.IsValid() { - helpers.SetFlash(c, "ForgottenPasswordFormMessage", tr.Translate("Invalid key provided.")) + templates.SetFlash(c, "ForgottenPasswordFormMessage", tr.Translate("Invalid key provided.")) } else { if data.Password1 != data.Password2 { - helpers.SetFlash(c, "ForgottenPasswordFormMessage", tr.Translate("The two passwords don't match.")) + templates.SetFlash(c, "ForgottenPasswordFormMessage", tr.Translate("The two passwords don't match.")) } else { u.ForgottenPasswordKey = nil u.SetPassword(data.Password1) if err := users.Update(c.Request().Context(), u, njudge.Fields(njudge.UserFields.ForgottenPasswordKey, njudge.UserFields.Password)); err == nil { - helpers.SetFlash(c, "ForgottenPasswordFormMessage", tr.Translate("Password changed succesfully! You can login with your new password.")) + templates.SetFlash(c, "ForgottenPasswordFormMessage", tr.Translate("Password changed succesfully! You can login with your new password.")) } else { return err } diff --git a/internal/web/handlers/user/login.go b/internal/web/handlers/user/login.go index ec182a9d..34899839 100644 --- a/internal/web/handlers/user/login.go +++ b/internal/web/handlers/user/login.go @@ -2,14 +2,13 @@ package user import ( "errors" + "github.com/mraron/njudge/internal/web/templates" "net/http" "github.com/markbates/goth/gothic" "github.com/mraron/njudge/internal/njudge" "github.com/mraron/njudge/internal/web/helpers/i18n" - "github.com/mraron/njudge/internal/web/helpers" - "github.com/labstack/echo-contrib/session" "github.com/labstack/echo/v4" "golang.org/x/crypto/bcrypt" @@ -38,22 +37,22 @@ func loginUserHandler(auth Authenticator) echo.HandlerFunc { tr := c.Get(i18n.TranslatorContextKey).(i18n.Translator) if u := c.Get("user").(*njudge.User); u != nil { - return c.Render(http.StatusOK, "error.gohtml", tr.Translate(alreadyLoggedInMessage)) + return templates.Render(c, http.StatusOK, templates.Error(tr.Translate(alreadyLoggedInMessage))) } user, err := auth(c) if err != nil { if errors.Is(err, ErrorLogin) { - helpers.SetFlash(c, "LoginMessage", err.(LoginErrorWithMessage).TranslatedMessage) + templates.SetFlash(c, "LoginMessage", err.(LoginErrorWithMessage).TranslatedMessage) return c.Redirect(http.StatusFound, c.Echo().Reverse("getUserLogin")) } else { return err } } - defer helpers.DeleteFlash(c, "LoginRedirect") + defer templates.DeleteFlash(c, "LoginRedirect") if !user.ActivationInfo.Activated { - helpers.SetFlash(c, "LoginMessage", tr.Translate("The account is not activated. Check your emails!")) + templates.SetFlash(c, "LoginMessage", tr.Translate("The account is not activated. Check your emails!")) return c.Redirect(http.StatusFound, "/user/login") } @@ -64,14 +63,14 @@ func loginUserHandler(auth Authenticator) echo.HandlerFunc { return err } - c.Set("user", user) + c.Set(templates.UserContextKey, user) to := "/" - if val, ok := helpers.GetFlash(c, "LoginRedirect").(string); ok { + if val, ok := templates.GetFlash(c, "LoginRedirect").(string); ok { to = val } - helpers.SetFlash(c, "TopMessage", tr.Translate("Successful login!")) + templates.SetFlash(c, templates.TopMessageContextKey, tr.Translate("Successful login!")) return c.Redirect(http.StatusFound, to) } } @@ -81,7 +80,7 @@ func BeginOAuth() echo.HandlerFunc { tr := c.Get(i18n.TranslatorContextKey).(i18n.Translator) if u := c.Get("user").(*njudge.User); u != nil { - return c.Render(http.StatusOK, "error.gohtml", tr.Translate(alreadyLoggedInMessage)) + return templates.Render(c, http.StatusOK, templates.Error(tr.Translate(alreadyLoggedInMessage))) } gothic.BeginAuthHandler(c.Response(), c.Request()) @@ -94,16 +93,16 @@ func GetLogin() echo.HandlerFunc { tr := c.Get(i18n.TranslatorContextKey).(i18n.Translator) if u := c.Get("user").(*njudge.User); u != nil { - return c.Render(http.StatusOK, "error.gohtml", tr.Translate(alreadyLoggedInMessage)) + return templates.Render(c, http.StatusOK, templates.Error(tr.Translate(alreadyLoggedInMessage))) } - helpers.DeleteFlash(c, "LoginMessage") + templates.DeleteFlash(c, "LoginMessage") to := "/" if val := c.QueryParams().Get("next"); val != "" { to = val } - helpers.SetFlash(c, "LoginRedirect", to) + templates.SetFlash(c, "LoginRedirect", to) return c.Render(http.StatusOK, "user/login", nil) } diff --git a/internal/web/handlers/user/middleware.go b/internal/web/handlers/user/middleware.go index b7a12366..fa62853b 100644 --- a/internal/web/handlers/user/middleware.go +++ b/internal/web/handlers/user/middleware.go @@ -5,6 +5,7 @@ import ( "github.com/labstack/echo/v4" "github.com/mraron/njudge/internal/njudge" "github.com/mraron/njudge/internal/web/helpers" + "github.com/mraron/njudge/internal/web/templates" ) func currentUser(c echo.Context, us njudge.Users) (*njudge.User, error) { @@ -31,7 +32,7 @@ func SetUserMiddleware(us njudge.Users) func(echo.HandlerFunc) echo.HandlerFunc return func(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { user, err := currentUser(c, us) - c.Set("user", user) + c.Set(templates.UserContextKey, user) if user != nil { c.Set("userID", user.ID) diff --git a/internal/web/handlers/user/profile/profile.go b/internal/web/handlers/user/profile/profile.go index 261904dd..93b4e605 100644 --- a/internal/web/handlers/user/profile/profile.go +++ b/internal/web/handlers/user/profile/profile.go @@ -1,30 +1,28 @@ package profile import ( + "github.com/mraron/njudge/internal/web/templates" "net/http" "golang.org/x/crypto/bcrypt" "github.com/labstack/echo/v4" "github.com/mraron/njudge/internal/njudge" - "github.com/mraron/njudge/internal/web/handlers/problemset" - "github.com/mraron/njudge/internal/web/helpers" "github.com/mraron/njudge/internal/web/helpers/i18n" - "github.com/mraron/njudge/internal/web/helpers/pagination" ) -func GetProfile(slist njudge.SubmissionListQuery) echo.HandlerFunc { +func GetProfile(sublist njudge.SubmissionListQuery) echo.HandlerFunc { return func(c echo.Context) error { tr := c.Get(i18n.TranslatorContextKey).(i18n.Translator) u := c.Get("profile").(*njudge.User) - solved, err := slist.GetSolvedSubmissionList(c.Request().Context(), u.ID) + solved, err := sublist.GetSolvedSubmissionList(c.Request().Context(), u.ID) if err != nil { return err } - attempted, err := slist.GetAttemptedSubmissionList(c.Request().Context(), u.ID) + attempted, err := sublist.GetAttemptedSubmissionList(c.Request().Context(), u.ID) if err != nil { return err } @@ -38,7 +36,7 @@ func GetProfile(slist njudge.SubmissionListQuery) echo.HandlerFunc { } } -func GetSubmissions(slist njudge.SubmissionListQuery) echo.HandlerFunc { +func GetSubmissions(sublist njudge.SubmissionListQuery) echo.HandlerFunc { type request struct { Page int `query:"page"` } @@ -64,18 +62,18 @@ func GetSubmissions(slist njudge.SubmissionListQuery) echo.HandlerFunc { UserID: u.ID, } - submissionList, err := slist.GetPagedSubmissionList(c.Request().Context(), statusReq) + submissionList, err := sublist.GetPagedSubmissionList(c.Request().Context(), statusReq) if err != nil { return err } qu := (*c.Request().URL).Query() - links, err := pagination.Links(submissionList.PaginationData.Page, submissionList.PaginationData.PerPage, int64(submissionList.PaginationData.Count), qu) + links, err := templates.Links(submissionList.PaginationData.Page, submissionList.PaginationData.PerPage, int64(submissionList.PaginationData.Count), qu) if err != nil { return err } - result := problemset.StatusPage{ + result := templates.SubmissionsViewModel{ Submissions: submissionList.Submissions, Pages: links, } @@ -83,7 +81,7 @@ func GetSubmissions(slist njudge.SubmissionListQuery) echo.HandlerFunc { c.Set("title", tr.Translate("%s's submissions", u.Name)) return c.Render(http.StatusOK, "user/profile/submissions", struct { User *njudge.User - StatusPage problemset.StatusPage + StatusPage templates.SubmissionsViewModel }{u, result}) } } @@ -92,7 +90,7 @@ func GetSettings() echo.HandlerFunc { return func(c echo.Context) error { u := c.Get("user").(*njudge.User) - helpers.DeleteFlash(c, "ChangePassword") + templates.DeleteFlash(c, "ChangePassword") return c.Render(http.StatusOK, "user/profile/settings", struct { User *njudge.User }{u}) @@ -115,17 +113,17 @@ func PostSettingsChangePassword(us njudge.Users) echo.HandlerFunc { } if err := bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(data.PasswordOld)); err != nil { - helpers.SetFlash(c, "ChangePassword", tr.Translate("Wrong old password.")) + templates.SetFlash(c, "ChangePassword", tr.Translate("Wrong old password.")) return c.Redirect(http.StatusFound, "../") } if len(data.PasswordNew1) == 0 { - helpers.SetFlash(c, "ChangePassword", tr.Translate("It's required to give a new password.")) + templates.SetFlash(c, "ChangePassword", tr.Translate("It's required to give a new password.")) return c.Redirect(http.StatusFound, "../") } if data.PasswordNew1 != data.PasswordNew2 { - helpers.SetFlash(c, "ChangePassword", tr.Translate("The two given passwords doesn't match.")) + templates.SetFlash(c, "ChangePassword", tr.Translate("The two given passwords doesn't match.")) return c.Redirect(http.StatusFound, "../") } diff --git a/internal/web/handlers/user/register.go b/internal/web/handlers/user/register.go index 6d82896d..4b6f2fbc 100644 --- a/internal/web/handlers/user/register.go +++ b/internal/web/handlers/user/register.go @@ -3,6 +3,7 @@ package user import ( "bytes" "errors" + "github.com/mraron/njudge/internal/web/templates" "net/http" "unicode" @@ -149,7 +150,7 @@ func GetActivateInfo() echo.HandlerFunc { tr := c.Get(i18n.TranslatorContextKey).(i18n.Translator) if u := c.Get("user").(*njudge.User); u != nil { - return c.Render(http.StatusOK, "error.gohtml", tr.Translate(alreadyLoggedInMessage)) + return templates.Render(c, http.StatusOK, templates.Error(tr.Translate(alreadyLoggedInMessage))) } return c.Render(http.StatusOK, "user/activate.gohtml", nil) @@ -170,7 +171,7 @@ func Activate(users njudge.Users) echo.HandlerFunc { tr := c.Get(i18n.TranslatorContextKey).(i18n.Translator) if u := c.Get("user").(*njudge.User); u != nil { - return c.Render(http.StatusOK, "error.gohtml", tr.Translate(alreadyLoggedInMessage)) + return templates.Render(c, http.StatusOK, templates.Error(tr.Translate(alreadyLoggedInMessage))) } user, err := users.GetByName(c.Request().Context(), data.Name) diff --git a/internal/web/helpers/config/config.go b/internal/web/helpers/config/config.go index 20105ee1..e670437f 100644 --- a/internal/web/helpers/config/config.go +++ b/internal/web/helpers/config/config.go @@ -1,13 +1,8 @@ package config import ( - "crypto/rsa" - "crypto/x509" "database/sql" - "encoding/pem" - "errors" "fmt" - "io/ioutil" "log/slog" "time" ) @@ -86,59 +81,4 @@ type Server struct { } `json:"smtp" mapstructure:"smtp"` Database `json:"database" mapstructure:"database"` - - Keys Keys -} - -type Keys struct { - PrivateKeyLocation string `json:"private_key" mapstructure:"private_key"` - PublicKeyLocation string `json:"public_key" mapstructure:"public_key"` - PrivateKey *rsa.PrivateKey - PublicKey *rsa.PublicKey -} - -func (k *Keys) Parse() error { - if k.PrivateKeyLocation != "" { - if k.PublicKeyLocation == "" { - return errors.New("private key filled, public not") - } - - privateKeyContents, err := ioutil.ReadFile(k.PrivateKeyLocation) - if err != nil { - return err - } - - block, _ := pem.Decode(privateKeyContents) - if block == nil { - return fmt.Errorf("can't parse pem private key file: %s", k.PrivateKeyLocation) - } - - var pKey any - if pKey, err = x509.ParsePKCS8PrivateKey(block.Bytes); err != nil { - return err - } - k.PrivateKey = pKey.(*rsa.PrivateKey) - - publicKeyContents, err := ioutil.ReadFile(k.PublicKeyLocation) - if err != nil { - return err - } - - block, _ = pem.Decode(publicKeyContents) - if block == nil { - return fmt.Errorf("can't parse pem public key file: %s", k.PrivateKeyLocation) - } - - if k.PublicKey, err = x509.ParsePKCS1PublicKey(block.Bytes); err != nil { - return err - } - } - - return nil -} - -func (k *Keys) MustParse() { - if err := k.Parse(); err != nil { - panic(err) - } } diff --git a/internal/web/helpers/helpers.go b/internal/web/helpers/helpers.go index 23bd9bbb..f14f1d34 100644 --- a/internal/web/helpers/helpers.go +++ b/internal/web/helpers/helpers.go @@ -1,15 +1,10 @@ package helpers import ( - "errors" - "net/http" - "time" - - "github.com/mraron/njudge/internal/njudge/db/models" - "github.com/mraron/njudge/internal/web/helpers/config" - - "github.com/golang-jwt/jwt" "github.com/labstack/echo/v4" + "github.com/mraron/njudge/internal/njudge/db/models" + "github.com/mraron/njudge/internal/web/templates" + "net/http" ) func CensorUserPassword(user *models.User) { @@ -17,35 +12,10 @@ func CensorUserPassword(user *models.User) { } func LoginRequired(c echo.Context) error { - SetFlash(c, "LoginMessage", "A kért oldal megtekintéséhez belépés szükséges!") + templates.SetFlash(c, "LoginMessage", "A kért oldal megtekintéséhez belépés szükséges!") to := "" if c.Request().Method == "GET" { to = "?next=" + c.Request().URL.Path } return c.Redirect(http.StatusFound, "/user/login"+to) } - -func UnauthorizedError(c echo.Context) error { - return echo.NewHTTPError(http.StatusUnauthorized, errors.New("unauthorized")) -} - -func GetJWT(cfg config.Keys) (string, error) { - if cfg.PrivateKey == nil { - return "", nil - } - - claims := &jwt.StandardClaims{ - ExpiresAt: time.Now().Add(10 * time.Minute).Unix(), - NotBefore: time.Now().Unix(), - Issuer: "njudge web", - IssuedAt: time.Now().Unix(), - } - - token := jwt.NewWithClaims(jwt.SigningMethodRS512, claims) - jwt, err := token.SignedString(cfg.PrivateKey) - if err != nil { - return "", err - } - - return jwt, nil -} diff --git a/internal/web/helpers/templates/funcs.go b/internal/web/helpers/templates/funcs.go index 2e9fc4a5..61843a23 100644 --- a/internal/web/helpers/templates/funcs.go +++ b/internal/web/helpers/templates/funcs.go @@ -6,6 +6,7 @@ import ( "encoding/json" "errors" "fmt" + "github.com/mraron/njudge/internal/web/templates" "github.com/mraron/njudge/pkg/language/memory" "html/template" "math" @@ -16,10 +17,8 @@ import ( "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" "github.com/mraron/njudge/internal/njudge" - "github.com/mraron/njudge/internal/web/helpers" "github.com/mraron/njudge/internal/web/helpers/i18n" "github.com/mraron/njudge/internal/web/helpers/roles" - "github.com/mraron/njudge/internal/web/helpers/templates/partials" "github.com/mraron/njudge/pkg/problems" "golang.org/x/text/message" ) @@ -52,7 +51,7 @@ func contextFuncs(c echo.Context) template.FuncMap { return &val.Value }, "getFlash": func(name string) interface{} { - return helpers.GetFlash(c, name) + return templates.GetFlash(c, name) }, "csrf": func() string { return c.Get(middleware.DefaultCSRFConfig.ContextKey).(string) @@ -66,7 +65,7 @@ func contextFuncs(c echo.Context) template.FuncMap { } } -func statelessFuncs(store problems.Store, users njudge.Users, ps njudge.Problems, tags njudge.Tags, store2 partials.Store) template.FuncMap { +func statelessFuncs(store problems.Store, users njudge.Users, ps njudge.Problems, tags njudge.Tags, store2 templates.Store) template.FuncMap { return template.FuncMap{ "translateContent": i18n.TranslateContent, "problem": store.GetProblem, diff --git a/internal/web/helpers/templates/templates.go b/internal/web/helpers/templates/templates.go index 34d4d797..d125208d 100644 --- a/internal/web/helpers/templates/templates.go +++ b/internal/web/helpers/templates/templates.go @@ -17,7 +17,6 @@ import ( "github.com/labstack/echo/v4" "github.com/mraron/njudge/internal/web/helpers/config" - "github.com/mraron/njudge/internal/web/helpers/templates/partials" "github.com/mraron/njudge/pkg/problems" ) @@ -28,12 +27,12 @@ type Renderer struct { users njudge.Users problems njudge.Problems tags njudge.Tags - partialsStore partials.Store + partialsStore templates.Store sync.RWMutex } -func New(cfg config.Server, problemStore problems.Store, users njudge.Users, ps njudge.Problems, tags njudge.Tags, partialsStore partials.Store) *Renderer { +func New(cfg config.Server, problemStore problems.Store, users njudge.Users, ps njudge.Problems, tags njudge.Tags, partialsStore templates.Store) *Renderer { renderer := &Renderer{ templates: make(map[string]*template.Template), cfg: cfg, diff --git a/internal/web/helpers/ui/link.go b/internal/web/helpers/ui/link.go deleted file mode 100644 index 1360d269..00000000 --- a/internal/web/helpers/ui/link.go +++ /dev/null @@ -1,6 +0,0 @@ -package ui - -type Link struct { - Text string - Href string -} diff --git a/internal/web/helpers/ui/sortcolumn.go b/internal/web/helpers/ui/sortcolumn.go deleted file mode 100644 index 8285fcbf..00000000 --- a/internal/web/helpers/ui/sortcolumn.go +++ /dev/null @@ -1,6 +0,0 @@ -package ui - -type SortColumn struct { - Order string - Href string -} diff --git a/internal/web/routes.go b/internal/web/routes.go index f90af303..b5d6b982 100644 --- a/internal/web/routes.go +++ b/internal/web/routes.go @@ -1,6 +1,7 @@ package web import ( + "github.com/mraron/njudge/internal/web/templates" "strings" "github.com/labstack/echo/v4/middleware" @@ -14,7 +15,6 @@ import ( "github.com/mraron/njudge/internal/web/handlers/taskarchive" "github.com/mraron/njudge/internal/web/handlers/user" "github.com/mraron/njudge/internal/web/handlers/user/profile" - "github.com/mraron/njudge/internal/web/helpers" ) func (s *Server) prepareRoutes(e *echo.Echo) { @@ -27,9 +27,11 @@ func (s *Server) prepareRoutes(e *echo.Echo) { })) e.Use(i18n.SetTranslatorMiddleware()) e.Use(user.SetUserMiddleware(s.Users)) - e.Use(helpers.ClearTemporaryFlashes()) + e.Use(templates.MoveFlashesToContextMiddleware()) + e.Use(templates.ClearTemporaryFlashesMiddleware()) + e.Use(templates.Middleware(s.Users, s.Problems, s.ProblemStore, s.PartialsStore)) - e.GET("/", handlers.GetHome()) + e.GET("/", handlers.GetHome(s.PartialsStore)) e.GET("/page/:page", handlers.GetPage(s.PartialsStore)) e.Static("/static", "static") @@ -39,7 +41,7 @@ func (s *Server) prepareRoutes(e *echo.Echo) { e.GET("/task_archive", taskarchive.Get(s.Categories, s.ProblemQuery, s.SolvedStatusQuery, s.ProblemStore)) ps := e.Group("/problemset", problemset.SetNameMiddleware()) - ps.GET("/:name/", problemset.GetProblemList(s.ProblemStore, s.Problems, s.Categories, s.ProblemListQuery, s.ProblemInfoQuery)) + ps.GET("/:name/", problemset.GetProblemList(s.ProblemStore, s.Problems, s.Categories, s.ProblemListQuery, s.ProblemInfoQuery, s.Tags)) ps.POST("/:name/submit", problemset.PostSubmit(s.SubmitService), user.RequireLoginMiddleware()) ps.GET("/status/", problemset.GetStatus(s.SubmissionListQuery)).Name = "getProblemsetStatus" @@ -123,6 +125,6 @@ func (s *Server) prepareRoutes(e *echo.Echo) { v1.PUT("/submissions/:id", api.Put[models.Submission](submissionDataProvider)) v1.DELETE("/submissions/:id", api.Delete[models.Submission](submissionDataProvider)) - e.GET("/admin", handlers.GetAdmin(s.Server), user.RequireLoginMiddleware()) + e.GET("/admin", handlers.GetAdmin(), user.RequireLoginMiddleware()) } } diff --git a/internal/web/server.go b/internal/web/server.go index 79180963..d0aa41d5 100644 --- a/internal/web/server.go +++ b/internal/web/server.go @@ -1,18 +1,16 @@ package web import ( - "context" "database/sql" + "github.com/mraron/njudge/internal/web/templates" _ "mime" "github.com/mraron/njudge/internal/njudge" "github.com/mraron/njudge/internal/njudge/email" - "github.com/mraron/njudge/internal/web/helpers/config" - "github.com/mraron/njudge/internal/web/helpers/templates/partials" - "github.com/labstack/echo/v4" _ "github.com/lib/pq" + "github.com/mraron/njudge/internal/web/helpers/config" "github.com/mraron/njudge/pkg/problems" _ "github.com/mraron/njudge/pkg/problems/config/feladat_txt" _ "github.com/mraron/njudge/pkg/problems/config/polygon" @@ -30,7 +28,7 @@ type Server struct { ProblemStore problems.Store MailService email.Service - PartialsStore partials.Store + PartialsStore templates.Store Categories njudge.Categories Tags njudge.Tags @@ -60,18 +58,3 @@ func (s *Server) Run() { panic(s.e.Start(":" + s.Port)) } - -func (s *Server) Submit(uid int, problemset, problem, language string, source []byte) (int, error) { - sub, err := s.SubmitService.Submit(context.Background(), njudge.SubmitRequest{ - UserID: uid, - Problemset: problemset, - Problem: problem, - Language: language, - Source: source, - }) - - if err != nil { - return -1, err - } - return sub.ID, nil -} diff --git a/internal/web/templates/404.gohtml b/internal/web/templates/404.gohtml deleted file mode 100644 index e9404c62..00000000 --- a/internal/web/templates/404.gohtml +++ /dev/null @@ -1,6 +0,0 @@ -{{template "header" .}} - -Upsz!
-{{.Data}} - -{{template "footer" .}} \ No newline at end of file diff --git a/internal/web/templates/_layout.gohtml b/internal/web/templates/_layout.gohtml index 8d4277b1..c68e4aec 100644 --- a/internal/web/templates/_layout.gohtml +++ b/internal/web/templates/_layout.gohtml @@ -22,6 +22,8 @@ + + {{ str2html (partial "custom_head") }} @@ -110,13 +112,14 @@ - + + + + {{ str2html (partial "custom_footer") }} diff --git a/internal/web/templates/admin.gohtml b/internal/web/templates/admin.gohtml deleted file mode 100644 index a55bd8e3..00000000 --- a/internal/web/templates/admin.gohtml +++ /dev/null @@ -1,134 +0,0 @@ - - - - njudge admin - - - -
- - - - \ No newline at end of file diff --git a/internal/web/templates/admin.templ b/internal/web/templates/admin.templ new file mode 100644 index 00000000..eb541335 --- /dev/null +++ b/internal/web/templates/admin.templ @@ -0,0 +1,138 @@ +package templates + +templ Admin() { + + + + njudge admin + + + +
+ + + + +} \ No newline at end of file diff --git a/internal/web/templates/error.gohtml b/internal/web/templates/error.gohtml deleted file mode 100644 index 491bb9ea..00000000 --- a/internal/web/templates/error.gohtml +++ /dev/null @@ -1,3 +0,0 @@ -{{template "header" .}} - -{{template "footer" .}} \ No newline at end of file diff --git a/internal/web/helpers/flash.go b/internal/web/templates/flash.go similarity index 64% rename from internal/web/helpers/flash.go rename to internal/web/templates/flash.go index 623db5fd..90b21e58 100644 --- a/internal/web/helpers/flash.go +++ b/internal/web/templates/flash.go @@ -1,14 +1,19 @@ -package helpers +package templates import ( "encoding/base64" "encoding/json" "net/http" + "strings" "time" "github.com/labstack/echo/v4" ) +const ( + TopMessageContextKey = "_top_message" +) + func SetFlash(c echo.Context, name string, value interface{}) { val, _ := json.Marshal(value) c.SetCookie(&http.Cookie{Name: "flash" + name, Value: base64.URLEncoding.EncodeToString(val), Path: "/"}) @@ -44,10 +49,24 @@ func GetFlash(c echo.Context, name string) interface{} { return res } -func ClearTemporaryFlashes() echo.MiddlewareFunc { +func MoveFlashesToContextMiddleware() echo.MiddlewareFunc { + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + for _, cookie := range c.Cookies() { + if strings.HasPrefix(cookie.Name, "flash") { + without, _ := strings.CutPrefix(cookie.Name, "flash") + c.Set(without, GetFlash(c, without)) + } + } + return next(c) + } + } +} + +func ClearTemporaryFlashesMiddleware() echo.MiddlewareFunc { return func(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { - DeleteFlash(c, "TopMessage") + DeleteFlash(c, TopMessageContextKey) return next(c) } } diff --git a/internal/web/templates/home.gohtml b/internal/web/templates/home.gohtml deleted file mode 100644 index 8a4df789..00000000 --- a/internal/web/templates/home.gohtml +++ /dev/null @@ -1,5 +0,0 @@ -{{template "header" .}} - -{{str2html (partial "home")}} - -{{template "footer" .}} \ No newline at end of file diff --git a/internal/web/templates/message.gohtml b/internal/web/templates/message.gohtml deleted file mode 100644 index bb031fc7..00000000 --- a/internal/web/templates/message.gohtml +++ /dev/null @@ -1,3 +0,0 @@ -{{template "header" .}} - -{{template "footer" .}} \ No newline at end of file diff --git a/internal/web/templates/misc.go b/internal/web/templates/misc.go new file mode 100644 index 00000000..c3ecf4c2 --- /dev/null +++ b/internal/web/templates/misc.go @@ -0,0 +1,16 @@ +package templates + +import ( + "github.com/a-h/templ" + "github.com/mraron/njudge/internal/njudge" +) + +type Link struct { + Text string + Href templ.SafeURL +} + +type SortColumn struct { + Order njudge.SortDirection + Href string +} diff --git a/internal/web/templates/page.gohtml b/internal/web/templates/page.gohtml deleted file mode 100644 index b1fd8c31..00000000 --- a/internal/web/templates/page.gohtml +++ /dev/null @@ -1,3 +0,0 @@ -{{template "header" .}} -{{.Data.Contents}} -{{template "footer" .}} \ No newline at end of file diff --git a/internal/web/helpers/pagination/pagination.go b/internal/web/templates/pagination.go similarity index 73% rename from internal/web/helpers/pagination/pagination.go rename to internal/web/templates/pagination.go index acefe5e9..776dcac8 100644 --- a/internal/web/helpers/pagination/pagination.go +++ b/internal/web/templates/pagination.go @@ -1,4 +1,4 @@ -package pagination +package templates import ( "errors" @@ -7,19 +7,19 @@ import ( "strconv" ) -type Data struct { +type PaginationData struct { Page int `query:"_page"` PerPage int `query:"_perPage"` SortDir string `query:"_sortDir"` SortField string `query:"_sortField"` } -func Parse(c echo.Context) (*Data, error) { - data := &Data{} +func ParsePaginationData(c echo.Context) (*PaginationData, error) { + data := &PaginationData{} return data, c.Bind(data) } -type Link struct { +type PaginationLink struct { Name string Active bool Disabled bool @@ -27,10 +27,10 @@ type Link struct { } // Links generates links for cnt elements such that every page contains perPage elements, and we are on page -func Links(page, perPage int, cnt int64, qu url.Values) ([]Link, error) { +func Links(page, perPage int, cnt int64, qu url.Values) ([]PaginationLink, error) { pageCnt := (int(cnt) + perPage - 1) / perPage - pages := make([]Link, pageCnt+2) - pages[0] = Link{"«", false, true, "#"} + pages := make([]PaginationLink, pageCnt+2) + pages[0] = PaginationLink{"«", false, true, "#"} if page > 1 { qu.Set("page", strconv.Itoa(page-1)) @@ -39,13 +39,13 @@ func Links(page, perPage int, cnt int64, qu url.Values) ([]Link, error) { } for i := 1; i < len(pages)-1; i++ { qu.Set("page", strconv.Itoa(i)) - pages[i] = Link{strconv.Itoa(i), false, false, "?" + qu.Encode()} + pages[i] = PaginationLink{strconv.Itoa(i), false, false, "?" + qu.Encode()} if i == page { pages[i].Active = true pages[i].Disabled = true } } - pages[len(pages)-1] = Link{"»", false, true, "#"} + pages[len(pages)-1] = PaginationLink{"»", false, true, "#"} if page < pageCnt { qu.Set("page", strconv.Itoa(page+1)) @@ -69,15 +69,15 @@ func abs(x int) int { } // LinksWithCountLimit filters links generated by Links around page in a pageLimit range -func LinksWithCountLimit(page, perPage int, cnt int64, qu url.Values, pageLimit int) ([]Link, error) { +func LinksWithCountLimit(page, perPage int, cnt int64, qu url.Values, pageLimit int) ([]PaginationLink, error) { links, err := Links(page, perPage, cnt, qu) if err != nil { return links, err } - empty := Link{"...", false, true, "#"} + empty := PaginationLink{"...", false, true, "#"} - ans := make([]Link, 0) + ans := make([]PaginationLink, 0) ans = append(ans, links[0]) for i := 1; i < len(links)-1; i++ { diff --git a/internal/web/helpers/pagination/pagination_test.go b/internal/web/templates/pagination_test.go similarity index 92% rename from internal/web/helpers/pagination/pagination_test.go rename to internal/web/templates/pagination_test.go index eaab9675..f23e75ef 100644 --- a/internal/web/helpers/pagination/pagination_test.go +++ b/internal/web/templates/pagination_test.go @@ -1,4 +1,4 @@ -package pagination +package templates import ( "net/url" @@ -9,7 +9,7 @@ func TestLinks(t *testing.T) { qu := url.Values{} qu.Set("njudge", "yes") - removeErr := func(l []Link, err error) []Link { + removeErr := func(l []PaginationLink, err error) []PaginationLink { if err != nil { t.Error(err) } @@ -17,12 +17,12 @@ func TestLinks(t *testing.T) { } var tests = []struct { - got []Link - expected []Link + got []PaginationLink + expected []PaginationLink }{ { got: removeErr(Links(2, 1, 3, qu)), - expected: []Link{ + expected: []PaginationLink{ {Name: "«", Active: false, Disabled: false, Url: "?njudge=yes&page=1"}, {Name: "1", Active: false, Disabled: false, Url: "?njudge=yes&page=1"}, {Name: "2", Active: true, Disabled: true, Url: "?njudge=yes&page=2"}, @@ -32,7 +32,7 @@ func TestLinks(t *testing.T) { }, { got: removeErr(Links(1, 1, 3, qu)), - expected: []Link{ + expected: []PaginationLink{ {Name: "«", Active: false, Disabled: true, Url: "#"}, {Name: "1", Active: true, Disabled: true, Url: "?njudge=yes&page=1"}, {Name: "2", Active: false, Disabled: false, Url: "?njudge=yes&page=2"}, @@ -42,7 +42,7 @@ func TestLinks(t *testing.T) { }, { got: removeErr(LinksWithCountLimit(50, 1, 100, qu, 2)), - expected: []Link{ + expected: []PaginationLink{ {Name: "«", Active: false, Disabled: false, Url: "?njudge=yes&page=49"}, {Name: "1", Active: false, Disabled: false, Url: "?njudge=yes&page=1"}, {Name: "...", Active: false, Disabled: true, Url: "#"}, @@ -58,7 +58,7 @@ func TestLinks(t *testing.T) { }, { got: removeErr(LinksWithCountLimit(3, 1, 100, qu, 2)), - expected: []Link{ + expected: []PaginationLink{ {Name: "«", Active: false, Disabled: false, Url: "?njudge=yes&page=2"}, {Name: "1", Active: false, Disabled: false, Url: "?njudge=yes&page=1"}, {Name: "2", Active: false, Disabled: false, Url: "?njudge=yes&page=2"}, @@ -72,7 +72,7 @@ func TestLinks(t *testing.T) { }, { got: removeErr(LinksWithCountLimit(3, 1, 100, qu, 1)), - expected: []Link{ + expected: []PaginationLink{ {Name: "«", Active: false, Disabled: false, Url: "?njudge=yes&page=2"}, {Name: "1", Active: false, Disabled: false, Url: "?njudge=yes&page=1"}, {Name: "2", Active: false, Disabled: false, Url: "?njudge=yes&page=2"}, diff --git a/internal/web/helpers/templates/partials/partials.go b/internal/web/templates/partials.go similarity index 82% rename from internal/web/helpers/templates/partials/partials.go rename to internal/web/templates/partials.go index 66b051b0..e60250e1 100644 --- a/internal/web/helpers/templates/partials/partials.go +++ b/internal/web/templates/partials.go @@ -1,9 +1,9 @@ -package partials +package templates import ( "context" "database/sql" - "errors" + "fmt" "time" "github.com/mraron/njudge/internal/njudge/db/models" @@ -11,6 +11,12 @@ import ( . "github.com/volatiletech/sqlboiler/v4/queries/qm" ) +const ( + CustomHeadPartial = "custom_head" + CustomFooterPartial = "custom_footer" + CustomMenuPartial = "custom_menu" +) + type Store interface { Get(name string) (string, error) } @@ -50,5 +56,5 @@ func (pc *Cached) Get(name string) (string, error) { type Empty struct{} func (e Empty) Get(name string) (string, error) { - return "", errors.New("unknown") + return "", fmt.Errorf("no such partial: %s", name) } diff --git a/internal/web/templates/problemset.templ b/internal/web/templates/problemset.templ new file mode 100644 index 00000000..f5207e35 --- /dev/null +++ b/internal/web/templates/problemset.templ @@ -0,0 +1,181 @@ +package templates + +import "github.com/mraron/njudge/internal/njudge" +import "github.com/mraron/njudge/pkg/problems" + +type CategoryFilterOption struct { + Name string + Value string + Selected bool +} + +type ProblemListProblem struct { + Name string + Titles problems.Contents + + Visible bool + UserInfo *njudge.ProblemUserInfo + + ShowTags bool + Tags []njudge.Tag + SolverCount int + + CategoryLink Link +} + +type ProblemListViewModel struct { + Pages []PaginationLink + Problems []ProblemListProblem + SolverSorter SortColumn + + Filtered bool + + Tags []njudge.Tag + TitleFilter string + TagsFilter string + CategoryFilterOptions []CategoryFilterOption +} + +templ tagList(tags []njudge.Tag) { + for _, tag := range tags { + {tag.Name}  + } +} + +templ solvedStatus(ss njudge.SolvedStatus) { + switch ss { + case njudge.Solved: + + case njudge.Attempted: + + } +} + +templ problemListFilter(vm ProblemListViewModel) { +
+
+
+

+ +

+
+ +
+
+
+
+ + +
+
+ + +
+
+ + + +
+ + if vm.Filtered { + {Tr(ctx, "Clear")} + } +
+
+
+
+
+ +} + +templ ProblemList(vm ProblemListViewModel) { + @Page() { + @problemListFilter(vm) +
+ @Pagination(vm.Pages) + +
+ + + + + + + + + + + + + for _, p := range vm.Problems { + + + + + + + + + } + +
{Tr(ctx, "Identifier")}{Tr(ctx, "Title")}{Tr(ctx, "Category")}{Tr(ctx, "Tags")} + + switch vm.SolverSorter.Order { + case "": + + case njudge.SortASC: + + case njudge.SortDESC: + + } + {Tr(ctx, "Solvers")} + +
+ if p.UserInfo != nil { + @solvedStatus(p.UserInfo.SolvedStatus) + } + {p.Name}{TrCs(ctx, p.Titles)} + if len(p.CategoryLink.Text) > 0 { + {p.CategoryLink.Text} + } + + if p.ShowTags { + @tagList(p.Tags) + } + {d(p.SolverCount)}
+
+ } +} \ No newline at end of file diff --git a/internal/web/templates/problemset/list.gohtml b/internal/web/templates/problemset/list.gohtml index c99a92a2..fc26c522 100644 --- a/internal/web/templates/problemset/list.gohtml +++ b/internal/web/templates/problemset/list.gohtml @@ -1,7 +1,6 @@ {{template "header" .}} - - +
@@ -22,15 +21,20 @@
- {{range $i := .Data.CategoryFilterOptions}} - + {{end}}
- + +
{{if .Data.Filtered}}{{Tr "Clear"}}{{end}} @@ -39,21 +43,30 @@
+ + let previousValues = $('#tagsFilterHidden').val().split(','); + console.log(previousValues); + $('#tagsFilter').val(previousValues); + $('#tagsFilter').select2({ + theme: 'bootstrap4', + allowClear: true, + closeOnSelect: false, + placeholder: "" + }) + + $('#tagsFilter').on('change', function() { + let selectedValues = $(this).val(); // Get selected values as an array + let formattedValues = selectedValues.join(','); // Join array elements with a comma + $('#tagsFilterHidden').val(formattedValues); // Update hidden input value + }) + }); +
{{template "pagination" .Data.Pages}} diff --git a/internal/web/templates/render.go b/internal/web/templates/render.go new file mode 100644 index 00000000..b1155358 --- /dev/null +++ b/internal/web/templates/render.go @@ -0,0 +1,144 @@ +package templates + +import ( + "github.com/a-h/templ" + "github.com/labstack/echo/v4" + "github.com/mraron/njudge/internal/njudge" + "github.com/mraron/njudge/internal/web/helpers/i18n" + "github.com/mraron/njudge/pkg/language/memory" + "github.com/mraron/njudge/pkg/problems" + "golang.org/x/net/context" + "strconv" + "time" +) + +const ( + UserContextKey = "user" + TitleContextKey = "_njudge_title" + + UsersContextKey = "_njudge_users" + ProblemsContextKey = "_njudge_problems" + ProblemsStoreContextKey = "_njudge_problems_store" + PartialsStoreContextKey = "_njudge_partials_store" +) + +func Middleware(users njudge.Users, ps njudge.Problems, problemStore problems.Store, partialsStore Store) echo.MiddlewareFunc { + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + c.Set(UsersContextKey, users) + c.Set(ProblemsContextKey, ps) + c.Set(ProblemsStoreContextKey, problemStore) + c.Set(PartialsStoreContextKey, partialsStore) + return next(c) + } + } +} + +func partial(ctx context.Context, name string) string { + if store, ok := ctx.Value(PartialsStoreContextKey).(Store); ok { + if res, err := store.Get(name); err == nil { + return res + } + } + return "" +} + +func userContext(ctx context.Context) *njudge.User { + if u, ok := ctx.Value("user").(*njudge.User); ok { + return u + } + return nil +} + +func user(ctx context.Context, id int) *njudge.User { + if users, ok := ctx.Value(UsersContextKey).(njudge.Users); ok { + if u, err := users.Get(ctx, id); err == nil { + return u + } + } + return nil +} + +func problem(ctx context.Context, id int) *njudge.Problem { + if ps, ok := ctx.Value(ProblemsContextKey).(njudge.Problems); ok { + if p, err := ps.Get(ctx, id); err == nil { + return p + } + } + return nil +} + +func problemWithStored(ctx context.Context, p *njudge.Problem) *njudge.ProblemStoredData { + if p != nil { + if store, ok := ctx.Value(ProblemsStoreContextKey).(problems.Store); ok { + if res, err := p.WithStoredData(store); err == nil { + return &res + } + return nil + } + } + return nil +} + +func d(x int) string { + return strconv.Itoa(x) +} + +func f(f float64) string { + return strconv.FormatFloat(f, 'f', -1, 64) +} + +func memKib(a memory.Amount) string { + return d(int(a / memory.KiB)) +} + +func iif[T any](b bool, i, e T) T { + if b { + return i + } + return e +} + +func TrCs(ctx context.Context, cs problems.Contents) string { + tr := ctx.Value(i18n.TranslatorContextKey).(i18n.Translator) + return tr.TranslateContent(cs).String() +} + +func Tr(ctx context.Context, key string, args ...any) string { + tr := ctx.Value(i18n.TranslatorContextKey).(i18n.Translator) + return tr.Translate(key, args...) +} + +type echoContextWrapper struct { + echo.Context +} + +func (e echoContextWrapper) Deadline() (deadline time.Time, ok bool) { + return e.Request().Context().Deadline() +} + +func (e echoContextWrapper) Done() <-chan struct{} { + return e.Request().Context().Done() +} + +func (e echoContextWrapper) Err() error { + return e.Request().Context().Err() +} + +func (e echoContextWrapper) Value(key any) any { + if _, ok := key.(string); !ok { + return e.Request().Context().Value(key) + } + return e.Get(key.(string)) +} + +func Render(ctx echo.Context, statusCode int, t templ.Component) error { + buf := templ.GetBuffer() + defer templ.ReleaseBuffer(buf) + + if err := t.Render(echoContextWrapper{ctx}, buf); err != nil { + return err + } + + return ctx.HTML(statusCode, buf.String()) +} diff --git a/internal/web/templates/status.gohtml b/internal/web/templates/status.gohtml deleted file mode 100644 index 72c342e2..00000000 --- a/internal/web/templates/status.gohtml +++ /dev/null @@ -1,12 +0,0 @@ -{{template "header" .}} -{{template "submissions" .Data}} - -{{template "footer" .}} \ No newline at end of file diff --git a/package.json b/package.json index 2634a5fb..1576bd24 100644 --- a/package.json +++ b/package.json @@ -3,8 +3,13 @@ "version": "0.1.0", "author": "Áron Noszály", "dependencies": { + "@ttskch/select2-bootstrap4-theme": "^1.5.2", "bootstrap": "^4.6.0", - "ng-admin": "*" + "bootstrap-icons": "^1.11.3", + "jquery": "^3.7.1", + "katex": "^0.16.10", + "ng-admin": "*", + "select2": "^4.0.13" }, "devDependencies": { "gulp": "^4.0.2", diff --git a/pkg/problems/data.go b/pkg/problems/data.go index 4f2ac4eb..b032b495 100644 --- a/pkg/problems/data.go +++ b/pkg/problems/data.go @@ -49,7 +49,7 @@ func (s BytesData) Type() string { } var ( - DataTypeText = "text" + DataTypeText = "text/plain" DataTypeHTML = "text/html" DataTypePDF = "application/pdf" ) diff --git a/static/css/main.min.css b/static/css/main.min.css index 4393c809..eca57eea 100644 --- a/static/css/main.min.css +++ b/static/css/main.min.css @@ -6,4 +6,6 @@ html{position:relative;min-height:100%}body{margin-bottom:90px}.footer{position: * Copyright 2011-2021 The Bootstrap Authors * Copyright 2011-2021 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) - */:root{--blue:#007bff;--indigo:#6610f2;--purple:#6f42c1;--pink:#e83e8c;--red:#dc3545;--orange:#fd7e14;--yellow:#ffc107;--green:#28a745;--teal:#20c997;--cyan:#17a2b8;--white:#fff;--gray:#6c757d;--gray-dark:#343a40;--primary:#007bff;--secondary:#6c757d;--success:#28a745;--info:#17a2b8;--warning:#ffc107;--danger:#dc3545;--light:#f8f9fa;--dark:#343a40;--breakpoint-xs:0;--breakpoint-sm:576px;--breakpoint-md:768px;--breakpoint-lg:992px;--breakpoint-xl:1200px;--font-family-sans-serif:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-family-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}footer,header,main,nav{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus:not(:focus-visible){outline:0!important}h2,h5{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}ul{margin-top:0;margin-bottom:1rem}ul ul{margin-bottom:0}b{font-weight:bolder}sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([class]){color:inherit;text-decoration:none}a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,pre{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto;-ms-overflow-style:scrollbar}img{vertical-align:middle;border-style:none}table{border-collapse:collapse}th{text-align:inherit;text-align:-webkit-match-parent}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}[type=button],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox]{box-sizing:border-box;padding:0}textarea{overflow:auto;resize:vertical}output{display:inline-block}template{display:none}[hidden]{display:none!important}.h2,.h5,h2,h5{margin-bottom:.5rem;font-weight:500;line-height:1.2}.h2,h2{font-size:2rem}.h5,h5{font-size:1.25rem}code{font-size:87.5%;color:#e83e8c;word-wrap:break-word}a>code{color:inherit}pre{display:block;font-size:87.5%;color:#212529}pre code{font-size:inherit;color:inherit;word-break:normal}.container,.container-fluid{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:576px){.container{max-width:540px}}@media (min-width:768px){.container{max-width:720px}}@media (min-width:992px){.container{max-width:960px}}@media (min-width:1200px){.container{max-width:1140px}}.row{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-15px;margin-left:-15px}.col,.col-12,.col-6,.col-lg-3,.col-lg-9,.col-md-auto,.col-xl-9{position:relative;width:100%;padding-right:15px;padding-left:15px}.col{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}@media (min-width:768px){.col-md-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}}@media (min-width:992px){.col-lg-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-lg-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}}@media (min-width:1200px){.col-xl-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}}.table{width:100%;margin-bottom:1rem;color:#212529}.table td,.table th{padding:.75rem;vertical-align:top;border-top:1px solid #dee2e6}.table thead th{vertical-align:bottom;border-bottom:2px solid #dee2e6}.table tbody+tbody{border-top:2px solid #dee2e6}.table-sm td,.table-sm th{padding:.3rem}.table-bordered{border:1px solid #dee2e6}.table-bordered td,.table-bordered th{border:1px solid #dee2e6}.table-bordered thead td,.table-bordered thead th{border-bottom-width:2px}.table-warning,.table-warning>td,.table-warning>th{background-color:#ffeeba}.table-warning tbody+tbody,.table-warning td,.table-warning th,.table-warning thead th{border-color:#ffdf7e}.table-active,.table-active>td,.table-active>th{background-color:rgba(0,0,0,.075)}.table-responsive{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive>.table-bordered{border:0}.form-control{display:block;width:100%;height:calc(1.5em + .75rem + 2px);padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control::-ms-expand{background-color:transparent;border:0}.form-control:-moz-focusring{color:transparent;text-shadow:0 0 0 #495057}.form-control:focus{color:#495057;background-color:#fff;border-color:#80bdff;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.form-control::-webkit-input-placeholder{color:#6c757d;opacity:1}.form-control::-moz-placeholder{color:#6c757d;opacity:1}.form-control:-ms-input-placeholder{color:#6c757d;opacity:1}.form-control::-ms-input-placeholder{color:#6c757d;opacity:1}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled{background-color:#e9ecef;opacity:1}select.form-control:focus::-ms-value{color:#495057;background-color:#fff}textarea.form-control{height:auto}.form-group{margin-bottom:1rem}.form-check{position:relative;display:block;padding-left:1.25rem}.form-check-input{position:absolute;margin-top:.3rem;margin-left:-1.25rem}.form-check-input:disabled~.form-check-label,.form-check-input[disabled]~.form-check-label{color:#6c757d}.form-check-label{margin-bottom:0}.btn{display:inline-block;font-weight:400;color:#212529;text-align:center;vertical-align:middle;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-color:transparent;border:1px solid transparent;padding:.375rem .75rem;font-size:1rem;line-height:1.5;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.btn{transition:none}}.btn:hover{color:#212529;text-decoration:none}.btn:focus{outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.btn.disabled,.btn:disabled{opacity:.65}.btn:not(:disabled):not(.disabled){cursor:pointer}a.btn.disabled{pointer-events:none}.btn-primary{color:#fff;background-color:#007bff;border-color:#007bff}.btn-primary:hover{color:#fff;background-color:#0069d9;border-color:#0062cc}.btn-primary:focus{color:#fff;background-color:#0069d9;border-color:#0062cc;box-shadow:0 0 0 .2rem rgba(38,143,255,.5)}.btn-primary.disabled,.btn-primary:disabled{color:#fff;background-color:#007bff;border-color:#007bff}.btn-primary:not(:disabled):not(.disabled).active,.btn-primary:not(:disabled):not(.disabled):active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#0062cc;border-color:#005cbf}.btn-primary:not(:disabled):not(.disabled).active:focus,.btn-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(38,143,255,.5)}.btn-secondary{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:hover{color:#fff;background-color:#5a6268;border-color:#545b62}.btn-secondary:focus{color:#fff;background-color:#5a6268;border-color:#545b62;box-shadow:0 0 0 .2rem rgba(130,138,145,.5)}.btn-secondary.disabled,.btn-secondary:disabled{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:not(:disabled):not(.disabled).active,.btn-secondary:not(:disabled):not(.disabled):active,.show>.btn-secondary.dropdown-toggle{color:#fff;background-color:#545b62;border-color:#4e555b}.btn-secondary:not(:disabled):not(.disabled).active:focus,.btn-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(130,138,145,.5)}.btn-danger{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:hover{color:#fff;background-color:#c82333;border-color:#bd2130}.btn-danger:focus{color:#fff;background-color:#c82333;border-color:#bd2130;box-shadow:0 0 0 .2rem rgba(225,83,97,.5)}.btn-danger.disabled,.btn-danger:disabled{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:not(:disabled):not(.disabled).active,.btn-danger:not(:disabled):not(.disabled):active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#bd2130;border-color:#b21f2d}.btn-danger:not(:disabled):not(.disabled).active:focus,.btn-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(225,83,97,.5)}.btn-link{font-weight:400;color:#007bff;text-decoration:none}.btn-link:hover{color:#0056b3;text-decoration:underline}.btn-link:focus{text-decoration:underline}.btn-link.disabled,.btn-link:disabled{color:#6c757d;pointer-events:none}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:.5rem}input[type=button].btn-block,input[type=submit].btn-block{width:100%}.fade{transition:opacity .15s linear}@media (prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.dropdown{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:10rem;padding:.5rem 0;margin:.125rem 0 0;font-size:1rem;color:#212529;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.15);border-radius:.25rem}.dropdown-menu-right{right:0;left:auto}.dropdown-item{display:block;width:100%;padding:.25rem 1.5rem;clear:both;font-weight:400;color:#212529;text-align:inherit;white-space:nowrap;background-color:transparent;border:0}.dropdown-item:focus,.dropdown-item:hover{color:#16181b;text-decoration:none;background-color:#e9ecef}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#007bff}.dropdown-item.disabled,.dropdown-item:disabled{color:#adb5bd;pointer-events:none;background-color:transparent}.dropdown-menu.show{display:block}.custom-select{display:inline-block;width:100%;height:calc(1.5em + .75rem + 2px);padding:.375rem 1.75rem .375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;vertical-align:middle;background:#fff url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") right .75rem center/8px 10px no-repeat;border:1px solid #ced4da;border-radius:.25rem;-webkit-appearance:none;-moz-appearance:none;appearance:none}.custom-select:focus{border-color:#80bdff;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-select:focus::-ms-value{color:#495057;background-color:#fff}.custom-select:disabled{color:#6c757d;background-color:#e9ecef}.custom-select::-ms-expand{display:none}.custom-select:-moz-focusring{color:transparent;text-shadow:0 0 0 #495057}.custom-select{transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.custom-select{transition:none}}.nav{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:.5rem 1rem}.nav-link:focus,.nav-link:hover{text-decoration:none}.nav-link.disabled{color:#6c757d;pointer-events:none;cursor:default}.nav-tabs{border-bottom:1px solid #dee2e6}.nav-tabs .nav-link{margin-bottom:-1px;border:1px solid transparent;border-top-left-radius:.25rem;border-top-right-radius:.25rem}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{border-color:#e9ecef #e9ecef #dee2e6}.nav-tabs .nav-link.disabled{color:#6c757d;background-color:transparent;border-color:transparent}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{color:#495057;background-color:#fff;border-color:#dee2e6 #dee2e6 #fff}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.navbar{position:relative;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:center;align-items:center;-ms-flex-pack:justify;justify-content:space-between;padding:.5rem 1rem}.navbar .container,.navbar .container-fluid{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:center;align-items:center;-ms-flex-pack:justify;justify-content:space-between}.navbar-brand{display:inline-block;padding-top:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;line-height:inherit;white-space:nowrap}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-nav{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static;float:none}.navbar-collapse{-ms-flex-preferred-size:100%;flex-basis:100%;-ms-flex-positive:1;flex-grow:1;-ms-flex-align:center;align-items:center}.navbar-toggler{padding:.25rem .75rem;font-size:1.25rem;line-height:1;background-color:transparent;border:1px solid transparent;border-radius:.25rem}.navbar-toggler:focus,.navbar-toggler:hover{text-decoration:none}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;content:"";background:50%/100% 100% no-repeat}@media (max-width:991.98px){.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid{padding-right:0;padding-left:0}}@media (min-width:992px){.navbar-expand-lg{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-lg .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-lg .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}}.navbar-dark .navbar-brand{color:#fff}.navbar-dark .navbar-brand:focus,.navbar-dark .navbar-brand:hover{color:#fff}.navbar-dark .navbar-nav .nav-link{color:rgba(255,255,255,.5)}.navbar-dark .navbar-nav .nav-link:focus,.navbar-dark .navbar-nav .nav-link:hover{color:rgba(255,255,255,.75)}.navbar-dark .navbar-nav .nav-link.disabled{color:rgba(255,255,255,.25)}.navbar-dark .navbar-nav .active>.nav-link,.navbar-dark .navbar-nav .nav-link.active,.navbar-dark .navbar-nav .nav-link.show,.navbar-dark .navbar-nav .show>.nav-link{color:#fff}.navbar-dark .navbar-toggler{color:rgba(255,255,255,.5);border-color:rgba(255,255,255,.1)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.5%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.card{position:relative;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid rgba(0,0,0,.125);border-radius:.25rem}.card-body{-ms-flex:1 1 auto;flex:1 1 auto;min-height:1px;padding:1.25rem}.card-header{padding:.75rem 1.25rem;margin-bottom:0;background-color:rgba(0,0,0,.03);border-bottom:1px solid rgba(0,0,0,.125)}.card-header:first-child{border-radius:calc(.25rem - 1px) calc(.25rem - 1px) 0 0}.card-footer{padding:.75rem 1.25rem;background-color:rgba(0,0,0,.03);border-top:1px solid rgba(0,0,0,.125)}.card-footer:last-child{border-radius:0 0 calc(.25rem - 1px) calc(.25rem - 1px)}.accordion{overflow-anchor:none}.accordion>.card{overflow:hidden}.accordion>.card:not(:last-of-type){border-bottom:0;border-bottom-right-radius:0;border-bottom-left-radius:0}.accordion>.card:not(:first-of-type){border-top-left-radius:0;border-top-right-radius:0}.accordion>.card>.card-header{border-radius:0;margin-bottom:-1px}.pagination{display:-ms-flexbox;display:flex;padding-left:0;list-style:none;border-radius:.25rem}.page-link{position:relative;display:block;padding:.5rem .75rem;margin-left:-1px;line-height:1.25;color:#007bff;background-color:#fff;border:1px solid #dee2e6}.page-link:hover{z-index:2;color:#0056b3;text-decoration:none;background-color:#e9ecef;border-color:#dee2e6}.page-link:focus{z-index:3;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.page-item:first-child .page-link{margin-left:0;border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.page-item:last-child .page-link{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.page-item.active .page-link{z-index:3;color:#fff;background-color:#007bff;border-color:#007bff}.page-item.disabled .page-link{color:#6c757d;pointer-events:none;cursor:auto;background-color:#fff;border-color:#dee2e6}.badge{display:inline-block;padding:.25em .4em;font-size:75%;font-weight:700;line-height:1;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.badge{transition:none}}a.badge:focus,a.badge:hover{text-decoration:none}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.badge-secondary{color:#fff;background-color:#6c757d}a.badge-secondary:focus,a.badge-secondary:hover{color:#fff;background-color:#545b62}a.badge-secondary:focus{outline:0;box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.alert{position:relative;padding:.75rem 1.25rem;margin-bottom:1rem;border:1px solid transparent;border-radius:.25rem}.alert-secondary{color:#383d41;background-color:#e2e3e5;border-color:#d6d8db}.alert-info{color:#0c5460;background-color:#d1ecf1;border-color:#bee5eb}.alert-danger{color:#721c24;background-color:#f8d7da;border-color:#f5c6cb}@-webkit-keyframes progress-bar-stripes{to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}.close{float:right;font-size:1.5rem;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;opacity:.5}.close:hover{color:#000;text-decoration:none}.close:not(:disabled):not(.disabled):focus,.close:not(:disabled):not(.disabled):hover{opacity:.75}button.close{padding:0;background-color:transparent;border:0}a.close.disabled{pointer-events:none}.modal-open{overflow:hidden}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal{position:fixed;top:0;left:0;z-index:1050;display:none;width:100%;height:100%;overflow:hidden;outline:0}.modal-dialog{position:relative;width:auto;margin:.5rem;pointer-events:none}.modal.fade .modal-dialog{transition:-webkit-transform .3s ease-out;transition:transform .3s ease-out;transition:transform .3s ease-out,-webkit-transform .3s ease-out;-webkit-transform:translate(0,-50px);transform:translate(0,-50px)}@media (prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{-webkit-transform:none;transform:none}.modal.modal-static .modal-dialog{-webkit-transform:scale(1.02);transform:scale(1.02)}.modal-dialog-scrollable{display:-ms-flexbox;display:flex;max-height:calc(100% - 1rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 1rem);overflow:hidden}.modal-dialog-scrollable .modal-footer,.modal-dialog-scrollable .modal-header{-ms-flex-negative:0;flex-shrink:0}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;min-height:calc(100% - 1rem)}.modal-dialog-centered::before{display:block;height:calc(100vh - 1rem);height:-webkit-min-content;height:-moz-min-content;height:min-content;content:""}.modal-dialog-centered.modal-dialog-scrollable{-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:center;justify-content:center;height:100%}.modal-dialog-centered.modal-dialog-scrollable .modal-content{max-height:none}.modal-dialog-centered.modal-dialog-scrollable::before{content:none}.modal-content{position:relative;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem;outline:0}.modal-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{display:-ms-flexbox;display:flex;-ms-flex-align:start;align-items:flex-start;-ms-flex-pack:justify;justify-content:space-between;padding:1rem 1rem;border-bottom:1px solid #dee2e6;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.modal-header .close{padding:1rem 1rem;margin:-1rem -1rem -1rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;-ms-flex:1 1 auto;flex:1 1 auto;padding:1rem}.modal-footer{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:center;align-items:center;-ms-flex-pack:end;justify-content:flex-end;padding:.75rem;border-top:1px solid #dee2e6;border-bottom-right-radius:calc(.3rem - 1px);border-bottom-left-radius:calc(.3rem - 1px)}.modal-footer>*{margin:.25rem}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-scrollable{max-height:calc(100% - 3.5rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 3.5rem)}.modal-dialog-centered{min-height:calc(100% - 3.5rem)}.modal-dialog-centered::before{height:calc(100vh - 3.5rem);height:-webkit-min-content;height:-moz-min-content;height:min-content}.modal-sm{max-width:300px}}@media (min-width:992px){.modal-lg,.modal-xl{max-width:800px}}@media (min-width:1200px){.modal-xl{max-width:1140px}}@-webkit-keyframes spinner-border{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes spinner-border{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes spinner-grow{0%{-webkit-transform:scale(0);transform:scale(0)}50%{opacity:1;-webkit-transform:none;transform:none}}.align-middle{vertical-align:middle!important}.bg-success{background-color:#28a745!important}a.bg-success:focus,a.bg-success:hover,button.bg-success:focus,button.bg-success:hover{background-color:#1e7e34!important}.bg-warning{background-color:#ffc107!important}a.bg-warning:focus,a.bg-warning:hover,button.bg-warning:focus,button.bg-warning:hover{background-color:#d39e00!important}.bg-danger{background-color:#dc3545!important}a.bg-danger:focus,a.bg-danger:hover,button.bg-danger:focus,button.bg-danger:hover{background-color:#bd2130!important}.bg-dark{background-color:#343a40!important}a.bg-dark:focus,a.bg-dark:hover,button.bg-dark:focus,button.bg-dark:hover{background-color:#1d2124!important}.border{border:1px solid #dee2e6!important}.border-bottom{border-bottom:1px solid #dee2e6!important}@media (min-width:768px){.justify-content-md-center{-ms-flex-pack:center!important;justify-content:center!important}}.m-0{margin:0!important}.mb-0{margin-bottom:0!important}.p-0{padding:0!important}.mr-auto{margin-right:auto!important}.ml-auto{margin-left:auto!important}.text-left{text-align:left!important}.text-center{text-align:center!important}.font-weight-bold{font-weight:700!important}.font-weight-bolder{font-weight:bolder!important}.text-success{color:#28a745!important}a.text-success:focus,a.text-success:hover{color:#19692c!important}.text-info{color:#17a2b8!important}a.text-info:focus,a.text-info:hover{color:#0f6674!important}.text-danger{color:#dc3545!important}a.text-danger:focus,a.text-danger:hover{color:#a71d2a!important}.text-muted{color:#6c757d!important}.text-decoration-none{text-decoration:none!important}.text-reset{color:inherit!important}@media print{*,::after,::before{text-shadow:none!important;box-shadow:none!important}a:not(.btn){text-decoration:underline}pre{white-space:pre-wrap!important}pre{border:1px solid #adb5bd;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}h2,p{orphans:3;widows:3}h2{page-break-after:avoid}@page{size:a3}body{min-width:992px!important}.container{min-width:992px!important}.navbar{display:none}.badge{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #dee2e6!important}} \ No newline at end of file + */:root{--blue:#007bff;--indigo:#6610f2;--purple:#6f42c1;--pink:#e83e8c;--red:#dc3545;--orange:#fd7e14;--yellow:#ffc107;--green:#28a745;--teal:#20c997;--cyan:#17a2b8;--white:#fff;--gray:#6c757d;--gray-dark:#343a40;--primary:#007bff;--secondary:#6c757d;--success:#28a745;--info:#17a2b8;--warning:#ffc107;--danger:#dc3545;--light:#f8f9fa;--dark:#343a40;--breakpoint-xs:0;--breakpoint-sm:576px;--breakpoint-md:768px;--breakpoint-lg:992px;--breakpoint-xl:1200px;--font-family-sans-serif:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-family-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}footer,header,main,nav{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus:not(:focus-visible){outline:0!important}h2,h5{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}ul{margin-top:0;margin-bottom:1rem}ul ul{margin-bottom:0}b{font-weight:bolder}sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([class]){color:inherit;text-decoration:none}a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,pre{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto;-ms-overflow-style:scrollbar}img{vertical-align:middle;border-style:none}table{border-collapse:collapse}th{text-align:inherit;text-align:-webkit-match-parent}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}[type=button],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox]{box-sizing:border-box;padding:0}textarea{overflow:auto;resize:vertical}output{display:inline-block}template{display:none}[hidden]{display:none!important}.h2,.h5,h2,h5{margin-bottom:.5rem;font-weight:500;line-height:1.2}.h2,h2{font-size:2rem}.h5,h5{font-size:1.25rem}code{font-size:87.5%;color:#e83e8c;word-wrap:break-word}a>code{color:inherit}pre{display:block;font-size:87.5%;color:#212529}pre code{font-size:inherit;color:inherit;word-break:normal}.container,.container-fluid{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:576px){.container{max-width:540px}}@media (min-width:768px){.container{max-width:720px}}@media (min-width:992px){.container{max-width:960px}}@media (min-width:1200px){.container{max-width:1140px}}.row{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-15px;margin-left:-15px}.col,.col-12,.col-6,.col-lg-3,.col-lg-9,.col-md-auto,.col-xl-9{position:relative;width:100%;padding-right:15px;padding-left:15px}.col{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}@media (min-width:768px){.col-md-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}}@media (min-width:992px){.col-lg-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-lg-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}}@media (min-width:1200px){.col-xl-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}}.table{width:100%;margin-bottom:1rem;color:#212529}.table td,.table th{padding:.75rem;vertical-align:top;border-top:1px solid #dee2e6}.table thead th{vertical-align:bottom;border-bottom:2px solid #dee2e6}.table tbody+tbody{border-top:2px solid #dee2e6}.table-sm td,.table-sm th{padding:.3rem}.table-bordered{border:1px solid #dee2e6}.table-bordered td,.table-bordered th{border:1px solid #dee2e6}.table-bordered thead td,.table-bordered thead th{border-bottom-width:2px}.table-warning,.table-warning>td,.table-warning>th{background-color:#ffeeba}.table-warning tbody+tbody,.table-warning td,.table-warning th,.table-warning thead th{border-color:#ffdf7e}.table-responsive{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive>.table-bordered{border:0}.form-control{display:block;width:100%;height:calc(1.5em + .75rem + 2px);padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control::-ms-expand{background-color:transparent;border:0}.form-control:-moz-focusring{color:transparent;text-shadow:0 0 0 #495057}.form-control:focus{color:#495057;background-color:#fff;border-color:#80bdff;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.form-control::-webkit-input-placeholder{color:#6c757d;opacity:1}.form-control::-moz-placeholder{color:#6c757d;opacity:1}.form-control:-ms-input-placeholder{color:#6c757d;opacity:1}.form-control::-ms-input-placeholder{color:#6c757d;opacity:1}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled{background-color:#e9ecef;opacity:1}select.form-control:focus::-ms-value{color:#495057;background-color:#fff}select.form-control[multiple]{height:auto}textarea.form-control{height:auto}.form-group{margin-bottom:1rem}.form-check{position:relative;display:block;padding-left:1.25rem}.form-check-input{position:absolute;margin-top:.3rem;margin-left:-1.25rem}.form-check-input:disabled~.form-check-label,.form-check-input[disabled]~.form-check-label{color:#6c757d}.form-check-label{margin-bottom:0}.btn{display:inline-block;font-weight:400;color:#212529;text-align:center;vertical-align:middle;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-color:transparent;border:1px solid transparent;padding:.375rem .75rem;font-size:1rem;line-height:1.5;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.btn{transition:none}}.btn:hover{color:#212529;text-decoration:none}.btn:focus{outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.btn.disabled,.btn:disabled{opacity:.65}.btn:not(:disabled):not(.disabled){cursor:pointer}a.btn.disabled{pointer-events:none}.btn-primary{color:#fff;background-color:#007bff;border-color:#007bff}.btn-primary:hover{color:#fff;background-color:#0069d9;border-color:#0062cc}.btn-primary:focus{color:#fff;background-color:#0069d9;border-color:#0062cc;box-shadow:0 0 0 .2rem rgba(38,143,255,.5)}.btn-primary.disabled,.btn-primary:disabled{color:#fff;background-color:#007bff;border-color:#007bff}.btn-primary:not(:disabled):not(.disabled).active,.btn-primary:not(:disabled):not(.disabled):active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#0062cc;border-color:#005cbf}.btn-primary:not(:disabled):not(.disabled).active:focus,.btn-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(38,143,255,.5)}.btn-secondary{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:hover{color:#fff;background-color:#5a6268;border-color:#545b62}.btn-secondary:focus{color:#fff;background-color:#5a6268;border-color:#545b62;box-shadow:0 0 0 .2rem rgba(130,138,145,.5)}.btn-secondary.disabled,.btn-secondary:disabled{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:not(:disabled):not(.disabled).active,.btn-secondary:not(:disabled):not(.disabled):active,.show>.btn-secondary.dropdown-toggle{color:#fff;background-color:#545b62;border-color:#4e555b}.btn-secondary:not(:disabled):not(.disabled).active:focus,.btn-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(130,138,145,.5)}.btn-danger{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:hover{color:#fff;background-color:#c82333;border-color:#bd2130}.btn-danger:focus{color:#fff;background-color:#c82333;border-color:#bd2130;box-shadow:0 0 0 .2rem rgba(225,83,97,.5)}.btn-danger.disabled,.btn-danger:disabled{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:not(:disabled):not(.disabled).active,.btn-danger:not(:disabled):not(.disabled):active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#bd2130;border-color:#b21f2d}.btn-danger:not(:disabled):not(.disabled).active:focus,.btn-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(225,83,97,.5)}.btn-link{font-weight:400;color:#007bff;text-decoration:none}.btn-link:hover{color:#0056b3;text-decoration:underline}.btn-link:focus{text-decoration:underline}.btn-link.disabled,.btn-link:disabled{color:#6c757d;pointer-events:none}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:.5rem}input[type=button].btn-block,input[type=submit].btn-block{width:100%}.fade{transition:opacity .15s linear}@media (prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.dropdown{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:10rem;padding:.5rem 0;margin:.125rem 0 0;font-size:1rem;color:#212529;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.15);border-radius:.25rem}.dropdown-menu-right{right:0;left:auto}.dropdown-item{display:block;width:100%;padding:.25rem 1.5rem;clear:both;font-weight:400;color:#212529;text-align:inherit;white-space:nowrap;background-color:transparent;border:0}.dropdown-item:focus,.dropdown-item:hover{color:#16181b;text-decoration:none;background-color:#e9ecef}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#007bff}.dropdown-item.disabled,.dropdown-item:disabled{color:#adb5bd;pointer-events:none;background-color:transparent}.dropdown-menu.show{display:block}.custom-select{display:inline-block;width:100%;height:calc(1.5em + .75rem + 2px);padding:.375rem 1.75rem .375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;vertical-align:middle;background:#fff url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") right .75rem center/8px 10px no-repeat;border:1px solid #ced4da;border-radius:.25rem;-webkit-appearance:none;-moz-appearance:none;appearance:none}.custom-select:focus{border-color:#80bdff;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-select:focus::-ms-value{color:#495057;background-color:#fff}.custom-select[multiple]{height:auto;padding-right:.75rem;background-image:none}.custom-select:disabled{color:#6c757d;background-color:#e9ecef}.custom-select::-ms-expand{display:none}.custom-select:-moz-focusring{color:transparent;text-shadow:0 0 0 #495057}.custom-select{transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.custom-select{transition:none}}.nav{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:.5rem 1rem}.nav-link:focus,.nav-link:hover{text-decoration:none}.nav-link.disabled{color:#6c757d;pointer-events:none;cursor:default}.nav-tabs{border-bottom:1px solid #dee2e6}.nav-tabs .nav-link{margin-bottom:-1px;border:1px solid transparent;border-top-left-radius:.25rem;border-top-right-radius:.25rem}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{border-color:#e9ecef #e9ecef #dee2e6}.nav-tabs .nav-link.disabled{color:#6c757d;background-color:transparent;border-color:transparent}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{color:#495057;background-color:#fff;border-color:#dee2e6 #dee2e6 #fff}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.navbar{position:relative;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:center;align-items:center;-ms-flex-pack:justify;justify-content:space-between;padding:.5rem 1rem}.navbar .container,.navbar .container-fluid{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:center;align-items:center;-ms-flex-pack:justify;justify-content:space-between}.navbar-brand{display:inline-block;padding-top:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;line-height:inherit;white-space:nowrap}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-nav{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static;float:none}.navbar-collapse{-ms-flex-preferred-size:100%;flex-basis:100%;-ms-flex-positive:1;flex-grow:1;-ms-flex-align:center;align-items:center}.navbar-toggler{padding:.25rem .75rem;font-size:1.25rem;line-height:1;background-color:transparent;border:1px solid transparent;border-radius:.25rem}.navbar-toggler:focus,.navbar-toggler:hover{text-decoration:none}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;content:"";background:50%/100% 100% no-repeat}@media (max-width:991.98px){.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid{padding-right:0;padding-left:0}}@media (min-width:992px){.navbar-expand-lg{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-lg .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-lg .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}}.navbar-dark .navbar-brand{color:#fff}.navbar-dark .navbar-brand:focus,.navbar-dark .navbar-brand:hover{color:#fff}.navbar-dark .navbar-nav .nav-link{color:rgba(255,255,255,.5)}.navbar-dark .navbar-nav .nav-link:focus,.navbar-dark .navbar-nav .nav-link:hover{color:rgba(255,255,255,.75)}.navbar-dark .navbar-nav .nav-link.disabled{color:rgba(255,255,255,.25)}.navbar-dark .navbar-nav .active>.nav-link,.navbar-dark .navbar-nav .nav-link.active,.navbar-dark .navbar-nav .nav-link.show,.navbar-dark .navbar-nav .show>.nav-link{color:#fff}.navbar-dark .navbar-toggler{color:rgba(255,255,255,.5);border-color:rgba(255,255,255,.1)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.5%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.card{position:relative;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid rgba(0,0,0,.125);border-radius:.25rem}.card-body{-ms-flex:1 1 auto;flex:1 1 auto;min-height:1px;padding:1.25rem}.card-header{padding:.75rem 1.25rem;margin-bottom:0;background-color:rgba(0,0,0,.03);border-bottom:1px solid rgba(0,0,0,.125)}.card-header:first-child{border-radius:calc(.25rem - 1px) calc(.25rem - 1px) 0 0}.card-footer{padding:.75rem 1.25rem;background-color:rgba(0,0,0,.03);border-top:1px solid rgba(0,0,0,.125)}.card-footer:last-child{border-radius:0 0 calc(.25rem - 1px) calc(.25rem - 1px)}.accordion{overflow-anchor:none}.accordion>.card{overflow:hidden}.accordion>.card:not(:last-of-type){border-bottom:0;border-bottom-right-radius:0;border-bottom-left-radius:0}.accordion>.card:not(:first-of-type){border-top-left-radius:0;border-top-right-radius:0}.accordion>.card>.card-header{border-radius:0;margin-bottom:-1px}.pagination{display:-ms-flexbox;display:flex;padding-left:0;list-style:none;border-radius:.25rem}.page-link{position:relative;display:block;padding:.5rem .75rem;margin-left:-1px;line-height:1.25;color:#007bff;background-color:#fff;border:1px solid #dee2e6}.page-link:hover{z-index:2;color:#0056b3;text-decoration:none;background-color:#e9ecef;border-color:#dee2e6}.page-link:focus{z-index:3;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.page-item:first-child .page-link{margin-left:0;border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.page-item:last-child .page-link{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.page-item.active .page-link{z-index:3;color:#fff;background-color:#007bff;border-color:#007bff}.page-item.disabled .page-link{color:#6c757d;pointer-events:none;cursor:auto;background-color:#fff;border-color:#dee2e6}.badge{display:inline-block;padding:.25em .4em;font-size:75%;font-weight:700;line-height:1;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.badge{transition:none}}a.badge:focus,a.badge:hover{text-decoration:none}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.badge-secondary{color:#fff;background-color:#6c757d}a.badge-secondary:focus,a.badge-secondary:hover{color:#fff;background-color:#545b62}a.badge-secondary:focus{outline:0;box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.alert{position:relative;padding:.75rem 1.25rem;margin-bottom:1rem;border:1px solid transparent;border-radius:.25rem}.alert-secondary{color:#383d41;background-color:#e2e3e5;border-color:#d6d8db}.alert-info{color:#0c5460;background-color:#d1ecf1;border-color:#bee5eb}.alert-danger{color:#721c24;background-color:#f8d7da;border-color:#f5c6cb}@-webkit-keyframes progress-bar-stripes{to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}.close{float:right;font-size:1.5rem;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;opacity:.5}.close:hover{color:#000;text-decoration:none}.close:not(:disabled):not(.disabled):focus,.close:not(:disabled):not(.disabled):hover{opacity:.75}button.close{padding:0;background-color:transparent;border:0}a.close.disabled{pointer-events:none}.modal-open{overflow:hidden}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal{position:fixed;top:0;left:0;z-index:1050;display:none;width:100%;height:100%;overflow:hidden;outline:0}.modal-dialog{position:relative;width:auto;margin:.5rem;pointer-events:none}.modal.fade .modal-dialog{transition:-webkit-transform .3s ease-out;transition:transform .3s ease-out;transition:transform .3s ease-out,-webkit-transform .3s ease-out;-webkit-transform:translate(0,-50px);transform:translate(0,-50px)}@media (prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{-webkit-transform:none;transform:none}.modal.modal-static .modal-dialog{-webkit-transform:scale(1.02);transform:scale(1.02)}.modal-dialog-scrollable{display:-ms-flexbox;display:flex;max-height:calc(100% - 1rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 1rem);overflow:hidden}.modal-dialog-scrollable .modal-footer,.modal-dialog-scrollable .modal-header{-ms-flex-negative:0;flex-shrink:0}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;min-height:calc(100% - 1rem)}.modal-dialog-centered::before{display:block;height:calc(100vh - 1rem);height:-webkit-min-content;height:-moz-min-content;height:min-content;content:""}.modal-dialog-centered.modal-dialog-scrollable{-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:center;justify-content:center;height:100%}.modal-dialog-centered.modal-dialog-scrollable .modal-content{max-height:none}.modal-dialog-centered.modal-dialog-scrollable::before{content:none}.modal-content{position:relative;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem;outline:0}.modal-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{display:-ms-flexbox;display:flex;-ms-flex-align:start;align-items:flex-start;-ms-flex-pack:justify;justify-content:space-between;padding:1rem 1rem;border-bottom:1px solid #dee2e6;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.modal-header .close{padding:1rem 1rem;margin:-1rem -1rem -1rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;-ms-flex:1 1 auto;flex:1 1 auto;padding:1rem}.modal-footer{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:center;align-items:center;-ms-flex-pack:end;justify-content:flex-end;padding:.75rem;border-top:1px solid #dee2e6;border-bottom-right-radius:calc(.3rem - 1px);border-bottom-left-radius:calc(.3rem - 1px)}.modal-footer>*{margin:.25rem}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-scrollable{max-height:calc(100% - 3.5rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 3.5rem)}.modal-dialog-centered{min-height:calc(100% - 3.5rem)}.modal-dialog-centered::before{height:calc(100vh - 3.5rem);height:-webkit-min-content;height:-moz-min-content;height:min-content}.modal-sm{max-width:300px}}@media (min-width:992px){.modal-lg,.modal-xl{max-width:800px}}@media (min-width:1200px){.modal-xl{max-width:1140px}}@-webkit-keyframes spinner-border{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes spinner-border{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes spinner-grow{0%{-webkit-transform:scale(0);transform:scale(0)}50%{opacity:1;-webkit-transform:none;transform:none}}.align-middle{vertical-align:middle!important}.bg-success{background-color:#28a745!important}a.bg-success:focus,a.bg-success:hover,button.bg-success:focus,button.bg-success:hover{background-color:#1e7e34!important}.bg-warning{background-color:#ffc107!important}a.bg-warning:focus,a.bg-warning:hover,button.bg-warning:focus,button.bg-warning:hover{background-color:#d39e00!important}.bg-danger{background-color:#dc3545!important}a.bg-danger:focus,a.bg-danger:hover,button.bg-danger:focus,button.bg-danger:hover{background-color:#bd2130!important}.bg-dark{background-color:#343a40!important}a.bg-dark:focus,a.bg-dark:hover,button.bg-dark:focus,button.bg-dark:hover{background-color:#1d2124!important}.border{border:1px solid #dee2e6!important}.border-bottom{border-bottom:1px solid #dee2e6!important}@media (min-width:768px){.justify-content-md-center{-ms-flex-pack:center!important;justify-content:center!important}}.w-100{width:100%!important}.m-0{margin:0!important}.mb-0{margin-bottom:0!important}.p-0{padding:0!important}.mr-auto{margin-right:auto!important}.ml-auto{margin-left:auto!important}.text-left{text-align:left!important}.text-center{text-align:center!important}.font-weight-bold{font-weight:700!important}.font-weight-bolder{font-weight:bolder!important}.text-success{color:#28a745!important}a.text-success:focus,a.text-success:hover{color:#19692c!important}.text-info{color:#17a2b8!important}a.text-info:focus,a.text-info:hover{color:#0f6674!important}.text-danger{color:#dc3545!important}a.text-danger:focus,a.text-danger:hover{color:#a71d2a!important}.text-muted{color:#6c757d!important}.text-decoration-none{text-decoration:none!important}.text-reset{color:inherit!important}@media print{*,::after,::before{text-shadow:none!important;box-shadow:none!important}a:not(.btn){text-decoration:underline}pre{white-space:pre-wrap!important}pre{border:1px solid #adb5bd;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}h2,p{orphans:3;widows:3}h2{page-break-after:avoid}@page{size:a3}body{min-width:992px!important}.container{min-width:992px!important}.navbar{display:none}.badge{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #dee2e6!important}} +.select2-container{box-sizing:border-box;display:inline-block;margin:0;position:relative;vertical-align:middle}.select2-container .select2-selection--single{box-sizing:border-box;cursor:pointer;display:block;height:28px;user-select:none;-webkit-user-select:none}.select2-container .select2-selection--single .select2-selection__rendered{display:block;padding-left:8px;padding-right:20px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.select2-container .select2-selection--single .select2-selection__clear{position:relative}.select2-container[dir=rtl] .select2-selection--single .select2-selection__rendered{padding-right:8px;padding-left:20px}.select2-container .select2-selection--multiple{box-sizing:border-box;cursor:pointer;display:block;min-height:32px;user-select:none;-webkit-user-select:none}.select2-container .select2-selection--multiple .select2-selection__rendered{display:inline-block;overflow:hidden;padding-left:8px;text-overflow:ellipsis;white-space:nowrap}.select2-container .select2-search--inline{float:left}.select2-container .select2-search--inline .select2-search__field{box-sizing:border-box;border:none;font-size:100%;margin-top:5px;padding:0}.select2-container .select2-search--inline .select2-search__field::-webkit-search-cancel-button{-webkit-appearance:none}.select2-dropdown{background-color:#fff;border:1px solid #aaa;border-radius:4px;box-sizing:border-box;display:block;position:absolute;left:-100000px;width:100%;z-index:1051}.select2-results{display:block}.select2-results__options{list-style:none;margin:0;padding:0}.select2-results__option{padding:6px;user-select:none;-webkit-user-select:none}.select2-results__option[aria-selected]{cursor:pointer}.select2-container--open .select2-dropdown{left:0}.select2-container--open .select2-dropdown--above{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0}.select2-container--open .select2-dropdown--below{border-top:none;border-top-left-radius:0;border-top-right-radius:0}.select2-search--dropdown{display:block;padding:4px}.select2-search--dropdown .select2-search__field{padding:4px;width:100%;box-sizing:border-box}.select2-search--dropdown .select2-search__field::-webkit-search-cancel-button{-webkit-appearance:none}.select2-search--dropdown.select2-search--hide{display:none}.select2-close-mask{border:0;margin:0;padding:0;display:block;position:fixed;left:0;top:0;min-height:100%;min-width:100%;height:auto;width:auto;opacity:0;z-index:99;background-color:#fff;filter:alpha(opacity=0)}.select2-hidden-accessible{border:0!important;clip:rect(0 0 0 0)!important;-webkit-clip-path:inset(50%)!important;clip-path:inset(50%)!important;height:1px!important;overflow:hidden!important;padding:0!important;position:absolute!important;width:1px!important;white-space:nowrap!important}.select2-container--default .select2-selection--single{background-color:#fff;border:1px solid #aaa;border-radius:4px}.select2-container--default .select2-selection--single .select2-selection__rendered{color:#444;line-height:28px}.select2-container--default .select2-selection--single .select2-selection__clear{cursor:pointer;float:right;font-weight:700}.select2-container--default .select2-selection--single .select2-selection__placeholder{color:#999}.select2-container--default .select2-selection--single .select2-selection__arrow{height:26px;position:absolute;top:1px;right:1px;width:20px}.select2-container--default .select2-selection--single .select2-selection__arrow b{border-color:#888 transparent transparent transparent;border-style:solid;border-width:5px 4px 0 4px;height:0;left:50%;margin-left:-4px;margin-top:-2px;position:absolute;top:50%;width:0}.select2-container--default[dir=rtl] .select2-selection--single .select2-selection__clear{float:left}.select2-container--default[dir=rtl] .select2-selection--single .select2-selection__arrow{left:1px;right:auto}.select2-container--default.select2-container--disabled .select2-selection--single{background-color:#eee;cursor:default}.select2-container--default.select2-container--disabled .select2-selection--single .select2-selection__clear{display:none}.select2-container--default.select2-container--open .select2-selection--single .select2-selection__arrow b{border-color:transparent transparent #888 transparent;border-width:0 4px 5px 4px}.select2-container--default .select2-selection--multiple{background-color:#fff;border:1px solid #aaa;border-radius:4px;cursor:text}.select2-container--default .select2-selection--multiple .select2-selection__rendered{box-sizing:border-box;list-style:none;margin:0;padding:0 5px;width:100%}.select2-container--default .select2-selection--multiple .select2-selection__rendered li{list-style:none}.select2-container--default .select2-selection--multiple .select2-selection__clear{cursor:pointer;float:right;font-weight:700;margin-top:5px;margin-right:10px;padding:1px}.select2-container--default .select2-selection--multiple .select2-selection__choice{background-color:#e4e4e4;border:1px solid #aaa;border-radius:4px;cursor:default;float:left;margin-right:5px;margin-top:5px;padding:0 5px}.select2-container--default .select2-selection--multiple .select2-selection__choice__remove{color:#999;cursor:pointer;display:inline-block;font-weight:700;margin-right:2px}.select2-container--default .select2-selection--multiple .select2-selection__choice__remove:hover{color:#333}.select2-container--default[dir=rtl] .select2-selection--multiple .select2-search--inline,.select2-container--default[dir=rtl] .select2-selection--multiple .select2-selection__choice{float:right}.select2-container--default[dir=rtl] .select2-selection--multiple .select2-selection__choice{margin-left:5px;margin-right:auto}.select2-container--default[dir=rtl] .select2-selection--multiple .select2-selection__choice__remove{margin-left:2px;margin-right:auto}.select2-container--default.select2-container--focus .select2-selection--multiple{border:solid #000 1px;outline:0}.select2-container--default.select2-container--disabled .select2-selection--multiple{background-color:#eee;cursor:default}.select2-container--default.select2-container--disabled .select2-selection__choice__remove{display:none}.select2-container--default.select2-container--open.select2-container--above .select2-selection--multiple,.select2-container--default.select2-container--open.select2-container--above .select2-selection--single{border-top-left-radius:0;border-top-right-radius:0}.select2-container--default.select2-container--open.select2-container--below .select2-selection--multiple,.select2-container--default.select2-container--open.select2-container--below .select2-selection--single{border-bottom-left-radius:0;border-bottom-right-radius:0}.select2-container--default .select2-search--dropdown .select2-search__field{border:1px solid #aaa}.select2-container--default .select2-search--inline .select2-search__field{background:0 0;border:none;outline:0;box-shadow:none;-webkit-appearance:textfield}.select2-container--default .select2-results>.select2-results__options{max-height:200px;overflow-y:auto}.select2-container--default .select2-results__option[role=group]{padding:0}.select2-container--default .select2-results__option[aria-disabled=true]{color:#999}.select2-container--default .select2-results__option[aria-selected=true]{background-color:#ddd}.select2-container--default .select2-results__option .select2-results__option{padding-left:1em}.select2-container--default .select2-results__option .select2-results__option .select2-results__group{padding-left:0}.select2-container--default .select2-results__option .select2-results__option .select2-results__option{margin-left:-1em;padding-left:2em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-2em;padding-left:3em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-3em;padding-left:4em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-4em;padding-left:5em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-5em;padding-left:6em}.select2-container--default .select2-results__option--highlighted[aria-selected]{background-color:#5897fb;color:#fff}.select2-container--default .select2-results__group{cursor:default;display:block;padding:6px}.select2-container--classic .select2-selection--single{background-color:#f7f7f7;border:1px solid #aaa;border-radius:4px;outline:0;background-image:-webkit-linear-gradient(top,#fff 50%,#eee 100%);background-image:-o-linear-gradient(top,#fff 50%,#eee 100%);background-image:linear-gradient(to bottom,#fff 50%,#eee 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0)}.select2-container--classic .select2-selection--single:focus{border:1px solid #5897fb}.select2-container--classic .select2-selection--single .select2-selection__rendered{color:#444;line-height:28px}.select2-container--classic .select2-selection--single .select2-selection__clear{cursor:pointer;float:right;font-weight:700;margin-right:10px}.select2-container--classic .select2-selection--single .select2-selection__placeholder{color:#999}.select2-container--classic .select2-selection--single .select2-selection__arrow{background-color:#ddd;border:none;border-left:1px solid #aaa;border-top-right-radius:4px;border-bottom-right-radius:4px;height:26px;position:absolute;top:1px;right:1px;width:20px;background-image:-webkit-linear-gradient(top,#eee 50%,#ccc 100%);background-image:-o-linear-gradient(top,#eee 50%,#ccc 100%);background-image:linear-gradient(to bottom,#eee 50%,#ccc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFCCCCCC', GradientType=0)}.select2-container--classic .select2-selection--single .select2-selection__arrow b{border-color:#888 transparent transparent transparent;border-style:solid;border-width:5px 4px 0 4px;height:0;left:50%;margin-left:-4px;margin-top:-2px;position:absolute;top:50%;width:0}.select2-container--classic[dir=rtl] .select2-selection--single .select2-selection__clear{float:left}.select2-container--classic[dir=rtl] .select2-selection--single .select2-selection__arrow{border:none;border-right:1px solid #aaa;border-radius:0;border-top-left-radius:4px;border-bottom-left-radius:4px;left:1px;right:auto}.select2-container--classic.select2-container--open .select2-selection--single{border:1px solid #5897fb}.select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow{background:0 0;border:none}.select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow b{border-color:transparent transparent #888 transparent;border-width:0 4px 5px 4px}.select2-container--classic.select2-container--open.select2-container--above .select2-selection--single{border-top:none;border-top-left-radius:0;border-top-right-radius:0;background-image:-webkit-linear-gradient(top,#fff 0,#eee 50%);background-image:-o-linear-gradient(top,#fff 0,#eee 50%);background-image:linear-gradient(to bottom,#fff 0,#eee 50%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0)}.select2-container--classic.select2-container--open.select2-container--below .select2-selection--single{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0;background-image:-webkit-linear-gradient(top,#eee 50%,#fff 100%);background-image:-o-linear-gradient(top,#eee 50%,#fff 100%);background-image:linear-gradient(to bottom,#eee 50%,#fff 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFFFFFFF', GradientType=0)}.select2-container--classic .select2-selection--multiple{background-color:#fff;border:1px solid #aaa;border-radius:4px;cursor:text;outline:0}.select2-container--classic .select2-selection--multiple:focus{border:1px solid #5897fb}.select2-container--classic .select2-selection--multiple .select2-selection__rendered{list-style:none;margin:0;padding:0 5px}.select2-container--classic .select2-selection--multiple .select2-selection__clear{display:none}.select2-container--classic .select2-selection--multiple .select2-selection__choice{background-color:#e4e4e4;border:1px solid #aaa;border-radius:4px;cursor:default;float:left;margin-right:5px;margin-top:5px;padding:0 5px}.select2-container--classic .select2-selection--multiple .select2-selection__choice__remove{color:#888;cursor:pointer;display:inline-block;font-weight:700;margin-right:2px}.select2-container--classic .select2-selection--multiple .select2-selection__choice__remove:hover{color:#555}.select2-container--classic[dir=rtl] .select2-selection--multiple .select2-selection__choice{float:right;margin-left:5px;margin-right:auto}.select2-container--classic[dir=rtl] .select2-selection--multiple .select2-selection__choice__remove{margin-left:2px;margin-right:auto}.select2-container--classic.select2-container--open .select2-selection--multiple{border:1px solid #5897fb}.select2-container--classic.select2-container--open.select2-container--above .select2-selection--multiple{border-top:none;border-top-left-radius:0;border-top-right-radius:0}.select2-container--classic.select2-container--open.select2-container--below .select2-selection--multiple{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0}.select2-container--classic .select2-search--dropdown .select2-search__field{border:1px solid #aaa;outline:0}.select2-container--classic .select2-search--inline .select2-search__field{outline:0;box-shadow:none}.select2-container--classic .select2-dropdown{background-color:#fff;border:1px solid transparent}.select2-container--classic .select2-dropdown--above{border-bottom:none}.select2-container--classic .select2-dropdown--below{border-top:none}.select2-container--classic .select2-results>.select2-results__options{max-height:200px;overflow-y:auto}.select2-container--classic .select2-results__option[role=group]{padding:0}.select2-container--classic .select2-results__option[aria-disabled=true]{color:grey}.select2-container--classic .select2-results__option--highlighted[aria-selected]{background-color:#3875d7;color:#fff}.select2-container--classic .select2-results__group{cursor:default;display:block;padding:6px}.select2-container--classic.select2-container--open .select2-dropdown{border-color:#5897fb} +.select2-container{display:block}.select2-container :focus{outline:0}.input-group .select2-container--bootstrap4{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1}.input-group-prepend~.select2-container--bootstrap4 .select2-selection{border-top-left-radius:0;border-bottom-left-radius:0}.input-group>.select2-container--bootstrap4:not(:last-child) .select2-selection{border-top-right-radius:0;border-bottom-right-radius:0}.select2-container--bootstrap4 .select2-selection{width:100%;background-color:#fff;border:1px solid #ced4da;border-radius:.25rem;-webkit-transition:border-color .15s ease-in-out,-webkit-box-shadow .15s ease-in-out;transition:border-color .15s ease-in-out,-webkit-box-shadow .15s ease-in-out;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out,-webkit-box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.select2-container--bootstrap4 .select2-selection{-webkit-transition:none;transition:none}}.select2-container--bootstrap4.select2-container--focus .select2-selection{border-color:#80bdff;-webkit-box-shadow:0 0 0 .2rem rgba(0,123,255,.25);box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.select2-container--bootstrap4.select2-container--focus.select2-container--open .select2-selection{border-bottom:none;border-bottom-right-radius:0;border-bottom-left-radius:0}.select2-container--bootstrap4.select2-container--open.select2-container--above .select2-selection{border-top-left-radius:0;border-top-right-radius:0}.select2-container--bootstrap4.select2-container--open.select2-container--below .select2-selection{border-bottom-right-radius:0;border-bottom-left-radius:0}.select2-container--bootstrap4.select2-container--disabled .select2-selection,.select2-container--bootstrap4.select2-container--disabled.select2-container--focus .select2-selection{cursor:not-allowed;background-color:#e9ecef;border-color:#ced4da;-webkit-box-shadow:none;box-shadow:none}.select2-container--bootstrap4.select2-container--disabled .select2-search__field,.select2-container--bootstrap4.select2-container--disabled.select2-container--focus .select2-search__field{background-color:transparent}form.was-validated select:invalid~.select2-container--bootstrap4 .select2-selection,select.is-invalid~.select2-container--bootstrap4 .select2-selection{border-color:#dc3545}form.was-validated select:valid~.select2-container--bootstrap4 .select2-selection,select.is-valid~.select2-container--bootstrap4 .select2-selection{border-color:#28a745}.select2-container--bootstrap4 .select2-search{width:100%}.select2-container--bootstrap4 .select2-dropdown{border-color:#ced4da;border-radius:0}.select2-container--bootstrap4 .select2-dropdown.select2-dropdown--below{border-top:none;border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.select2-container--bootstrap4 .select2-dropdown.select2-dropdown--above{border-top:1px solid #ced4da;border-top-left-radius:.25rem;border-top-right-radius:.25rem}.select2-container--bootstrap4 .select2-dropdown .select2-results__option[aria-selected=true]{color:#212529;background-color:#f2f2f2}.select2-container--bootstrap4 .select2-results__option--highlighted,.select2-container--bootstrap4 .select2-results__option--highlighted.select2-results__option[aria-selected=true]{color:#fff;background-color:#007bff}.select2-container--bootstrap4 .select2-results__option[role=group]{padding:0}.select2-container--bootstrap4 .select2-results__option[role=group] .select2-results__options--nested .select2-results__option{padding-left:1em}.select2-container--bootstrap4 .select2-results__option{padding:.375rem .75rem}.select2-container--bootstrap4 .select2-results>.select2-results__options{max-height:15em;overflow-y:auto}.select2-container--bootstrap4 .select2-results__group{display:list-item;padding:6px;color:#6c757d}.select2-container--bootstrap4 .select2-selection__clear{float:right;width:.9em;height:.9em;padding-left:.15em;margin-top:.7em;margin-right:.3em;line-height:.75em;color:#f8f9fa;background-color:#c8c8c8;border-radius:100%}.select2-container--bootstrap4 .select2-selection__clear:hover{background-color:#afafaf}.select2-container--bootstrap4 .select2-selection--single{height:calc(1.5em + .75rem + 2px)!important}.select2-container--bootstrap4 .select2-selection--single .select2-selection__placeholder{line-height:calc(1.5em + .75rem);color:#6c757d}.select2-container--bootstrap4 .select2-selection--single .select2-selection__arrow{position:absolute;top:50%;right:3px;width:20px}.select2-container--bootstrap4 .select2-selection--single .select2-selection__arrow b{position:absolute;top:60%;left:50%;width:0;height:0;margin-top:-2px;margin-left:-4px;border-color:#343a40 transparent transparent transparent;border-style:solid;border-width:5px 4px 0}.select2-container--bootstrap4 .select2-selection--single .select2-selection__rendered{padding-left:.75rem;line-height:calc(1.5em + .75rem);color:#495057}.select2-search--dropdown .select2-search__field{padding:.375rem .75rem;border:1px solid #ced4da;border-radius:.25rem}.select2-results__message{color:#6c757d}.select2-container--bootstrap4 .select2-selection--multiple{min-height:calc(1.5em + .75rem + 2px)!important}.select2-container--bootstrap4 .select2-selection--multiple .select2-selection__rendered{-webkit-box-sizing:border-box;box-sizing:border-box;width:100%;padding:0 .375rem;margin:0;list-style:none}.select2-container--bootstrap4 .select2-selection--multiple .select2-selection__choice{float:left;padding:0;padding-right:.75rem;margin-top:calc(.375rem - 2px);margin-right:.375rem;color:#495057;cursor:pointer;border:1px solid #bdc6d0;border-radius:.2rem}.select2-container--bootstrap4 .select2-selection--multiple .select2-search__field{color:#495057}.select2-container--bootstrap4 .select2-selection--multiple .select2-selection__choice+.select2-search{width:0}.select2-container--bootstrap4 .select2-selection--multiple .select2-selection__choice__remove{float:left;padding-right:3px;padding-left:3px;margin-right:1px;margin-left:3px;font-weight:700;color:#bdc6d0}.select2-container--bootstrap4 .select2-selection--multiple .select2-selection__choice__remove:hover{color:#343a40}.select2-container--bootstrap4 .select2-selection--multiple .select2-selection__clear{position:absolute!important;top:0;right:.7em;float:none;margin-right:0}.select2-container--bootstrap4.select2-container--disabled .select2-selection--multiple .select2-selection__choice{padding:0 5px;cursor:not-allowed}.select2-container--bootstrap4.select2-container--disabled .select2-selection--multiple .select2-selection__choice .select2-selection__choice__remove{display:none} \ No newline at end of file