feat:
This commit is contained in:
parent
4cdd751b16
commit
be25404852
51 changed files with 606 additions and 1194 deletions
7
go.mod
7
go.mod
|
@ -5,20 +5,23 @@ go 1.21.3
|
||||||
toolchain go1.22.0
|
toolchain go1.22.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/DATA-DOG/go-sqlmock v1.5.2
|
||||||
github.com/ilyakaznacheev/cleanenv v1.5.0
|
github.com/ilyakaznacheev/cleanenv v1.5.0
|
||||||
github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438
|
github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438
|
||||||
github.com/open-policy-agent/opa v0.67.1
|
github.com/open-policy-agent/opa v0.67.1
|
||||||
|
github.com/stretchr/testify v1.9.0
|
||||||
|
github.com/valkey-io/valkey-go v1.0.47
|
||||||
go.uber.org/zap v1.27.0
|
go.uber.org/zap v1.27.0
|
||||||
google.golang.org/grpc v1.65.0
|
google.golang.org/grpc v1.65.0
|
||||||
google.golang.org/protobuf v1.34.2
|
google.golang.org/protobuf v1.34.2
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.sch9.ru/new_gate/models v0.0.1 // indirect
|
|
||||||
github.com/OneOfOne/xxhash v1.2.8 // indirect
|
github.com/OneOfOne/xxhash v1.2.8 // indirect
|
||||||
github.com/agnivade/levenshtein v1.1.1 // indirect
|
github.com/agnivade/levenshtein v1.1.1 // indirect
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||||
github.com/go-ini/ini v1.67.0 // indirect
|
github.com/go-ini/ini v1.67.0 // indirect
|
||||||
github.com/go-logr/logr v1.4.2 // indirect
|
github.com/go-logr/logr v1.4.2 // indirect
|
||||||
github.com/go-logr/stdr v1.2.2 // indirect
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
|
@ -28,6 +31,7 @@ require (
|
||||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
||||||
github.com/jackc/puddle/v2 v2.2.1 // indirect
|
github.com/jackc/puddle/v2 v2.2.1 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/prometheus/client_golang v1.19.1 // indirect
|
github.com/prometheus/client_golang v1.19.1 // indirect
|
||||||
github.com/prometheus/client_model v0.6.1 // indirect
|
github.com/prometheus/client_model v0.6.1 // indirect
|
||||||
github.com/prometheus/common v0.48.0 // indirect
|
github.com/prometheus/common v0.48.0 // indirect
|
||||||
|
@ -35,7 +39,6 @@ require (
|
||||||
github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 // indirect
|
github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 // indirect
|
||||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||||
github.com/tchap/go-patricia/v2 v2.3.1 // indirect
|
github.com/tchap/go-patricia/v2 v2.3.1 // indirect
|
||||||
github.com/valkey-io/valkey-go v1.0.47 // indirect
|
|
||||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||||
github.com/yashtewari/glob-intersection v0.2.0 // indirect
|
github.com/yashtewari/glob-intersection v0.2.0 // indirect
|
||||||
|
|
11
go.sum
11
go.sum
|
@ -1,11 +1,11 @@
|
||||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||||
git.sch9.ru/new_gate/models v0.0.0-20240923183834-466a81903af8 h1:PwRvUZ1iSZO1Na+0HXpjYiCgt5gxqh3aKbWtV64FDXk=
|
|
||||||
git.sch9.ru/new_gate/models v0.0.0-20240923183834-466a81903af8/go.mod h1:oLUfkN1hwclYTj8Y+br5ShZHLB4y2StRwf+mhc33NNc=
|
|
||||||
git.sch9.ru/new_gate/models v0.0.1 h1:VmRUUNNFrX1ZN26wuyfa5Z8il7VuIN49XjXsCtDQhLs=
|
git.sch9.ru/new_gate/models v0.0.1 h1:VmRUUNNFrX1ZN26wuyfa5Z8il7VuIN49XjXsCtDQhLs=
|
||||||
git.sch9.ru/new_gate/models v0.0.1/go.mod h1:oLUfkN1hwclYTj8Y+br5ShZHLB4y2StRwf+mhc33NNc=
|
git.sch9.ru/new_gate/models v0.0.1/go.mod h1:oLUfkN1hwclYTj8Y+br5ShZHLB4y2StRwf+mhc33NNc=
|
||||||
github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
|
github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
|
||||||
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||||
|
github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=
|
||||||
|
github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=
|
||||||
github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8=
|
github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8=
|
||||||
github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q=
|
github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q=
|
||||||
github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8=
|
github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8=
|
||||||
|
@ -88,6 +88,7 @@ github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
|
||||||
github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
|
github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
|
||||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||||
|
github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE=
|
||||||
github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM=
|
github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM=
|
||||||
github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
|
@ -100,6 +101,8 @@ github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o
|
||||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||||
github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM=
|
github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM=
|
||||||
github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk=
|
github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk=
|
||||||
|
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
|
||||||
|
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
|
||||||
github.com/open-policy-agent/opa v0.67.1 h1:rzy26J6g1X+CKknAcx0Vfbt41KqjuSzx4E0A8DAZf3E=
|
github.com/open-policy-agent/opa v0.67.1 h1:rzy26J6g1X+CKknAcx0Vfbt41KqjuSzx4E0A8DAZf3E=
|
||||||
github.com/open-policy-agent/opa v0.67.1/go.mod h1:aqKlHc8E2VAAylYE9x09zJYr/fYzGX+JKne89UGqFzk=
|
github.com/open-policy-agent/opa v0.67.1/go.mod h1:aqKlHc8E2VAAylYE9x09zJYr/fYzGX+JKne89UGqFzk=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
|
@ -161,6 +164,8 @@ go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||||
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
|
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
|
||||||
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
|
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
|
||||||
|
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
|
||||||
|
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
||||||
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
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/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
|
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
|
||||||
|
@ -168,8 +173,6 @@ golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
|
||||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
|
|
||||||
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
|
||||||
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
|
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
|
||||||
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||||
|
|
|
@ -1,4 +1,13 @@
|
||||||
package contests
|
package contests
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
contestv1 "git.sch9.ru/new_gate/ms-tester/pkg/go/gen/proto/contest/v1"
|
||||||
|
"google.golang.org/protobuf/types/known/emptypb"
|
||||||
|
)
|
||||||
|
|
||||||
type ContestHandlers interface {
|
type ContestHandlers interface {
|
||||||
|
CreateContest(ctx context.Context, req *contestv1.CreateContestRequest) (*contestv1.CreateContestResponse, error)
|
||||||
|
ReadContest(ctx context.Context, req *contestv1.ReadContestRequest) (*contestv1.ReadContestResponse, error)
|
||||||
|
DeleteContest(ctx context.Context, req *contestv1.DeleteContestRequest) (*emptypb.Empty, error)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +1,51 @@
|
||||||
package grpc
|
package grpc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"git.sch9.ru/new_gate/ms-tester/internal/contests"
|
||||||
|
contestv1 "git.sch9.ru/new_gate/ms-tester/pkg/go/gen/proto/contest/v1"
|
||||||
|
"git.sch9.ru/new_gate/ms-tester/pkg/utils"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/protobuf/types/known/emptypb"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ContestHandlers struct {
|
||||||
|
contestv1.UnimplementedContestServiceServer
|
||||||
|
|
||||||
|
contestUC contests.ContestUseCase
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewContestHandlers(gserver *grpc.Server, contestUC contests.ContestUseCase) {
|
||||||
|
handlers := &ContestHandlers{contestUC: contestUC}
|
||||||
|
|
||||||
|
contestv1.RegisterContestServiceServer(gserver, handlers)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ContestHandlers) CreateContest(ctx context.Context, req *contestv1.CreateContestRequest) (*contestv1.CreateContestResponse, error) {
|
||||||
|
id, err := h.contestUC.CreateContest(ctx, req.GetTitle())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &contestv1.CreateContestResponse{Id: id}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ContestHandlers) ReadContest(ctx context.Context, req *contestv1.ReadContestRequest) (*contestv1.ReadContestResponse, error) {
|
||||||
|
contest, err := h.contestUC.ReadContestById(ctx, req.GetId())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &contestv1.ReadContestResponse{Contest: &contestv1.ReadContestResponse_Contest{
|
||||||
|
Id: *contest.Id,
|
||||||
|
Title: *contest.Title,
|
||||||
|
CreatedAt: utils.TimestampP(contest.CreatedAt),
|
||||||
|
UpdatedAt: utils.TimestampP(contest.UpdatedAt),
|
||||||
|
}}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ContestHandlers) DeleteContest(ctx context.Context, req *contestv1.DeleteContestRequest) (*emptypb.Empty, error) {
|
||||||
|
err := h.contestUC.DeleteContest(ctx, req.GetId())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &emptypb.Empty{}, nil
|
||||||
|
}
|
||||||
|
|
|
@ -2,12 +2,11 @@ package contests
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"git.sch9.ru/new_gate/models"
|
"git.sch9.ru/new_gate/ms-tester/internal/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ContestRepository interface {
|
type ContestRepository interface {
|
||||||
CreateContest(ctx context.Context, contest *models.Contest) (int32, error)
|
CreateContest(ctx context.Context, title string) (int32, error)
|
||||||
ReadContestById(ctx context.Context, id int32) (*models.Contest, error)
|
ReadContestById(ctx context.Context, id int32) (*models.Contest, error)
|
||||||
UpdateContest(ctx context.Context, contest *models.Contest) error
|
|
||||||
DeleteContest(ctx context.Context, id int32) error
|
DeleteContest(ctx context.Context, id int32) error
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,79 +0,0 @@
|
||||||
package repository
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"git.sch9.ru/new_gate/models"
|
|
||||||
"github.com/jmoiron/sqlx"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ParticipantStorage struct {
|
|
||||||
db *sqlx.DB
|
|
||||||
logger *zap.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewParticipantStorage(db *sqlx.DB, logger *zap.Logger) *ParticipantStorage {
|
|
||||||
return &ParticipantStorage{
|
|
||||||
db: db,
|
|
||||||
logger: logger,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (storage *ParticipantStorage) CreateParticipant(ctx context.Context, participant *models.Participant) (int32, error) {
|
|
||||||
query := storage.db.Rebind(`
|
|
||||||
INSERT INTO participants
|
|
||||||
(user_id,contest_id,name)
|
|
||||||
VALUES (?,?,?)
|
|
||||||
RETURNING id
|
|
||||||
`)
|
|
||||||
|
|
||||||
rows, err := storage.db.QueryxContext(
|
|
||||||
ctx,
|
|
||||||
query,
|
|
||||||
participant.UserId,
|
|
||||||
participant.ContestId,
|
|
||||||
participant.Name,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return 0, handlePgErr(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer rows.Close()
|
|
||||||
var id int32
|
|
||||||
err = rows.StructScan(&id)
|
|
||||||
if err != nil {
|
|
||||||
return 0, handlePgErr(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return id, nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (storage *ParticipantStorage) ReadParticipantById(ctx context.Context, id int32) (*models.Participant, error) {
|
|
||||||
var participant models.Participant
|
|
||||||
query := storage.db.Rebind("SELECT * from participants WHERE id=? LIMIT 1")
|
|
||||||
err := storage.db.GetContext(ctx, &participant, query, id)
|
|
||||||
if err != nil {
|
|
||||||
return nil, handlePgErr(err)
|
|
||||||
}
|
|
||||||
return &participant, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (storage *ParticipantStorage) UpdateParticipant(ctx context.Context, id int32, participant models.Participant) error {
|
|
||||||
query := storage.db.Rebind("UPDATE participants SET name=?")
|
|
||||||
_, err := storage.db.ExecContext(ctx, query, participant.Name)
|
|
||||||
if err != nil {
|
|
||||||
return handlePgErr(err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (storage *ParticipantStorage) DeleteParticipant(ctx context.Context, id int32) error {
|
|
||||||
query := storage.db.Rebind("DELETE FROM participants WHERE id=?")
|
|
||||||
_, err := storage.db.ExecContext(ctx, query, id)
|
|
||||||
if err != nil {
|
|
||||||
return handlePgErr(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -3,7 +3,7 @@ package repository
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"git.sch9.ru/new_gate/models"
|
"git.sch9.ru/new_gate/ms-tester/internal/models"
|
||||||
"git.sch9.ru/new_gate/ms-tester/pkg/utils"
|
"git.sch9.ru/new_gate/ms-tester/pkg/utils"
|
||||||
"github.com/jackc/pgerrcode"
|
"github.com/jackc/pgerrcode"
|
||||||
"github.com/jackc/pgx/v5/pgconn"
|
"github.com/jackc/pgx/v5/pgconn"
|
||||||
|
@ -23,37 +23,32 @@ func NewContestRepository(db *sqlx.DB, logger *zap.Logger) *ContestRepository {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ContestRepository) CreateContest(ctx context.Context, contest *models.Contest) (int32, error) {
|
const createContestQuery = "INSERT INTO contest (title) VALUES (?) RETURNING id"
|
||||||
query := r.db.Rebind(`
|
|
||||||
INSERT INTO contests
|
|
||||||
(name)
|
|
||||||
VALUES (?)
|
|
||||||
RETURNING id
|
|
||||||
`)
|
|
||||||
|
|
||||||
rows, err := r.db.QueryxContext(
|
func (r *ContestRepository) CreateContest(ctx context.Context, title string) (int32, error) {
|
||||||
ctx,
|
query := r.db.Rebind(createContestQuery)
|
||||||
query,
|
|
||||||
contest.Name,
|
rows, err := r.db.QueryxContext(ctx, query, title)
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, handlePgErr(err)
|
return 0, handlePgErr(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
var id int32
|
var id int32
|
||||||
err = rows.StructScan(&id)
|
rows.Next()
|
||||||
|
err = rows.Scan(&id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, handlePgErr(err)
|
return 0, handlePgErr(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return id, nil
|
return id, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const readContestByIdQuery = "SELECT * from contest WHERE id=? LIMIT 1"
|
||||||
|
|
||||||
func (r *ContestRepository) ReadContestById(ctx context.Context, id int32) (*models.Contest, error) {
|
func (r *ContestRepository) ReadContestById(ctx context.Context, id int32) (*models.Contest, error) {
|
||||||
var contest models.Contest
|
var contest models.Contest
|
||||||
query := r.db.Rebind("SELECT * from contests WHERE id=? LIMIT 1")
|
query := r.db.Rebind(readContestByIdQuery)
|
||||||
err := r.db.GetContext(ctx, &contest, query, id)
|
err := r.db.GetContext(ctx, &contest, query, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, handlePgErr(err)
|
return nil, handlePgErr(err)
|
||||||
|
@ -61,18 +56,10 @@ func (r *ContestRepository) ReadContestById(ctx context.Context, id int32) (*mod
|
||||||
return &contest, nil
|
return &contest, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ContestRepository) UpdateContest(ctx context.Context, contest *models.Contest) error {
|
const deleteContestQuery = "DELETE FROM contest WHERE id=?"
|
||||||
query := r.db.Rebind("UPDATE contests SET name=? WHERE id=?")
|
|
||||||
_, err := r.db.ExecContext(ctx, query, contest.Name, contest.Id)
|
|
||||||
if err != nil {
|
|
||||||
return handlePgErr(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ContestRepository) DeleteContest(ctx context.Context, id int32) error {
|
func (r *ContestRepository) DeleteContest(ctx context.Context, id int32) error {
|
||||||
query := r.db.Rebind("DELETE FROM contests WHERE id=?")
|
query := r.db.Rebind(deleteContestQuery)
|
||||||
_, err := r.db.ExecContext(ctx, query, id)
|
_, err := r.db.ExecContext(ctx, query, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return handlePgErr(err)
|
return handlePgErr(err)
|
||||||
|
|
58
internal/contests/repository/pg_repository_test.go
Normal file
58
internal/contests/repository/pg_repository_test.go
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/DATA-DOG/go-sqlmock"
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestContestRepository_CreateContest(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
db, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
sqlxDB := sqlx.NewDb(db, "sqlmock")
|
||||||
|
defer sqlxDB.Close()
|
||||||
|
|
||||||
|
contestRepo := NewContestRepository(sqlxDB, zap.NewNop())
|
||||||
|
|
||||||
|
t.Run("valid contest creation", func(t *testing.T) {
|
||||||
|
title := "Contest title"
|
||||||
|
|
||||||
|
rows := sqlmock.NewRows([]string{"id"}).AddRow(1)
|
||||||
|
|
||||||
|
mock.ExpectQuery(sqlxDB.Rebind(createContestQuery)).WithArgs(title).WillReturnRows(rows)
|
||||||
|
|
||||||
|
id, err := contestRepo.CreateContest(context.Background(), title)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, int32(1), id)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContestRepository_DeleteContest(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
db, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
sqlxDB := sqlx.NewDb(db, "sqlmock")
|
||||||
|
defer sqlxDB.Close()
|
||||||
|
|
||||||
|
contestRepo := NewContestRepository(sqlxDB, zap.NewNop())
|
||||||
|
|
||||||
|
t.Run("valid contest deletion", func(t *testing.T) {
|
||||||
|
id := int32(1)
|
||||||
|
rows := sqlmock.NewResult(1, 1)
|
||||||
|
|
||||||
|
mock.ExpectExec(sqlxDB.Rebind(deleteContestQuery)).WithArgs(id).WillReturnResult(rows)
|
||||||
|
|
||||||
|
err = contestRepo.DeleteContest(context.Background(), id)
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
}
|
|
@ -1,114 +0,0 @@
|
||||||
package repository
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"git.sch9.ru/new_gate/models"
|
|
||||||
"github.com/jmoiron/sqlx"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
)
|
|
||||||
|
|
||||||
type SolutionStorage struct {
|
|
||||||
db *sqlx.DB
|
|
||||||
logger *zap.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSolutionStorage(db *sqlx.DB, logger *zap.Logger) *SolutionStorage {
|
|
||||||
return &SolutionStorage{
|
|
||||||
db: db,
|
|
||||||
logger: logger,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: testing graph
|
|
||||||
|
|
||||||
func (storage *SolutionStorage) updateResult(ctx context.Context, participantId int32, taskId int32) error {
|
|
||||||
tx, err := storage.db.Beginx()
|
|
||||||
if err != nil {
|
|
||||||
return handlePgErr(err)
|
|
||||||
}
|
|
||||||
query := tx.Rebind("UPDATE participant_subtask AS ps SET best_score = (SELECT COALESCE(max(score),0) FROM subtaskruns WHERE subtask_id = ps.subtask_id AND solution_id IN (SELECT id FROM solutions WHERE participant_id=ps.participant_id)) WHERE participant_id = 2 AND subtask_id IN (SELECT id FROM subtasks WHERE task_id = 2)")
|
|
||||||
tx.QueryxContext(ctx, query, participantId, taskId)
|
|
||||||
query = tx.Rebind("UPDATE participant_task SET best_score = (select max(best_score) from participant_subtask WHERE participant_id = ? AND subtask_id IN (SELECT id FROM subtask WHERE task_id = ?)) WHERE participant_id = ? AND task_id = ?")
|
|
||||||
tx.QueryxContext(ctx, query, participantId, taskId, participantId, taskId)
|
|
||||||
err = tx.Commit()
|
|
||||||
if err != nil {
|
|
||||||
return handlePgErr(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (storage *SolutionStorage) CreateSolution(ctx context.Context, solution *models.Solution) (int32, error) {
|
|
||||||
query := storage.db.Rebind(`
|
|
||||||
INSERT INTO solutions
|
|
||||||
(participant_id,task_id,language_id,solution_hash,result)
|
|
||||||
VALUES (?, ?, ?, ?, ?)
|
|
||||||
RETURNING id
|
|
||||||
`)
|
|
||||||
|
|
||||||
rows, err := storage.db.QueryxContext(
|
|
||||||
ctx,
|
|
||||||
query,
|
|
||||||
solution.ParticipantId,
|
|
||||||
solution.TaskId,
|
|
||||||
solution.LanguageId,
|
|
||||||
"", //FIXME
|
|
||||||
models.NotTested,
|
|
||||||
)
|
|
||||||
//TODO: add testing tree
|
|
||||||
if err != nil {
|
|
||||||
return 0, handlePgErr(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer rows.Close()
|
|
||||||
var id int32
|
|
||||||
err = rows.StructScan(&id)
|
|
||||||
if err != nil {
|
|
||||||
return 0, handlePgErr(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return id, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (storage *SolutionStorage) ReadSolutionById(ctx context.Context, id int32) (*models.Solution, error) {
|
|
||||||
var solution models.Solution
|
|
||||||
query := storage.db.Rebind("SELECT * from solutions WHERE id=? LIMIT 1")
|
|
||||||
err := storage.db.GetContext(ctx, &solution, query, id)
|
|
||||||
if err != nil {
|
|
||||||
return nil, handlePgErr(err)
|
|
||||||
}
|
|
||||||
return &solution, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (storage *SolutionStorage) RejudgeSolution(ctx context.Context, id int32) error {
|
|
||||||
tx, err := storage.db.Beginx()
|
|
||||||
if err != nil {
|
|
||||||
return handlePgErr(err)
|
|
||||||
}
|
|
||||||
query := tx.Rebind("UPDATE solutions SET result = ? WHERE id = ?")
|
|
||||||
tx.QueryxContext(ctx, query, models.NotTested, id) // FIXME
|
|
||||||
query = tx.Rebind("UPDATE subtaskruns SET result = ?,score = 0 WHERE solution_id = ?")
|
|
||||||
tx.QueryxContext(ctx, query, models.NotTested, id) // FIXME
|
|
||||||
query = tx.Rebind("UPDATE testruns SET result = ?, score = 0 WHERE testgrouprun_id IN (SELECT id FROM tesgrouprun WHERE solution_id = ?)")
|
|
||||||
tx.QueryxContext(ctx, query, models.NotTested, id) // FIXME
|
|
||||||
err = tx.Commit()
|
|
||||||
var solution models.Solution
|
|
||||||
query = storage.db.Rebind("SELECT * from solutions WHERE id=? LIMIT 1")
|
|
||||||
err = storage.db.GetContext(ctx, &solution, query, id)
|
|
||||||
if err != nil {
|
|
||||||
return handlePgErr(err)
|
|
||||||
}
|
|
||||||
storage.updateResult(ctx, *solution.ParticipantId, *solution.TaskId) // FIXME
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (storage *SolutionStorage) DeleteSolution(ctx context.Context, id int32) error {
|
|
||||||
query := storage.db.Rebind("DELETE FROM solutions WHERE id=?")
|
|
||||||
_, err := storage.db.ExecContext(ctx, query, id)
|
|
||||||
if err != nil {
|
|
||||||
return handlePgErr(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,80 +0,0 @@
|
||||||
package repository
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"git.sch9.ru/new_gate/models"
|
|
||||||
"github.com/jmoiron/sqlx"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
)
|
|
||||||
|
|
||||||
type TaskStorage struct {
|
|
||||||
db *sqlx.DB
|
|
||||||
logger *zap.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewTaskStorage(db *sqlx.DB, logger *zap.Logger) *TaskStorage {
|
|
||||||
return &TaskStorage{
|
|
||||||
db: db,
|
|
||||||
logger: logger,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (storage *TaskStorage) CreateTask(ctx context.Context, task *models.Task) (int32, error) {
|
|
||||||
query := storage.db.Rebind(`
|
|
||||||
INSERT INTO tasks
|
|
||||||
(contest_id,problem_id,position,position_name)
|
|
||||||
VALUES (?, ?, ?, ?)
|
|
||||||
RETURNING id
|
|
||||||
`)
|
|
||||||
|
|
||||||
rows, err := storage.db.QueryxContext(
|
|
||||||
ctx,
|
|
||||||
query,
|
|
||||||
task.ContestId,
|
|
||||||
task.ProblemId,
|
|
||||||
task.Position,
|
|
||||||
task.PositionName,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return 0, handlePgErr(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer rows.Close()
|
|
||||||
var id int32
|
|
||||||
err = rows.StructScan(&id)
|
|
||||||
if err != nil {
|
|
||||||
return 0, handlePgErr(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return id, nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (storage *TaskStorage) ReadTaskById(ctx context.Context, id int32) (*models.Task, error) {
|
|
||||||
var task models.Task
|
|
||||||
query := storage.db.Rebind("SELECT * from tasks WHERE id=? LIMIT 1")
|
|
||||||
err := storage.db.GetContext(ctx, &task, query, id)
|
|
||||||
if err != nil {
|
|
||||||
return nil, handlePgErr(err)
|
|
||||||
}
|
|
||||||
return &task, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (storage *TaskStorage) UpdateTask(ctx context.Context, id int32, task models.Task) error {
|
|
||||||
query := storage.db.Rebind("UPDATE tasks SET position=?,position_name=?")
|
|
||||||
_, err := storage.db.ExecContext(ctx, query, task.Position, task.PositionName)
|
|
||||||
if err != nil {
|
|
||||||
return handlePgErr(err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (storage *TaskStorage) DeleteTask(ctx context.Context, id int32) error {
|
|
||||||
query := storage.db.Rebind("DELETE FROM tasks WHERE id=?")
|
|
||||||
_, err := storage.db.ExecContext(ctx, query, id)
|
|
||||||
if err != nil {
|
|
||||||
return handlePgErr(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -2,12 +2,11 @@ package contests
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"git.sch9.ru/new_gate/models"
|
"git.sch9.ru/new_gate/ms-tester/internal/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ContestUseCase interface {
|
type ContestUseCase interface {
|
||||||
CreateContest(ctx context.Context, contest *models.Contest) (int32, error)
|
CreateContest(ctx context.Context, title string) (int32, error)
|
||||||
ReadContestById(ctx context.Context, id int32) (*models.Contest, error)
|
ReadContestById(ctx context.Context, id int32) (*models.Contest, error)
|
||||||
UpdateContest(ctx context.Context, contest *models.Contest) error
|
|
||||||
DeleteContest(ctx context.Context, id int32) error
|
DeleteContest(ctx context.Context, id int32) error
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,57 +0,0 @@
|
||||||
package usecase
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"git.sch9.ru/new_gate/models"
|
|
||||||
"git.sch9.ru/new_gate/ms-tester/pkg/utils"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ParticipantStorage interface {
|
|
||||||
CreateParticipant(ctx context.Context, participant *models.Participant) (int32, error)
|
|
||||||
ReadParticipantById(ctx context.Context, id int32) (*models.Participant, error)
|
|
||||||
UpdateParticipant(ctx context.Context, participant *models.Participant) error
|
|
||||||
DeleteParticipant(ctx context.Context, id int32) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type ParticipantService struct {
|
|
||||||
participantStorage ParticipantStorage
|
|
||||||
permissionService IPermissionService
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewParticipantService(
|
|
||||||
participantStorage ParticipantStorage,
|
|
||||||
permissionService IPermissionService,
|
|
||||||
) *ParticipantService {
|
|
||||||
return &ParticipantService{
|
|
||||||
participantStorage: participantStorage,
|
|
||||||
permissionService: permissionService,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (service *ParticipantService) CreateParticipant(ctx context.Context, participant *models.Participant) (int32, error) {
|
|
||||||
if !service.permissionService.Allowed(ctx, extractUser(ctx), "create") {
|
|
||||||
return 0, utils.ServiceError(nil, utils.ErrNoPermission, "permission denied")
|
|
||||||
}
|
|
||||||
return service.participantStorage.CreateParticipant(ctx, participant)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (service *ParticipantService) ReadParticipantById(ctx context.Context, id int32) (*models.Participant, error) {
|
|
||||||
if !service.permissionService.Allowed(ctx, extractUser(ctx), "read") {
|
|
||||||
return nil, utils.ServiceError(nil, utils.ErrNoPermission, "permission denied")
|
|
||||||
}
|
|
||||||
return service.participantStorage.ReadParticipantById(ctx, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (service *ParticipantService) UpdateParticipant(ctx context.Context, participant *models.Participant) error {
|
|
||||||
if !service.permissionService.Allowed(ctx, extractUser(ctx), "update") {
|
|
||||||
return utils.ServiceError(nil, utils.ErrNoPermission, "permission denied")
|
|
||||||
}
|
|
||||||
return service.participantStorage.UpdateParticipant(ctx, participant)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (service *ParticipantService) DeleteParticipant(ctx context.Context, id int32) error {
|
|
||||||
if !service.permissionService.Allowed(ctx, extractUser(ctx), "delete") {
|
|
||||||
return utils.ServiceError(nil, utils.ErrNoPermission, "permission denied")
|
|
||||||
}
|
|
||||||
return service.participantStorage.DeleteParticipant(ctx, id)
|
|
||||||
}
|
|
|
@ -1,39 +0,0 @@
|
||||||
package usecase
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"git.sch9.ru/new_gate/models"
|
|
||||||
"github.com/open-policy-agent/opa/rego"
|
|
||||||
)
|
|
||||||
|
|
||||||
type PermissionService struct {
|
|
||||||
query *rego.PreparedEvalQuery
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewPermissionService() *PermissionService {
|
|
||||||
query, err := rego.New(
|
|
||||||
rego.Query("allow = data.problem.rbac.allow"),
|
|
||||||
rego.Load([]string{"./opa/all.rego"}, nil),
|
|
||||||
).PrepareForEval(context.TODO())
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &PermissionService{
|
|
||||||
query: &query,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *PermissionService) Allowed(ctx context.Context, user *models.User, action string) bool {
|
|
||||||
input := map[string]interface{}{
|
|
||||||
"user": user,
|
|
||||||
"action": action,
|
|
||||||
}
|
|
||||||
|
|
||||||
result, err := s.query.Eval(ctx, rego.EvalInput(input))
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return result[0].Bindings["allow"].(bool)
|
|
||||||
}
|
|
|
@ -1,57 +0,0 @@
|
||||||
package usecase
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"git.sch9.ru/new_gate/models"
|
|
||||||
"git.sch9.ru/new_gate/ms-tester/pkg/utils"
|
|
||||||
)
|
|
||||||
|
|
||||||
type SolutionStorage interface {
|
|
||||||
CreateSolution(ctx context.Context, solution models.Solution) (int32, error)
|
|
||||||
ReadSolutionById(ctx context.Context, id int32) (models.Solution, error)
|
|
||||||
RejudgeSolution(ctx context.Context, id int32) error
|
|
||||||
DeleteSolution(ctx context.Context, id int32) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type SolutionService struct {
|
|
||||||
solutionStorage SolutionStorage
|
|
||||||
permissionService IPermissionService
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSolutionService(
|
|
||||||
solutionStorage SolutionStorage,
|
|
||||||
permissionService IPermissionService,
|
|
||||||
) *SolutionService {
|
|
||||||
return &SolutionService{
|
|
||||||
solutionStorage: solutionStorage,
|
|
||||||
permissionService: permissionService,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (service *SolutionService) CreateSolution(ctx context.Context, solution models.Solution) (int32, error) {
|
|
||||||
if !service.permissionService.Allowed(ctx, extractUser(ctx), "create") {
|
|
||||||
return 0, utils.ServiceError(nil, utils.ErrNoPermission, "permission denied")
|
|
||||||
}
|
|
||||||
return service.solutionStorage.CreateSolution(ctx, solution)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (service *SolutionService) ReadSolutionById(ctx context.Context, id int32) (models.Solution, error) {
|
|
||||||
if !service.permissionService.Allowed(ctx, extractUser(ctx), "read") {
|
|
||||||
return models.Solution{}, utils.ServiceError(nil, utils.ErrNoPermission, "permission denied")
|
|
||||||
}
|
|
||||||
return service.solutionStorage.ReadSolutionById(ctx, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (service *SolutionService) RejudgeSolution(ctx context.Context, id int32) error {
|
|
||||||
if !service.permissionService.Allowed(ctx, extractUser(ctx), "update") {
|
|
||||||
return utils.ServiceError(nil, utils.ErrNoPermission, "permission denied")
|
|
||||||
}
|
|
||||||
return service.solutionStorage.RejudgeSolution(ctx, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (service *SolutionService) DeleteSolution(ctx context.Context, id int32) error {
|
|
||||||
if !service.permissionService.Allowed(ctx, extractUser(ctx), "delete") {
|
|
||||||
return utils.ServiceError(nil, utils.ErrNoPermission, "permission denied")
|
|
||||||
}
|
|
||||||
return service.solutionStorage.DeleteSolution(ctx, id)
|
|
||||||
}
|
|
|
@ -1,41 +0,0 @@
|
||||||
package usecase
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"git.sch9.ru/new_gate/models"
|
|
||||||
"git.sch9.ru/new_gate/ms-tester/pkg/utils"
|
|
||||||
)
|
|
||||||
|
|
||||||
type TaskStorage interface {
|
|
||||||
CreateTask(ctx context.Context, task models.Task) (int32, error)
|
|
||||||
DeleteTask(ctx context.Context, id int32) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type TaskService struct {
|
|
||||||
taskStorage TaskStorage
|
|
||||||
permissionService IPermissionService
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewTaskService(
|
|
||||||
taskStorage TaskStorage,
|
|
||||||
permissionService IPermissionService,
|
|
||||||
) *TaskService {
|
|
||||||
return &TaskService{
|
|
||||||
taskStorage: taskStorage,
|
|
||||||
permissionService: permissionService,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (service *TaskService) CreateTask(ctx context.Context, task models.Task) (int32, error) {
|
|
||||||
if !service.permissionService.Allowed(ctx, extractUser(ctx), "create") {
|
|
||||||
return 0, utils.ServiceError(nil, utils.ErrNoPermission, "permission denied")
|
|
||||||
}
|
|
||||||
return service.taskStorage.CreateTask(ctx, task)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (service *TaskService) DeleteTask(ctx context.Context, id int32) error {
|
|
||||||
if !service.permissionService.Allowed(ctx, extractUser(ctx), "delete") {
|
|
||||||
return utils.ServiceError(nil, utils.ErrNoPermission, "permission denied")
|
|
||||||
}
|
|
||||||
return service.taskStorage.DeleteTask(ctx, id)
|
|
||||||
}
|
|
|
@ -4,48 +4,28 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"git.sch9.ru/new_gate/models"
|
"git.sch9.ru/new_gate/models"
|
||||||
"git.sch9.ru/new_gate/ms-tester/internal/contests"
|
"git.sch9.ru/new_gate/ms-tester/internal/contests"
|
||||||
"git.sch9.ru/new_gate/ms-tester/pkg/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type ContestService struct {
|
type ContestUseCase struct {
|
||||||
contestStorage contests.ContestRepository
|
contestRepo contests.ContestRepository
|
||||||
permissionService IPermissionService
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewContestService(
|
func NewContestUseCase(
|
||||||
contestStorage ContestStorage,
|
contestRepo contests.ContestRepository,
|
||||||
permissionService IPermissionService,
|
) *ContestUseCase {
|
||||||
) *ContestService {
|
return &ContestUseCase{
|
||||||
return &ContestService{
|
contestRepo: contestRepo,
|
||||||
contestStorage: contestStorage,
|
|
||||||
permissionService: permissionService,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (service *ContestService) CreateContest(ctx context.Context, contest *models.Contest) (int32, error) {
|
func (uc *ContestUseCase) CreateContest(ctx context.Context, title string) (int32, error) {
|
||||||
if !service.permissionService.Allowed(ctx, extractUser(ctx), "create") {
|
return uc.contestRepo.CreateContest(ctx, title)
|
||||||
return 0, utils.ServiceError(nil, utils.ErrNoPermission, "permission denied")
|
|
||||||
}
|
|
||||||
return service.contestStorage.CreateContest(ctx, contest)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (service *ContestService) ReadContestById(ctx context.Context, id int32) (*models.Contest, error) {
|
func (uc *ContestUseCase) ReadContestById(ctx context.Context, id int32) (*models.Contest, error) {
|
||||||
if !service.permissionService.Allowed(ctx, extractUser(ctx), "read") {
|
return uc.contestRepo.ReadContestById(ctx, id)
|
||||||
return nil, utils.ServiceError(nil, utils.ErrNoPermission, "permission denied")
|
|
||||||
}
|
|
||||||
return service.contestStorage.ReadContestById(ctx, id)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (service *ContestService) UpdateContest(ctx context.Context, contest *models.Contest) error {
|
func (uc *ContestUseCase) DeleteContest(ctx context.Context, id int32) error {
|
||||||
if !service.permissionService.Allowed(ctx, extractUser(ctx), "update") {
|
return uc.contestRepo.DeleteContest(ctx, id)
|
||||||
return utils.ServiceError(nil, utils.ErrNoPermission, "permission denied")
|
|
||||||
}
|
|
||||||
return service.contestStorage.UpdateContest(ctx, contest)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (service *ContestService) DeleteContest(ctx context.Context, id int32) error {
|
|
||||||
if !service.permissionService.Allowed(ctx, extractUser(ctx), "delete") {
|
|
||||||
return utils.ServiceError(nil, utils.ErrNoPermission, "permission denied")
|
|
||||||
}
|
|
||||||
return service.contestStorage.DeleteContest(ctx, id)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
package languages
|
|
||||||
|
|
||||||
type languageHandlers interface {
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
package grpc
|
|
|
@ -1,10 +0,0 @@
|
||||||
package languages
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"git.sch9.ru/new_gate/models"
|
|
||||||
)
|
|
||||||
|
|
||||||
type LanguageRepository interface {
|
|
||||||
ReadLanguageById(ctx context.Context, id int32) (*models.Language, error)
|
|
||||||
}
|
|
|
@ -1,28 +0,0 @@
|
||||||
package repository
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"git.sch9.ru/new_gate/models"
|
|
||||||
"git.sch9.ru/new_gate/ms-tester/pkg/utils"
|
|
||||||
"github.com/jmoiron/sqlx"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
)
|
|
||||||
|
|
||||||
type LanguageRepository struct {
|
|
||||||
db *sqlx.DB
|
|
||||||
logger *zap.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewLanguageRepository(db *sqlx.DB, logger *zap.Logger) *LanguageRepository {
|
|
||||||
return &LanguageRepository{
|
|
||||||
db: db,
|
|
||||||
logger: logger,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *LanguageRepository) ReadLanguageById(ctx context.Context, id int32) (*models.Language, error) {
|
|
||||||
if id <= int32(len(models.Languages)) {
|
|
||||||
return nil, utils.StorageError(nil, utils.ErrNotFound, "languages not found")
|
|
||||||
}
|
|
||||||
return &models.Languages[id], nil
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
package languages
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"git.sch9.ru/new_gate/models"
|
|
||||||
)
|
|
||||||
|
|
||||||
type LanguageUseCase interface {
|
|
||||||
ReadLanguageById(ctx context.Context, id int32) (*models.Language, error)
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
package usecase
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"git.sch9.ru/new_gate/models"
|
|
||||||
"git.sch9.ru/new_gate/ms-tester/internal/languages"
|
|
||||||
)
|
|
||||||
|
|
||||||
type LanguageUseCase struct {
|
|
||||||
languageRepo languages.LanguageRepository
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewLanguageUseCase(
|
|
||||||
languageRepo languages.LanguageRepository,
|
|
||||||
) *LanguageUseCase {
|
|
||||||
return &LanguageUseCase{
|
|
||||||
languageRepo: languageRepo,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (uc *LanguageUseCase) ReadLanguageById(ctx context.Context, id int32) (*models.Language, error) {
|
|
||||||
//userId := ctx.Value("user_id").(int32)
|
|
||||||
panic("access control is not implemented yet")
|
|
||||||
return uc.languageRepo.ReadLanguageById(ctx, id)
|
|
||||||
}
|
|
10
internal/models/contest.go
Normal file
10
internal/models/contest.go
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
package models
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type Contest struct {
|
||||||
|
Id *int32 `db:"id"`
|
||||||
|
Title *string `db:"title"`
|
||||||
|
CreatedAt *time.Time `db:"created_at"`
|
||||||
|
UpdatedAt *time.Time `db:"updated_at"`
|
||||||
|
}
|
13
internal/models/language.go
Normal file
13
internal/models/language.go
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
package models
|
||||||
|
|
||||||
|
type Language struct {
|
||||||
|
Name string
|
||||||
|
CompileCmd []string //source: src;result:executable
|
||||||
|
RunCmd []string //source: executable
|
||||||
|
}
|
||||||
|
|
||||||
|
var Languages = [...]Language{
|
||||||
|
{Name: "gcc std=c90",
|
||||||
|
CompileCmd: []string{"gcc", "src", "-std=c90", "-o", "executable"},
|
||||||
|
RunCmd: []string{"executable"}},
|
||||||
|
}
|
8
internal/models/participant.go
Normal file
8
internal/models/participant.go
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
package models
|
||||||
|
|
||||||
|
type Participant struct {
|
||||||
|
Id *int32 `db:"id"`
|
||||||
|
UserId *int32 `db:"user_id"`
|
||||||
|
ContestId *int32 `db:"contest_id"`
|
||||||
|
Name *string `db:"name"`
|
||||||
|
}
|
15
internal/models/problem.go
Normal file
15
internal/models/problem.go
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
package models
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type Problem struct {
|
||||||
|
Id *int32 `db:"id"`
|
||||||
|
Title *string `db:"title"`
|
||||||
|
Content *string `db:"content"`
|
||||||
|
TimeLimit *int32 `db:"time_limit"`
|
||||||
|
MemoryLimit *int32 `db:"memory_limit"`
|
||||||
|
TestingStrategy *int32 `db:"testing_strategy"`
|
||||||
|
TestingOrder *string `db:"testing_order"`
|
||||||
|
CreatedAt *time.Time `db:"created_at"`
|
||||||
|
UpdatedAt *time.Time `db:"updated_at"`
|
||||||
|
}
|
30
internal/models/result.go
Normal file
30
internal/models/result.go
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Result int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
NotTested Result = 1 // change only with schema change
|
||||||
|
Accepted Result = 2
|
||||||
|
WrongAnswer Result = 3
|
||||||
|
PresentationError Result = 4
|
||||||
|
CompilationError Result = 5
|
||||||
|
MemoryLimitExceeded Result = 6
|
||||||
|
TimeLimitExceeded Result = 7
|
||||||
|
RuntimeError Result = 8
|
||||||
|
SystemFailDuringTesting Result = 9
|
||||||
|
Testing Result = 10
|
||||||
|
)
|
||||||
|
|
||||||
|
var ErrBadResult = errors.New("bad result")
|
||||||
|
|
||||||
|
func (result Result) Valid() error {
|
||||||
|
switch result {
|
||||||
|
case NotTested, Accepted, TimeLimitExceeded, MemoryLimitExceeded, CompilationError, SystemFailDuringTesting:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return ErrBadResult
|
||||||
|
}
|
52
internal/models/role.go
Normal file
52
internal/models/role.go
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Role int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
RoleSpectator Role = 0
|
||||||
|
RoleParticipant Role = 1
|
||||||
|
RoleModerator Role = 2
|
||||||
|
RoleAdmin Role = 3
|
||||||
|
)
|
||||||
|
|
||||||
|
func (role Role) IsAdmin() bool {
|
||||||
|
return role == RoleAdmin
|
||||||
|
}
|
||||||
|
|
||||||
|
func (role Role) IsModerator() bool {
|
||||||
|
return role == RoleModerator
|
||||||
|
}
|
||||||
|
|
||||||
|
func (role Role) IsParticipant() bool {
|
||||||
|
return role == RoleParticipant
|
||||||
|
}
|
||||||
|
|
||||||
|
func (role Role) IsSpectator() bool {
|
||||||
|
return role == RoleSpectator
|
||||||
|
}
|
||||||
|
|
||||||
|
func (role Role) AtLeast(other Role) bool {
|
||||||
|
return role >= other
|
||||||
|
}
|
||||||
|
|
||||||
|
func (role Role) AtMost(other Role) bool {
|
||||||
|
return role <= other
|
||||||
|
}
|
||||||
|
|
||||||
|
var ErrBadRole = errors.New("bad role")
|
||||||
|
|
||||||
|
func (role Role) Valid() error {
|
||||||
|
switch role {
|
||||||
|
case RoleSpectator, RoleParticipant, RoleModerator, RoleAdmin:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return ErrBadRole
|
||||||
|
}
|
||||||
|
|
||||||
|
func (role Role) AsPointer() *Role {
|
||||||
|
return &role
|
||||||
|
}
|
14
internal/models/solution.go
Normal file
14
internal/models/solution.go
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
package models
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type Solution struct {
|
||||||
|
Id *int32 `db:"id"`
|
||||||
|
ParticipantId *int32 `db:"participant_id"`
|
||||||
|
TaskId *int32 `db:"task_id"`
|
||||||
|
LanguageId *int32 `db:"language_id"`
|
||||||
|
SolutionHash *string `db:"solution_hash"`
|
||||||
|
Result *int32 `db:"result"`
|
||||||
|
Score *int32 `db:"score"`
|
||||||
|
CreatedAt *time.Time `db:"created_at"`
|
||||||
|
}
|
9
internal/models/task.go
Normal file
9
internal/models/task.go
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
package models
|
||||||
|
|
||||||
|
type Task struct {
|
||||||
|
Id *int32 `db:"id"`
|
||||||
|
ContestId *int32 `db:"contest_id"`
|
||||||
|
ProblemId *int32 `db:"problem_id"`
|
||||||
|
Position *int32 `db:"position"`
|
||||||
|
PositionName *string `db:"position_name"`
|
||||||
|
}
|
9
internal/models/user.go
Normal file
9
internal/models/user.go
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
package models
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
UserId *int32 `json:"user_id" db:"user_id"`
|
||||||
|
Role *Role `json:"role" db:"role"`
|
||||||
|
UpdatedAt *time.Time `json:"updated_at" db:"updated_at"`
|
||||||
|
}
|
|
@ -7,7 +7,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Handlers interface {
|
type Handlers interface {
|
||||||
CreateProblem(server problemv1.ProblemService_CreateProblemServer) error
|
CreateProblem(ctx context.Context, req *problemv1.CreateProblemRequest) (*problemv1.CreateProblemResponse, error)
|
||||||
ReadProblem(ctx context.Context, req *problemv1.ReadProblemRequest) (*problemv1.ReadProblemResponse, error)
|
ReadProblem(ctx context.Context, req *problemv1.ReadProblemRequest) (*problemv1.ReadProblemResponse, error)
|
||||||
DeleteProblem(ctx context.Context, req *problemv1.DeleteProblemRequest) (*emptypb.Empty, error)
|
DeleteProblem(ctx context.Context, req *problemv1.DeleteProblemRequest) (*emptypb.Empty, error)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,15 +2,11 @@ package grpc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"git.sch9.ru/new_gate/models"
|
|
||||||
"git.sch9.ru/new_gate/ms-tester/internal/problems"
|
"git.sch9.ru/new_gate/ms-tester/internal/problems"
|
||||||
problemv1 "git.sch9.ru/new_gate/ms-tester/pkg/go/gen/proto/problem/v1"
|
problemv1 "git.sch9.ru/new_gate/ms-tester/pkg/go/gen/proto/problem/v1"
|
||||||
"git.sch9.ru/new_gate/ms-tester/pkg/utils"
|
"git.sch9.ru/new_gate/ms-tester/pkg/utils"
|
||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/grpc/status"
|
|
||||||
"google.golang.org/protobuf/types/known/emptypb"
|
"google.golang.org/protobuf/types/known/emptypb"
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type problemHandlers struct {
|
type problemHandlers struct {
|
||||||
|
@ -19,105 +15,18 @@ type problemHandlers struct {
|
||||||
problemUC problems.ProblemUseCase
|
problemUC problems.ProblemUseCase
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *problemHandlers) CreateProblem(server problemv1.ProblemService_CreateProblemServer) error {
|
func NewProblemHandlers(gserver *grpc.Server, problemUC problems.ProblemUseCase) {
|
||||||
ctx := server.Context()
|
handlers := &problemHandlers{problemUC: problemUC}
|
||||||
|
|
||||||
if err := h.problemUC.CanCreateProblem(ctx); err != nil {
|
problemv1.RegisterProblemServiceServer(gserver, handlers)
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
req, err := server.Recv() // receive problem
|
func (h *problemHandlers) CreateProblem(ctx context.Context, req *problemv1.CreateProblemRequest) (*problemv1.CreateProblemResponse, error) {
|
||||||
|
id, err := h.problemUC.CreateProblem(ctx, req.GetTitle())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return utils.TransportError(err, utils.ErrBadInput, "can't receive problem")
|
return nil, err
|
||||||
}
|
}
|
||||||
problem := req.GetProblem()
|
return &problemv1.CreateProblemResponse{Id: id}, nil
|
||||||
if problem == nil {
|
|
||||||
return utils.TransportError(nil, utils.ErrBadInput, "empty problem")
|
|
||||||
}
|
|
||||||
|
|
||||||
p := &models.Problem{
|
|
||||||
Name: utils.AsStringP(problem.Name),
|
|
||||||
Description: utils.AsStringP(problem.Description),
|
|
||||||
TimeLimit: utils.AsInt32P(problem.TimeLimit),
|
|
||||||
MemoryLimit: utils.AsInt32P(problem.MemoryLimit),
|
|
||||||
}
|
|
||||||
|
|
||||||
ch := readChunks(ctx, server)
|
|
||||||
|
|
||||||
err = writeChunks(ctx, ch) // temp stub
|
|
||||||
if err != nil {
|
|
||||||
return err // FIXME
|
|
||||||
}
|
|
||||||
|
|
||||||
id, err := h.problemUC.CreateProblem(ctx, p) // FIXME
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = server.SendAndClose(&problemv1.CreateProblemResponse{
|
|
||||||
Id: id,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return utils.TransportError(err, utils.ErrBadInput, "can't send response")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeChunks(ctx context.Context, chunks <-chan []byte) error {
|
|
||||||
// use s3
|
|
||||||
// FIXME: use ctx?
|
|
||||||
f, err := os.Create("out.txt") // FIXME: uuidv4 as initial temp name?
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
var off int64 = 0
|
|
||||||
for chunk := range chunks {
|
|
||||||
_, err = f.WriteAt(chunk, off)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
off += int64(len(chunk))
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: rename file to its hash
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func readChunks(ctx context.Context, server problemv1.ProblemService_CreateProblemServer) <-chan []byte {
|
|
||||||
ch := make(chan []byte)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
defer close(ch)
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return // FIXME
|
|
||||||
default:
|
|
||||||
req, err := server.Recv()
|
|
||||||
if err != nil {
|
|
||||||
if err == io.EOF {
|
|
||||||
return // FIXME
|
|
||||||
}
|
|
||||||
if status.Code(err) == codes.Canceled {
|
|
||||||
return // FIXME
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
test := req.GetTest()
|
|
||||||
if test == nil {
|
|
||||||
return // FIXME
|
|
||||||
}
|
|
||||||
|
|
||||||
ch <- test.Chunk
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
return ch
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *problemHandlers) ReadProblem(ctx context.Context, req *problemv1.ReadProblemRequest) (*problemv1.ReadProblemResponse, error) {
|
func (h *problemHandlers) ReadProblem(ctx context.Context, req *problemv1.ReadProblemRequest) (*problemv1.ReadProblemResponse, error) {
|
||||||
|
@ -128,38 +37,18 @@ func (h *problemHandlers) ReadProblem(ctx context.Context, req *problemv1.ReadPr
|
||||||
return &problemv1.ReadProblemResponse{
|
return &problemv1.ReadProblemResponse{
|
||||||
Problem: &problemv1.ReadProblemResponse_Problem{
|
Problem: &problemv1.ReadProblemResponse_Problem{
|
||||||
Id: *problem.Id,
|
Id: *problem.Id,
|
||||||
Name: *problem.Name,
|
Title: *problem.Title,
|
||||||
Description: *problem.Description,
|
Content: *problem.Content,
|
||||||
TimeLimit: *problem.TimeLimit,
|
TimeLimit: *problem.TimeLimit,
|
||||||
MemoryLimit: *problem.MemoryLimit,
|
MemoryLimit: *problem.MemoryLimit,
|
||||||
|
TestingStrategy: *problem.TestingStrategy,
|
||||||
|
TestingOrder: *problem.TestingOrder,
|
||||||
CreatedAt: utils.TimestampP(problem.CreatedAt),
|
CreatedAt: utils.TimestampP(problem.CreatedAt),
|
||||||
UpdatedAt: utils.TimestampP(problem.UpdatedAt),
|
UpdatedAt: utils.TimestampP(problem.UpdatedAt),
|
||||||
},
|
},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
//func (h *problemHandlers) UpdateProblem(ctx context.Context, req *problemv1.UpdateProblemRequest) (*emptypb.Empty, error) {
|
|
||||||
// problem := req.GetProblem()
|
|
||||||
// if problem == nil {
|
|
||||||
// return nil, status.Errorf(codes.Unknown, "") // FIXME
|
|
||||||
// }
|
|
||||||
// err := s.problemService.UpdateProblem(
|
|
||||||
// ctx,
|
|
||||||
// &models.Problem{
|
|
||||||
// Id: utils.AsInt32P(problem.Id),
|
|
||||||
// Name: problem.Name,
|
|
||||||
// Description: problem.Description,
|
|
||||||
// TimeLimit: problem.TimeLimit,
|
|
||||||
// MemoryLimit: problem.MemoryLimit,
|
|
||||||
// },
|
|
||||||
// )
|
|
||||||
// if err != nil {
|
|
||||||
// return nil, status.Errorf(codes.Unknown, err.Error()) // FIXME
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// return &emptypb.Empty{}, nil
|
|
||||||
//}
|
|
||||||
|
|
||||||
func (h *problemHandlers) DeleteProblem(ctx context.Context, req *problemv1.DeleteProblemRequest) (*emptypb.Empty, error) {
|
func (h *problemHandlers) DeleteProblem(ctx context.Context, req *problemv1.DeleteProblemRequest) (*emptypb.Empty, error) {
|
||||||
err := h.problemUC.DeleteProblem(ctx, req.GetId())
|
err := h.problemUC.DeleteProblem(ctx, req.GetId())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -2,12 +2,11 @@ package problems
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"git.sch9.ru/new_gate/models"
|
"git.sch9.ru/new_gate/ms-tester/internal/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ProblemPostgresRepository interface {
|
type ProblemPostgresRepository interface {
|
||||||
CreateProblem(ctx context.Context, problem *models.Problem, testGroupData []models.TestGroupData) (int32, error)
|
CreateProblem(ctx context.Context, title string) (int32, error)
|
||||||
ReadProblemById(ctx context.Context, id int32) (*models.Problem, error)
|
ReadProblemById(ctx context.Context, id int32) (*models.Problem, error)
|
||||||
UpdateProblem(ctx context.Context, problem *models.Problem) error
|
|
||||||
DeleteProblem(ctx context.Context, id int32) error
|
DeleteProblem(ctx context.Context, id int32) error
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,59 +23,19 @@ func NewProblemRepository(db *sqlx.DB, logger *zap.Logger) *ProblemRepository {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (storage *ProblemRepository) CreateProblem(ctx context.Context, problem *models.Problem, testGroupData []models.TestGroupData) (int32, error) {
|
const createProblemQuery = "INSERT INTO problems (title) VALUES (?) RETURNING id"
|
||||||
tx, err := storage.db.Beginx()
|
|
||||||
|
func (r *ProblemRepository) CreateProblem(ctx context.Context, title string) (int32, error) {
|
||||||
|
query := r.db.Rebind(createProblemQuery)
|
||||||
|
rows, err := r.db.QueryxContext(ctx, query, title)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, handlePgErr(err)
|
return 0, handlePgErr(err)
|
||||||
}
|
}
|
||||||
query := tx.Rebind(`
|
|
||||||
INSERT INTO problems
|
|
||||||
(name,description,time_limit,memory_limit)
|
|
||||||
VALUES (?, ?, ?, ?)
|
|
||||||
RETURNING id
|
|
||||||
`)
|
|
||||||
rows, err := tx.QueryxContext(
|
|
||||||
ctx,
|
|
||||||
query,
|
|
||||||
problem.Name,
|
|
||||||
problem.Description,
|
|
||||||
problem.TimeLimit,
|
|
||||||
problem.MemoryLimit,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return 0, handlePgErr(errors.Join(err, tx.Rollback()))
|
|
||||||
}
|
|
||||||
for _, tgd := range testGroupData {
|
|
||||||
query := tx.Rebind(`
|
|
||||||
INSERT INTO testgroups
|
|
||||||
(problem_id,testing_strategy)
|
|
||||||
VALUES ((select last_value from problems_id_seq),?)
|
|
||||||
RETURNING id
|
|
||||||
`)
|
|
||||||
rows, err = tx.QueryxContext(ctx, query, tgd.Ts)
|
|
||||||
if err != nil {
|
|
||||||
return 0, handlePgErr(errors.Join(err, tx.Rollback()))
|
|
||||||
}
|
|
||||||
var i int32 = 0
|
|
||||||
for ; i < tgd.TestAmount; i++ {
|
|
||||||
query := tx.Rebind(`
|
|
||||||
INSERT INTO tests
|
|
||||||
(testgroup_id)
|
|
||||||
VALUES ((select last_value from testgroups_id_seq))
|
|
||||||
RETURNING id
|
|
||||||
`)
|
|
||||||
rows, err = tx.QueryxContext(ctx, query, tgd.Ts)
|
|
||||||
if err != nil {
|
|
||||||
return 0, handlePgErr(errors.Join(err, tx.Rollback()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
err = tx.Commit()
|
|
||||||
//add test saving
|
|
||||||
|
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
var id int32
|
var id int32
|
||||||
err = rows.StructScan(&id)
|
rows.Next()
|
||||||
|
err = rows.Scan(&id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, handlePgErr(err)
|
return 0, handlePgErr(err)
|
||||||
}
|
}
|
||||||
|
@ -83,28 +43,23 @@ RETURNING id
|
||||||
return id, nil
|
return id, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (storage *ProblemRepository) ReadProblemById(ctx context.Context, id int32) (*models.Problem, error) {
|
const readProblemQuery = "SELECT * from problems WHERE id=? LIMIT 1"
|
||||||
|
|
||||||
|
func (r *ProblemRepository) ReadProblemById(ctx context.Context, id int32) (*models.Problem, error) {
|
||||||
var problem models.Problem
|
var problem models.Problem
|
||||||
query := storage.db.Rebind("SELECT * from problems WHERE id=? LIMIT 1")
|
query := r.db.Rebind(readProblemQuery)
|
||||||
err := storage.db.GetContext(ctx, &problem, query, id)
|
err := r.db.GetContext(ctx, &problem, query, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, handlePgErr(err)
|
return nil, handlePgErr(err)
|
||||||
}
|
}
|
||||||
return &problem, nil
|
return &problem, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (storage *ProblemRepository) UpdateProblem(ctx context.Context, problem *models.Problem) error {
|
const deleteProblemQuery = "DELETE FROM problems WHERE id=?"
|
||||||
query := storage.db.Rebind("UPDATE problems SET name=?,description=?,time_limit=?,memory_limit=? WHERE id=?")
|
|
||||||
_, err := storage.db.ExecContext(ctx, query, problem.Name, problem.Description, problem.TimeLimit, problem.MemoryLimit, problem.Id)
|
|
||||||
if err != nil {
|
|
||||||
return handlePgErr(err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (storage *ProblemRepository) DeleteProblem(ctx context.Context, id int32) error {
|
func (r *ProblemRepository) DeleteProblem(ctx context.Context, id int32) error {
|
||||||
query := storage.db.Rebind("DELETE FROM problems WHERE id=?")
|
query := r.db.Rebind(deleteProblemQuery)
|
||||||
_, err := storage.db.ExecContext(ctx, query, id)
|
_, err := r.db.ExecContext(ctx, query, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return handlePgErr(err)
|
return handlePgErr(err)
|
||||||
}
|
}
|
||||||
|
|
58
internal/problems/repository/pg_repository_test.go
Normal file
58
internal/problems/repository/pg_repository_test.go
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/DATA-DOG/go-sqlmock"
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestProblemRepository_CreateProblem(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
db, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
sqlxDB := sqlx.NewDb(db, "sqlmock")
|
||||||
|
defer sqlxDB.Close()
|
||||||
|
|
||||||
|
problemRepo := NewProblemRepository(sqlxDB, zap.NewNop())
|
||||||
|
|
||||||
|
t.Run("valid problem creation", func(t *testing.T) {
|
||||||
|
title := "Problem title"
|
||||||
|
|
||||||
|
rows := sqlmock.NewRows([]string{"id"}).AddRow(1)
|
||||||
|
|
||||||
|
mock.ExpectQuery(sqlxDB.Rebind(createProblemQuery)).WithArgs(title).WillReturnRows(rows)
|
||||||
|
|
||||||
|
id, err := problemRepo.CreateProblem(context.Background(), title)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, int32(1), id)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProblemRepository_DeleteProblem(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
db, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
sqlxDB := sqlx.NewDb(db, "sqlmock")
|
||||||
|
defer sqlxDB.Close()
|
||||||
|
|
||||||
|
problemRepo := NewProblemRepository(sqlxDB, zap.NewNop())
|
||||||
|
|
||||||
|
t.Run("valid problem deletion", func(t *testing.T) {
|
||||||
|
id := int32(1)
|
||||||
|
rows := sqlmock.NewResult(1, 1)
|
||||||
|
|
||||||
|
mock.ExpectExec(sqlxDB.Rebind(deleteProblemQuery)).WithArgs(id).WillReturnResult(rows)
|
||||||
|
|
||||||
|
err = problemRepo.DeleteProblem(context.Background(), id)
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
}
|
|
@ -2,16 +2,17 @@ package problems
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"git.sch9.ru/new_gate/models"
|
"git.sch9.ru/new_gate/ms-tester/internal/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ProblemUseCase interface {
|
type ProblemPolicyAgent interface {
|
||||||
CanCreateProblem(ctx context.Context) error
|
CanCreateProblem(ctx context.Context) error
|
||||||
CanReadProblemById(ctx context.Context) error
|
CanReadProblem(ctx context.Context) error
|
||||||
CanUpdateProblem(ctx context.Context) error
|
|
||||||
CanDeleteProblem(ctx context.Context) error
|
CanDeleteProblem(ctx context.Context) error
|
||||||
CreateProblem(ctx context.Context, problem *models.Problem) (int32, error)
|
}
|
||||||
|
|
||||||
|
type ProblemUseCase interface {
|
||||||
|
CreateProblem(ctx context.Context, title string) (int32, error)
|
||||||
ReadProblemById(ctx context.Context, id int32) (*models.Problem, error)
|
ReadProblemById(ctx context.Context, id int32) (*models.Problem, error)
|
||||||
UpdateProblem(ctx context.Context, problem *models.Problem) error
|
|
||||||
DeleteProblem(ctx context.Context, id int32) error
|
DeleteProblem(ctx context.Context, id int32) error
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,44 +0,0 @@
|
||||||
package problem.rbac
|
|
||||||
|
|
||||||
import rego.v1
|
|
||||||
|
|
||||||
spectator := 0
|
|
||||||
participant := 1
|
|
||||||
moderator := 2
|
|
||||||
admin := 3
|
|
||||||
|
|
||||||
permissions := {
|
|
||||||
"read": is_spectator,
|
|
||||||
"participate": is_participant,
|
|
||||||
"update": is_moderator,
|
|
||||||
"create": is_moderator,
|
|
||||||
"delete": is_moderator,
|
|
||||||
}
|
|
||||||
|
|
||||||
default allow := false
|
|
||||||
|
|
||||||
allow if is_admin
|
|
||||||
|
|
||||||
allow if {
|
|
||||||
permissions[input.action]
|
|
||||||
}
|
|
||||||
|
|
||||||
default is_admin := false
|
|
||||||
is_admin if {
|
|
||||||
input.user.role == admin
|
|
||||||
}
|
|
||||||
|
|
||||||
default is_moderator := false
|
|
||||||
is_moderator if {
|
|
||||||
input.user.role >= moderator
|
|
||||||
}
|
|
||||||
|
|
||||||
default is_participant := false
|
|
||||||
is_participant if {
|
|
||||||
input.user.role >= participant
|
|
||||||
}
|
|
||||||
|
|
||||||
default is_spectator := true
|
|
||||||
is_spectator if {
|
|
||||||
input.user.role >= spectator
|
|
||||||
}
|
|
67
internal/problems/usecase/policy_agent.go
Normal file
67
internal/problems/usecase/policy_agent.go
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
package usecase
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"git.sch9.ru/new_gate/models"
|
||||||
|
"github.com/open-policy-agent/opa/rego"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PermissionService struct {
|
||||||
|
query *rego.PreparedEvalQuery
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPermissionService() *PermissionService {
|
||||||
|
query, err := rego.New(
|
||||||
|
rego.Query("allow = data.problem.rbac.allow"),
|
||||||
|
rego.Load([]string{"./opa/problem.rego"}, nil),
|
||||||
|
).PrepareForEval(context.TODO())
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &PermissionService{
|
||||||
|
query: &query,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PermissionService) Allowed(ctx context.Context, user *models.User, action string) bool {
|
||||||
|
input := map[string]interface{}{
|
||||||
|
"user": user,
|
||||||
|
"action": action,
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := s.query.Eval(ctx, rego.EvalInput(input))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return result[0].Bindings["allow"].(bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
//func (service *ProblemUseCase) CanCreateProblem(ctx context.Context) error {
|
||||||
|
// if !service.permissionService.Allowed(ctx, extractUser(ctx), "create") {
|
||||||
|
// return utils.ServiceError(nil, utils.ErrNoPermission, "permission denied")
|
||||||
|
// }
|
||||||
|
// return nil
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//func (service *ProblemUseCase) CanReadProblemById(ctx context.Context) error {
|
||||||
|
// if !service.permissionService.Allowed(ctx, extractUser(ctx), "read") {
|
||||||
|
// return utils.ServiceError(nil, utils.ErrNoPermission, "permission denied")
|
||||||
|
// }
|
||||||
|
// return nil
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//func (service *ProblemUseCase) CanUpdateProblem(ctx context.Context) error {
|
||||||
|
// if !service.permissionService.Allowed(ctx, extractUser(ctx), "update") {
|
||||||
|
// return utils.ServiceError(nil, utils.ErrNoPermission, "permission denied")
|
||||||
|
// }
|
||||||
|
// return nil
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//func (service *ProblemUseCase) CanDeleteProblem(ctx context.Context) error {
|
||||||
|
// if !service.permissionService.Allowed(ctx, extractUser(ctx), "delete") {
|
||||||
|
// return utils.ServiceError(nil, utils.ErrNoPermission, "permission denied")
|
||||||
|
// }
|
||||||
|
// return nil
|
||||||
|
//}
|
|
@ -2,100 +2,34 @@ package usecase
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"git.sch9.ru/new_gate/models"
|
"git.sch9.ru/new_gate/ms-tester/internal/models"
|
||||||
|
"git.sch9.ru/new_gate/ms-tester/internal/problems"
|
||||||
"git.sch9.ru/new_gate/ms-tester/pkg/external/pandoc"
|
"git.sch9.ru/new_gate/ms-tester/pkg/external/pandoc"
|
||||||
"git.sch9.ru/new_gate/ms-tester/pkg/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type ProblemStorage interface {
|
|
||||||
CreateProblem(ctx context.Context, problem *models.Problem, testGroupData []models.TestGroupData) (int32, error)
|
|
||||||
ReadProblemById(ctx context.Context, id int32) (*models.Problem, error)
|
|
||||||
UpdateProblem(ctx context.Context, problem *models.Problem) error
|
|
||||||
DeleteProblem(ctx context.Context, id int32) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type IPermissionService interface {
|
|
||||||
Allowed(ctx context.Context, user *models.User, action string) bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type ProblemUseCase struct {
|
type ProblemUseCase struct {
|
||||||
problemStorage ProblemStorage
|
problemRepo problems.ProblemPostgresRepository
|
||||||
pandocClient pandoc.PandocClient
|
pandocClient pandoc.PandocClient
|
||||||
permissionService IPermissionService
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewProblemUseCase(
|
func NewProblemUseCase(
|
||||||
problemStorage ProblemStorage,
|
problemRepo problems.ProblemPostgresRepository,
|
||||||
pandocClient pandoc.PandocClient,
|
pandocClient pandoc.PandocClient,
|
||||||
permissionService IPermissionService,
|
|
||||||
) *ProblemUseCase {
|
) *ProblemUseCase {
|
||||||
return &ProblemUseCase{
|
return &ProblemUseCase{
|
||||||
problemStorage: problemStorage,
|
problemRepo: problemRepo,
|
||||||
pandocClient: pandocClient,
|
pandocClient: pandocClient,
|
||||||
permissionService: permissionService,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func extractUser(ctx context.Context) *models.User {
|
func (u *ProblemUseCase) CreateProblem(ctx context.Context, title string) (int32, error) {
|
||||||
return ctx.Value("user").(*models.User)
|
return u.problemRepo.CreateProblem(ctx, title)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (service *ProblemUseCase) CanCreateProblem(ctx context.Context) error {
|
func (u *ProblemUseCase) ReadProblemById(ctx context.Context, id int32) (*models.Problem, error) {
|
||||||
if !service.permissionService.Allowed(ctx, extractUser(ctx), "create") {
|
return u.problemRepo.ReadProblemById(ctx, id)
|
||||||
return utils.ServiceError(nil, utils.ErrNoPermission, "permission denied")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (service *ProblemUseCase) CanReadProblemById(ctx context.Context) error {
|
func (u *ProblemUseCase) DeleteProblem(ctx context.Context, id int32) error {
|
||||||
if !service.permissionService.Allowed(ctx, extractUser(ctx), "read") {
|
return u.problemRepo.DeleteProblem(ctx, id)
|
||||||
return utils.ServiceError(nil, utils.ErrNoPermission, "permission denied")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (service *ProblemUseCase) CanUpdateProblem(ctx context.Context) error {
|
|
||||||
if !service.permissionService.Allowed(ctx, extractUser(ctx), "update") {
|
|
||||||
return utils.ServiceError(nil, utils.ErrNoPermission, "permission denied")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (service *ProblemUseCase) CanDeleteProblem(ctx context.Context) error {
|
|
||||||
if !service.permissionService.Allowed(ctx, extractUser(ctx), "delete") {
|
|
||||||
return utils.ServiceError(nil, utils.ErrNoPermission, "permission denied")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (service *ProblemUseCase) CreateProblem(ctx context.Context, problem *models.Problem) (int32, error) {
|
|
||||||
if err := service.CanCreateProblem(ctx); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
_, err := service.pandocClient.ConvertLatexToHtml5(ctx, *problem.Description)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return service.problemStorage.CreateProblem(ctx, problem, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (service *ProblemUseCase) ReadProblemById(ctx context.Context, id int32) (*models.Problem, error) {
|
|
||||||
if err := service.CanReadProblemById(ctx); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return service.problemStorage.ReadProblemById(ctx, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (service *ProblemUseCase) UpdateProblem(ctx context.Context, problem *models.Problem) error {
|
|
||||||
if err := service.CanUpdateProblem(ctx); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return service.problemStorage.UpdateProblem(ctx, problem)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (service *ProblemUseCase) DeleteProblem(ctx context.Context, id int32) error {
|
|
||||||
if err := service.CanDeleteProblem(ctx); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return service.problemStorage.DeleteProblem(ctx, id)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ type PermissionService struct {
|
||||||
func NewPermissionService() *PermissionService {
|
func NewPermissionService() *PermissionService {
|
||||||
query, err := rego.New(
|
query, err := rego.New(
|
||||||
rego.Query("allow = data.problem.rbac.allow"),
|
rego.Query("allow = data.problem.rbac.allow"),
|
||||||
rego.Load([]string{"./opa/all.rego"}, nil),
|
rego.Load([]string{"./opa/problem.rego"}, nil),
|
||||||
).PrepareForEval(context.TODO())
|
).PrepareForEval(context.TODO())
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -1,264 +1,125 @@
|
||||||
-- +goose Up
|
-- +goose Up
|
||||||
-- +goose StatementBegin
|
-- +goose StatementBegin
|
||||||
--CREATE TABLE IF NOT EXISTS languages
|
|
||||||
--(
|
|
||||||
-- id serial NOT NULL,
|
|
||||||
-- name VARCHAR(60) NOT NULL,
|
|
||||||
-- build_file_hash CHAR(128) NULL,
|
|
||||||
-- execute_file_hash CHAR(128) NULL,
|
|
||||||
-- created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
||||||
-- updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
||||||
--
|
|
||||||
-- PRIMARY KEY (id)
|
|
||||||
--);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS problems
|
CREATE TABLE IF NOT EXISTS problems
|
||||||
(
|
(
|
||||||
id serial NOT NULL,
|
id serial NOT NULL,
|
||||||
name VARCHAR(300) NOT NULL,
|
title varchar(255) NOT NULL,
|
||||||
description TEXT NOT NULL,
|
content varchar(65536) NOT NULL DEFAULT '',
|
||||||
time_limit INT NOT NULL,
|
time_limit integer NOT NULL DEFAULT 1000,
|
||||||
memory_limit INT NOT NULL,
|
memory_limit integer NOT NULL DEFAULT 65536,
|
||||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
testing_strategy integer NOT NULL DEFAULT 1,
|
||||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
testing_order varchar(1024) NOT NULL DEFAULT '',
|
||||||
|
created_at timestamptz NOT NULL DEFAULT now(),
|
||||||
PRIMARY KEY(id)
|
updated_at timestamptz NOT NULL DEFAULT now(),
|
||||||
|
PRIMARY KEY (id),
|
||||||
|
CHECK (length(title) != 0),
|
||||||
|
CHECK (memory_limit > 0),
|
||||||
|
CHECK (time_limit > 0),
|
||||||
|
CHECK (testing_strategy > 0)
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE INDEX ON problems USING BTREE (id);
|
CREATE TRIGGER on_problems_update
|
||||||
|
BEFORE UPDATE
|
||||||
|
ON problems
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE PROCEDURE updated_at_update();
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS contests
|
CREATE TABLE IF NOT EXISTS contests
|
||||||
(
|
(
|
||||||
id serial NOT NULL,
|
id serial NOT NULL,
|
||||||
name VARCHAR(300) NOT NULL,
|
title varchar(255) NOT NULL,
|
||||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
created_at timestamptz NOT NULL DEFAULT now(),
|
||||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
updated_at timestamptz NOT NULL DEFAULT now(),
|
||||||
|
PRIMARY KEY (id),
|
||||||
PRIMARY KEY(id)
|
CHECK (length(title) != 0)
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE INDEX ON contests USING BTREE (id);
|
CREATE TRIGGER on_contests_update
|
||||||
|
BEFORE UPDATE
|
||||||
|
ON contests
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE PROCEDURE updated_at_update();
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS tasks
|
CREATE TABLE IF NOT EXISTS tasks
|
||||||
(
|
|
||||||
id SERIAL NOT NULL,
|
|
||||||
contest_id INT REFERENCES contests ON DELETE CASCADE,
|
|
||||||
problem_id INT REFERENCES problems ON DELETE CASCADE,
|
|
||||||
position INT NOT NULL,
|
|
||||||
position_name VARCHAR(10) NOT NULL, -- problem name like: A,B,A1,etc
|
|
||||||
UNIQUE (contest_id,problem_id),
|
|
||||||
UNIQUE (contest_id,position),
|
|
||||||
UNIQUE (contest_id,position_name),
|
|
||||||
PRIMARY KEY (id)
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX ON tasks USING BTREE (id);
|
|
||||||
CREATE INDEX ON tasks USING BTREE (contest_id);
|
|
||||||
CREATE INDEX ON tasks USING BTREE (problem_id);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS testgroups
|
|
||||||
(
|
(
|
||||||
id serial NOT NULL,
|
id serial NOT NULL,
|
||||||
problem_id INT REFERENCES problems ON DELETE CASCADE,
|
problem_id integer NOT NULL REFERENCES problems (id),
|
||||||
testing_strategy INT NOT NULL,
|
contest_id integer NOT NULL REFERENCES contests (id),
|
||||||
|
position integer NOT NULL,
|
||||||
PRIMARY KEY (id)
|
prefix varchar(10) NOT NULL,
|
||||||
|
created_at timestamptz NOT NULL DEFAULT now(),
|
||||||
|
updated_at timestamptz NOT NULL DEFAULT now(),
|
||||||
|
PRIMARY KEY (id),
|
||||||
|
UNIQUE (problem_id, contest_id),
|
||||||
|
CHECK (position >= 0)
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE INDEX ON testgroups USING BTREE (id);
|
CREATE TRIGGER on_tasks_update
|
||||||
CREATE INDEX ON testgroups USING BTREE (problem_id);
|
BEFORE UPDATE
|
||||||
|
ON tasks
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE PROCEDURE updated_at_update();
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS subtasks
|
|
||||||
(
|
|
||||||
id SERIAL NOT NULL,
|
|
||||||
contest_id INT REFERENCES contests ON DELETE CASCADE,
|
|
||||||
testgroup_id INT REFERENCES testgroups ON DELETE CASCADE,
|
|
||||||
task_id INT REFERENCES tasks ON DELETE CASCADE,
|
|
||||||
UNIQUE (contest_id,testgroup_id),
|
|
||||||
PRIMARY KEY (id)
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX ON subtasks USING BTREE (id);
|
|
||||||
CREATE INDEX ON subtasks USING BTREE (contest_id);
|
|
||||||
CREATE INDEX ON subtasks USING BTREE (testgroup_id);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS participants
|
CREATE TABLE IF NOT EXISTS participants
|
||||||
(
|
(
|
||||||
id serial NOT NULL,
|
id serial NOT NULL,
|
||||||
user_id INT NOT NULL,
|
user_id integer NOT NULL,
|
||||||
contest_id INT REFERENCES contests ON DELETE CASCADE,
|
contest_id integer NOT NULL REFERENCES contests (id),
|
||||||
name varchar(200) NOT NULL,
|
name varchar(255) NOT NULL,
|
||||||
|
created_at timestamptz NOT NULL DEFAULT now(),
|
||||||
|
updated_at timestamptz NOT NULL DEFAULT now(),
|
||||||
|
PRIMARY KEY (id),
|
||||||
UNIQUE (user_id, contest_id),
|
UNIQUE (user_id, contest_id),
|
||||||
PRIMARY KEY (id)
|
CHECK (length(name) != 0)
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE INDEX ON participants USING BTREE (id);
|
CREATE TRIGGER on_participants_update
|
||||||
CREATE INDEX ON participants USING BTREE (user_id);
|
BEFORE UPDATE
|
||||||
CREATE INDEX ON participants USING BTREE (contest_id);
|
ON participants
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE PROCEDURE updated_at_update();
|
||||||
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS solutions
|
CREATE TABLE IF NOT EXISTS solutions
|
||||||
(
|
(
|
||||||
id serial NOT NULL,
|
id serial NOT NULL,
|
||||||
participant_id INT REFERENCES participants ON DELETE CASCADE,
|
task_id integer NOT NULL REFERENCES tasks (id),
|
||||||
task_id INT REFERENCES problems ON DELETE CASCADE,
|
participant_id integer NOT NULL REFERENCES participants (id),
|
||||||
-- language_id INT NOT NULL REFERENCES languages ON DELETE CASCADE,
|
solution varchar(1048576) NOT NULL,
|
||||||
solution_hash CHAR(128) NOT NULL,
|
state integer NOT NULL DEFAULT 1,
|
||||||
result INT NOT NULL,
|
results varchar(1000) NOT NULL,
|
||||||
score INT NOT NULL,
|
score integer NOT NULL,
|
||||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
penalty integer NOT NULL,
|
||||||
|
total_score integer NOT NULL,
|
||||||
|
language integer NOT NULL,
|
||||||
|
updated_at timestamptz NOT NULL DEFAULT now(),
|
||||||
|
created_at timestamptz NOT NULL DEFAULT now(),
|
||||||
PRIMARY KEY (id)
|
PRIMARY KEY (id)
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE INDEX ON solutions USING BTREE (id);
|
CREATE TRIGGER on_solutions_update
|
||||||
-- CREATE INDEX ON solutions USING BTREE (id,participant_id,task_id,language_id); FIXME
|
BEFORE UPDATE
|
||||||
|
ON solutions
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE PROCEDURE updated_at_update();
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS best_solutions
|
||||||
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS tests
|
|
||||||
(
|
(
|
||||||
id serial NOT NULL,
|
id serial NOT NULL,
|
||||||
--problem_id INT NOT NULL,
|
participant_id integer NOT NULL REFERENCES participants (id),
|
||||||
testgroup_id INT REFERENCES testgroups ON DELETE CASCADE,
|
task_id integer NOT NULL REFERENCES tasks (id),
|
||||||
|
solution_id integer NOT NULL REFERENCES solutions (id),
|
||||||
PRIMARY KEY (id)
|
best_total_score integer NOT NULL,
|
||||||
|
created_at timestamptz NOT NULL DEFAULT now(),
|
||||||
|
updated_at timestamptz NOT NULL DEFAULT now(),
|
||||||
|
PRIMARY KEY (id),
|
||||||
|
UNIQUE (participant_id, task_id)
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE INDEX ON tests USING BTREE (id);
|
CREATE TRIGGER on_best_solutions_update
|
||||||
CREATE INDEX ON tests USING BTREE (testgroup_id);
|
BEFORE UPDATE
|
||||||
|
ON best_solutions
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE PROCEDURE updated_at_update();
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS subtaskruns
|
|
||||||
(
|
|
||||||
id serial NOT NULL,
|
|
||||||
subtask_id INT REFERENCES subtasks ON DELETE CASCADE,
|
|
||||||
solution_id INT REFERENCES solutions ON DELETE CASCADE,
|
|
||||||
result INT NOT NULL,
|
|
||||||
score INT NOT NULL,
|
|
||||||
|
|
||||||
UNIQUE (subtask_id,solution_id),
|
|
||||||
PRIMARY KEY (id)
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX ON subtaskruns USING BTREE (id);
|
|
||||||
CREATE INDEX ON subtaskruns USING BTREE (result);
|
|
||||||
CREATE INDEX ON subtaskruns USING BTREE (solution_id);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS testruns
|
|
||||||
(
|
|
||||||
id serial NOT NULL,
|
|
||||||
test_id INT REFERENCES tests ON DELETE CASCADE,
|
|
||||||
--solution_id INT REFERENCES solutions ON DELETE CASCADE,
|
|
||||||
subtaskrun_id INT REFERENCES subtaskruns ON DELETE CASCADE,
|
|
||||||
result INT NOT NULL,
|
|
||||||
|
|
||||||
PRIMARY KEY (id)
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX ON testruns USING BTREE (id);
|
|
||||||
CREATE INDEX ON testruns USING BTREE (result);
|
|
||||||
CREATE INDEX ON testruns USING BTREE (subtaskrun_id);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS participant_subtask
|
|
||||||
(
|
|
||||||
participant_id INT REFERENCES participants ON DELETE CASCADE,
|
|
||||||
subtask_id INT REFERENCES subtasks ON DELETE CASCADE,
|
|
||||||
--result INT NOT NULL,
|
|
||||||
best_score INT NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS participant_task
|
|
||||||
(
|
|
||||||
participant_id INT REFERENCES participants ON DELETE CASCADE,
|
|
||||||
task_id INT REFERENCES tasks ON DELETE CASCADE,
|
|
||||||
--result INT NOT NULL,
|
|
||||||
best_score INT NOT NULL,
|
|
||||||
penalty INT NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
CREATE FUNCTION on_new_participant() RETURNS TRIGGER AS
|
|
||||||
$$ BEGIN
|
|
||||||
--RAISE NOTICE 'NEW.ID:%, NEW.contest_id:%', NEW.id,NEW.contest_id;
|
|
||||||
INSERT INTO participant_task (participant_id,task_id,best_score,penalty) SELECT NEW.id,id,0,0 FROM tasks WHERE contest_id=NEW.contest_id;
|
|
||||||
INSERT INTO participant_subtask (participant_id,subtask_id,best_score) SELECT NEW.id,id,0 FROM subtasks WHERE contest_id=NEW.contest_id;
|
|
||||||
RETURN NEW;
|
|
||||||
END; $$ LANGUAGE plpgsql;
|
|
||||||
|
|
||||||
CREATE TRIGGER new_participant_trg AFTER INSERT ON participants FOR EACH ROW EXECUTE FUNCTION on_new_participant();
|
|
||||||
|
|
||||||
CREATE FUNCTION on_new_task() RETURNS TRIGGER AS
|
|
||||||
$$ BEGIN
|
|
||||||
INSERT INTO participant_task (participant_id,task_id,best_score,penalty) SELECT id,NEW.id,0,0 FROM participants WHERE contest_id=NEW.contest_id;
|
|
||||||
INSERT INTO subtasks(contest_id,testgroup_id,task_id) SELECT NEW.contest_id,id,NEW.id FROM testgroups WHERE problem_id = NEW.problem_id;
|
|
||||||
RETURN NEW;
|
|
||||||
END; $$ LANGUAGE plpgsql;
|
|
||||||
|
|
||||||
CREATE TRIGGER new_task_trg AFTER INSERT ON tasks FOR EACH ROW EXECUTE FUNCTION on_new_task();
|
|
||||||
|
|
||||||
CREATE FUNCTION on_new_subtask() RETURNS TRIGGER AS
|
|
||||||
$$ BEGIN
|
|
||||||
INSERT INTO participant_subtask (participant_id,subtask_id,best_score) SELECT id,NEW.id,0 FROM participants WHERE contest_id=NEW.contest_id;
|
|
||||||
RETURN NEW;
|
|
||||||
END; $$ LANGUAGE plpgsql;
|
|
||||||
|
|
||||||
CREATE TRIGGER new_subtask_trg AFTER INSERT ON subtasks FOR EACH ROW EXECUTE FUNCTION on_new_subtask();
|
|
||||||
|
|
||||||
|
|
||||||
CREATE FUNCTION on_new_solution() RETURNS TRIGGER
|
|
||||||
LANGUAGE plpgsql AS $$
|
|
||||||
BEGIN
|
|
||||||
INSERT INTO subtaskruns (subtask_id,solution_id,result,score) (SELECT id,NEW.id,0,0 FROM subtasks WHERE task_id = NEW.task_id);
|
|
||||||
--INSERT INTO testruns (test_id,subtaskrun_id,result) (SELECT id,str.id,0 FROM tests WHERE testgroup_id IN (SELECT testgroup_id FROM subtasks WHERE id IN (SELECT subtask_id FROM subtaskruns AS str WHERE solution_id=NEW.id)));
|
|
||||||
RETURN NEW;
|
|
||||||
END;
|
|
||||||
$$;
|
|
||||||
|
|
||||||
CREATE TRIGGER on_new_solution_trg AFTER INSERT ON solutions FOR EACH ROW EXECUTE FUNCTION on_new_solution();
|
|
||||||
|
|
||||||
CREATE FUNCTION on_new_subtaskrun() RETURNS TRIGGER
|
|
||||||
LANGUAGE plpgsql AS $$
|
|
||||||
BEGIN
|
|
||||||
INSERT INTO testruns (test_id,subtaskrun_id,result) (SELECT id,NEW.id,0 FROM tests WHERE testgroup_id IN (SELECT testgroup_id FROM subtasks WHERE id=NEW.subtask_id));
|
|
||||||
RETURN NEW;
|
|
||||||
END;
|
|
||||||
$$;
|
|
||||||
|
|
||||||
CREATE TRIGGER on_new_subtaskrun_trg AFTER INSERT ON subtaskruns FOR EACH ROW EXECUTE FUNCTION on_new_subtaskrun();
|
|
||||||
|
|
||||||
CREATE FUNCTION updated_at_update() RETURNS TRIGGER
|
CREATE FUNCTION updated_at_update() RETURNS TRIGGER
|
||||||
LANGUAGE plpgsql AS
|
LANGUAGE plpgsql AS
|
||||||
|
@ -269,31 +130,21 @@ BEGIN
|
||||||
END;
|
END;
|
||||||
$$;
|
$$;
|
||||||
|
|
||||||
|
|
||||||
--CREATE TRIGGER languages_upd_trg BEFORE UPDATE ON languages FOR EACH ROW EXECUTE FUNCTION updated_at_update();
|
|
||||||
CREATE TRIGGER problems_upd_trg BEFORE UPDATE ON problems FOR EACH ROW EXECUTE FUNCTION updated_at_update();
|
|
||||||
CREATE TRIGGER contests_upd_trg BEFORE UPDATE ON contests FOR EACH ROW EXECUTE FUNCTION updated_at_update();
|
|
||||||
-- +goose StatementEnd
|
-- +goose StatementEnd
|
||||||
|
|
||||||
-- +goose Down
|
-- +goose Down
|
||||||
-- +goose StatementBegin
|
-- +goose StatementBegin
|
||||||
DROP FUNCTION IF EXISTS updated_at_update CASCADE;
|
DROP TRIGGER IF EXISTS on_problems_update ON problems;
|
||||||
DROP FUNCTION IF EXISTS on_new_participant CASCADE;
|
DROP TABLE IF EXISTS problems;
|
||||||
DROP FUNCTION IF EXISTS on_new_task CASCADE;
|
DROP TRIGGER IF EXISTS on_contests_update ON contests;
|
||||||
DROP FUNCTION IF EXISTS on_new_subtask CASCADE;
|
DROP TABLE IF EXISTS contests;
|
||||||
DROP FUNCTION IF EXISTS on_new_solution CASCADE;
|
DROP TRIGGER IF EXISTS on_tasks_update ON tasks;
|
||||||
DROP FUNCTION IF EXISTS on_new_subtaskrun CASCADE;
|
DROP TABLE IF EXISTS tasks;
|
||||||
DROP TABLE IF EXISTS tests CASCADE;
|
DROP TRIGGER IF EXISTS on_participants_update ON participants;
|
||||||
DROP TABLE IF EXISTS solutions CASCADE;
|
DROP TABLE IF EXISTS participants;
|
||||||
--DROP TABLE IF EXISTS languages CASCADE;
|
DROP TRIGGER IF EXISTS on_solutions_update ON solutions;
|
||||||
DROP TABLE IF EXISTS testgroups CASCADE;
|
DROP TABLE IF EXISTS solutions;
|
||||||
DROP TABLE IF EXISTS testruns CASCADE;
|
DROP TRIGGER IF EXISTS on_best_solutions_update ON best_solutions;
|
||||||
DROP TABLE IF EXISTS subtaskruns CASCADE;
|
DROP TABLE IF EXISTS best_solutions;
|
||||||
DROP TABLE IF EXISTS problems CASCADE;
|
DROP FUNCTION updated_at_update();
|
||||||
DROP TABLE IF EXISTS contests CASCADE;
|
|
||||||
DROP TABLE IF EXISTS tasks CASCADE;
|
|
||||||
DROP TABLE IF EXISTS subtasks CASCADE;
|
|
||||||
DROP TABLE IF EXISTS participants CASCADE;
|
|
||||||
DROP TABLE IF EXISTS participant_task CASCADE;
|
|
||||||
DROP TABLE IF EXISTS participant_subtask CASCADE;
|
|
||||||
-- +goose StatementEnd
|
-- +goose StatementEnd
|
||||||
|
|
1
pkg/external/kafka/kafka.go
vendored
1
pkg/external/kafka/kafka.go
vendored
|
@ -1 +0,0 @@
|
||||||
package kafka
|
|
1
pkg/external/ms-auth/client.go
vendored
1
pkg/external/ms-auth/client.go
vendored
|
@ -1 +0,0 @@
|
||||||
package ms_auth
|
|
1
pkg/external/rabbitmq/client.go
vendored
Normal file
1
pkg/external/rabbitmq/client.go
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
package rabbitmq
|
Loading…
Reference in a new issue