From be25404852f045e819299b59e5c241ded753424d Mon Sep 17 00:00:00 2001 From: Vyacheslav1557 Date: Sun, 13 Oct 2024 19:01:36 +0500 Subject: [PATCH] feat: --- go.mod | 7 +- go.sum | 11 +- internal/contests/delivery.go | 9 + internal/contests/delivery/grpc/handlers.go | 50 +++ internal/contests/pg_repository.go | 5 +- internal/contests/repository/participants.go | 79 ---- internal/contests/repository/pg_repository.go | 39 +- .../contests/repository/pg_repository_test.go | 58 +++ internal/contests/repository/solution.go | 114 ------ internal/contests/repository/task.go | 80 ---- internal/contests/usecase.go | 5 +- .../usecase/{all.rego => contest.rego} | 0 internal/contests/usecase/participants.go | 57 --- internal/contests/usecase/permission.go | 39 -- internal/contests/usecase/solution.go | 57 --- internal/contests/usecase/task.go | 41 -- internal/contests/usecase/usecase.go | 46 +-- internal/languages/delivery.go | 4 - internal/languages/delivery/grpc/handlers.go | 1 - internal/languages/pg_repository.go | 10 - .../languages/repository/pg_repository.go | 28 -- internal/languages/usecase.go | 10 - internal/languages/usecase/usecase.go | 25 -- internal/models/contest.go | 10 + internal/models/language.go | 13 + internal/models/participant.go | 8 + internal/models/problem.go | 15 + internal/models/result.go | 30 ++ internal/models/role.go | 52 +++ internal/models/solution.go | 14 + internal/models/task.go | 9 + internal/models/user.go | 9 + internal/problems/delivery.go | 2 +- internal/problems/delivery/grpc/handlers.go | 145 +------ internal/problems/pg_repository.go | 5 +- internal/problems/repository/pg_repository.go | 77 +--- .../problems/repository/pg_repository_test.go | 58 +++ internal/problems/usecase.go | 13 +- internal/problems/usecase/all.rego | 44 --- internal/problems/usecase/policy_agent.go | 67 ++++ .../usecase/problem.rego} | 0 internal/problems/usecase/usecase.go | 92 +---- internal/tester/usecase/permission.go | 2 +- migrations/20240727123308_initial.sql | 357 +++++------------- pkg/external/aws/{aws.go => client.go} | 0 pkg/external/kafka/kafka.go | 1 - pkg/external/ms-auth/client.go | 1 - pkg/external/pandoc/{pandoc.go => client.go} | 0 .../postgres/{postgres.go => client.go} | 0 pkg/external/rabbitmq/client.go | 1 + pkg/external/valkey/{valkey.go => client.go} | 0 51 files changed, 606 insertions(+), 1194 deletions(-) delete mode 100644 internal/contests/repository/participants.go create mode 100644 internal/contests/repository/pg_repository_test.go delete mode 100644 internal/contests/repository/solution.go delete mode 100644 internal/contests/repository/task.go rename internal/contests/usecase/{all.rego => contest.rego} (100%) delete mode 100644 internal/contests/usecase/participants.go delete mode 100644 internal/contests/usecase/permission.go delete mode 100644 internal/contests/usecase/solution.go delete mode 100644 internal/contests/usecase/task.go delete mode 100644 internal/languages/delivery.go delete mode 100644 internal/languages/delivery/grpc/handlers.go delete mode 100644 internal/languages/pg_repository.go delete mode 100644 internal/languages/repository/pg_repository.go delete mode 100644 internal/languages/usecase.go delete mode 100644 internal/languages/usecase/usecase.go create mode 100644 internal/models/contest.go create mode 100644 internal/models/language.go create mode 100644 internal/models/participant.go create mode 100644 internal/models/problem.go create mode 100644 internal/models/result.go create mode 100644 internal/models/role.go create mode 100644 internal/models/solution.go create mode 100644 internal/models/task.go create mode 100644 internal/models/user.go create mode 100644 internal/problems/repository/pg_repository_test.go delete mode 100644 internal/problems/usecase/all.rego create mode 100644 internal/problems/usecase/policy_agent.go rename internal/{languages/usecase/all.rego => problems/usecase/problem.rego} (100%) rename pkg/external/aws/{aws.go => client.go} (100%) delete mode 100644 pkg/external/kafka/kafka.go delete mode 100644 pkg/external/ms-auth/client.go rename pkg/external/pandoc/{pandoc.go => client.go} (100%) rename pkg/external/postgres/{postgres.go => client.go} (100%) create mode 100644 pkg/external/rabbitmq/client.go rename pkg/external/valkey/{valkey.go => client.go} (100%) diff --git a/go.mod b/go.mod index 41b084a..d59166c 100644 --- a/go.mod +++ b/go.mod @@ -5,20 +5,23 @@ go 1.21.3 toolchain go1.22.0 require ( + github.com/DATA-DOG/go-sqlmock v1.5.2 github.com/ilyakaznacheev/cleanenv v1.5.0 github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438 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 google.golang.org/grpc v1.65.0 google.golang.org/protobuf v1.34.2 ) require ( - git.sch9.ru/new_gate/models v0.0.1 // indirect github.com/OneOfOne/xxhash v1.2.8 // indirect github.com/agnivade/levenshtein v1.1.1 // indirect github.com/beorn7/perks v1.0.1 // 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-logr/logr v1.4.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/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // 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_model v0.6.1 // 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/sirupsen/logrus v1.9.3 // 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/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/yashtewari/glob-intersection v0.2.0 // indirect diff --git a/go.sum b/go.sum index e264c7b..fa54feb 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,11 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= 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/go.mod h1:oLUfkN1hwclYTj8Y+br5ShZHLB4y2StRwf+mhc33NNc= github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= 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/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= 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/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= 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/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= 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/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM= 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/go.mod h1:aqKlHc8E2VAAylYE9x09zJYr/fYzGX+JKne89UGqFzk= 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= 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/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/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 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/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 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/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= diff --git a/internal/contests/delivery.go b/internal/contests/delivery.go index b1d0091..bbb81f3 100644 --- a/internal/contests/delivery.go +++ b/internal/contests/delivery.go @@ -1,4 +1,13 @@ 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 { + 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) } diff --git a/internal/contests/delivery/grpc/handlers.go b/internal/contests/delivery/grpc/handlers.go index 21e034e..67141e4 100644 --- a/internal/contests/delivery/grpc/handlers.go +++ b/internal/contests/delivery/grpc/handlers.go @@ -1 +1,51 @@ 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 +} diff --git a/internal/contests/pg_repository.go b/internal/contests/pg_repository.go index 4085d03..1dc1368 100644 --- a/internal/contests/pg_repository.go +++ b/internal/contests/pg_repository.go @@ -2,12 +2,11 @@ package contests import ( "context" - "git.sch9.ru/new_gate/models" + "git.sch9.ru/new_gate/ms-tester/internal/models" ) 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) - UpdateContest(ctx context.Context, contest *models.Contest) error DeleteContest(ctx context.Context, id int32) error } diff --git a/internal/contests/repository/participants.go b/internal/contests/repository/participants.go deleted file mode 100644 index 99ab645..0000000 --- a/internal/contests/repository/participants.go +++ /dev/null @@ -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 -} diff --git a/internal/contests/repository/pg_repository.go b/internal/contests/repository/pg_repository.go index 00e214b..e3f1c82 100644 --- a/internal/contests/repository/pg_repository.go +++ b/internal/contests/repository/pg_repository.go @@ -3,7 +3,7 @@ package repository import ( "context" "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" "github.com/jackc/pgerrcode" "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) { - query := r.db.Rebind(` -INSERT INTO contests - (name) -VALUES (?) -RETURNING id -`) +const createContestQuery = "INSERT INTO contest (title) VALUES (?) RETURNING id" - rows, err := r.db.QueryxContext( - ctx, - query, - contest.Name, - ) +func (r *ContestRepository) CreateContest(ctx context.Context, title string) (int32, error) { + query := r.db.Rebind(createContestQuery) + + rows, err := r.db.QueryxContext(ctx, query, title) if err != nil { return 0, handlePgErr(err) } defer rows.Close() var id int32 - err = rows.StructScan(&id) + rows.Next() + err = rows.Scan(&id) if err != nil { return 0, handlePgErr(err) } return id, nil - } +const readContestByIdQuery = "SELECT * from contest WHERE id=? LIMIT 1" + func (r *ContestRepository) ReadContestById(ctx context.Context, id int32) (*models.Contest, error) { 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) if err != nil { return nil, handlePgErr(err) @@ -61,18 +56,10 @@ func (r *ContestRepository) ReadContestById(ctx context.Context, id int32) (*mod return &contest, nil } -func (r *ContestRepository) UpdateContest(ctx context.Context, contest *models.Contest) error { - 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 -} +const deleteContestQuery = "DELETE FROM contest WHERE id=?" 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) if err != nil { return handlePgErr(err) diff --git a/internal/contests/repository/pg_repository_test.go b/internal/contests/repository/pg_repository_test.go new file mode 100644 index 0000000..beee36a --- /dev/null +++ b/internal/contests/repository/pg_repository_test.go @@ -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) + }) +} diff --git a/internal/contests/repository/solution.go b/internal/contests/repository/solution.go deleted file mode 100644 index cc46339..0000000 --- a/internal/contests/repository/solution.go +++ /dev/null @@ -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 -} diff --git a/internal/contests/repository/task.go b/internal/contests/repository/task.go deleted file mode 100644 index 82e2d85..0000000 --- a/internal/contests/repository/task.go +++ /dev/null @@ -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 -} diff --git a/internal/contests/usecase.go b/internal/contests/usecase.go index e90643b..c076721 100644 --- a/internal/contests/usecase.go +++ b/internal/contests/usecase.go @@ -2,12 +2,11 @@ package contests import ( "context" - "git.sch9.ru/new_gate/models" + "git.sch9.ru/new_gate/ms-tester/internal/models" ) 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) - UpdateContest(ctx context.Context, contest *models.Contest) error DeleteContest(ctx context.Context, id int32) error } diff --git a/internal/contests/usecase/all.rego b/internal/contests/usecase/contest.rego similarity index 100% rename from internal/contests/usecase/all.rego rename to internal/contests/usecase/contest.rego diff --git a/internal/contests/usecase/participants.go b/internal/contests/usecase/participants.go deleted file mode 100644 index 9ada21d..0000000 --- a/internal/contests/usecase/participants.go +++ /dev/null @@ -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) -} diff --git a/internal/contests/usecase/permission.go b/internal/contests/usecase/permission.go deleted file mode 100644 index ae3855f..0000000 --- a/internal/contests/usecase/permission.go +++ /dev/null @@ -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) -} diff --git a/internal/contests/usecase/solution.go b/internal/contests/usecase/solution.go deleted file mode 100644 index 482e403..0000000 --- a/internal/contests/usecase/solution.go +++ /dev/null @@ -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) -} diff --git a/internal/contests/usecase/task.go b/internal/contests/usecase/task.go deleted file mode 100644 index 719fb44..0000000 --- a/internal/contests/usecase/task.go +++ /dev/null @@ -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) -} diff --git a/internal/contests/usecase/usecase.go b/internal/contests/usecase/usecase.go index 6b0e586..8c82c96 100644 --- a/internal/contests/usecase/usecase.go +++ b/internal/contests/usecase/usecase.go @@ -4,48 +4,28 @@ import ( "context" "git.sch9.ru/new_gate/models" "git.sch9.ru/new_gate/ms-tester/internal/contests" - "git.sch9.ru/new_gate/ms-tester/pkg/utils" ) -type ContestService struct { - contestStorage contests.ContestRepository - permissionService IPermissionService +type ContestUseCase struct { + contestRepo contests.ContestRepository } -func NewContestService( - contestStorage ContestStorage, - permissionService IPermissionService, -) *ContestService { - return &ContestService{ - contestStorage: contestStorage, - permissionService: permissionService, +func NewContestUseCase( + contestRepo contests.ContestRepository, +) *ContestUseCase { + return &ContestUseCase{ + contestRepo: contestRepo, } } -func (service *ContestService) CreateContest(ctx context.Context, contest *models.Contest) (int32, error) { - if !service.permissionService.Allowed(ctx, extractUser(ctx), "create") { - return 0, utils.ServiceError(nil, utils.ErrNoPermission, "permission denied") - } - return service.contestStorage.CreateContest(ctx, contest) +func (uc *ContestUseCase) CreateContest(ctx context.Context, title string) (int32, error) { + return uc.contestRepo.CreateContest(ctx, title) } -func (service *ContestService) ReadContestById(ctx context.Context, id int32) (*models.Contest, error) { - if !service.permissionService.Allowed(ctx, extractUser(ctx), "read") { - return nil, utils.ServiceError(nil, utils.ErrNoPermission, "permission denied") - } - return service.contestStorage.ReadContestById(ctx, id) +func (uc *ContestUseCase) ReadContestById(ctx context.Context, id int32) (*models.Contest, error) { + return uc.contestRepo.ReadContestById(ctx, id) } -func (service *ContestService) UpdateContest(ctx context.Context, contest *models.Contest) error { - if !service.permissionService.Allowed(ctx, extractUser(ctx), "update") { - 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) +func (uc *ContestUseCase) DeleteContest(ctx context.Context, id int32) error { + return uc.contestRepo.DeleteContest(ctx, id) } diff --git a/internal/languages/delivery.go b/internal/languages/delivery.go deleted file mode 100644 index e86683a..0000000 --- a/internal/languages/delivery.go +++ /dev/null @@ -1,4 +0,0 @@ -package languages - -type languageHandlers interface { -} diff --git a/internal/languages/delivery/grpc/handlers.go b/internal/languages/delivery/grpc/handlers.go deleted file mode 100644 index 21e034e..0000000 --- a/internal/languages/delivery/grpc/handlers.go +++ /dev/null @@ -1 +0,0 @@ -package grpc diff --git a/internal/languages/pg_repository.go b/internal/languages/pg_repository.go deleted file mode 100644 index b966134..0000000 --- a/internal/languages/pg_repository.go +++ /dev/null @@ -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) -} diff --git a/internal/languages/repository/pg_repository.go b/internal/languages/repository/pg_repository.go deleted file mode 100644 index add08ea..0000000 --- a/internal/languages/repository/pg_repository.go +++ /dev/null @@ -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 -} diff --git a/internal/languages/usecase.go b/internal/languages/usecase.go deleted file mode 100644 index b6eab08..0000000 --- a/internal/languages/usecase.go +++ /dev/null @@ -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) -} diff --git a/internal/languages/usecase/usecase.go b/internal/languages/usecase/usecase.go deleted file mode 100644 index a8a322a..0000000 --- a/internal/languages/usecase/usecase.go +++ /dev/null @@ -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) -} diff --git a/internal/models/contest.go b/internal/models/contest.go new file mode 100644 index 0000000..366e416 --- /dev/null +++ b/internal/models/contest.go @@ -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"` +} diff --git a/internal/models/language.go b/internal/models/language.go new file mode 100644 index 0000000..62ccced --- /dev/null +++ b/internal/models/language.go @@ -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"}}, +} diff --git a/internal/models/participant.go b/internal/models/participant.go new file mode 100644 index 0000000..8db6814 --- /dev/null +++ b/internal/models/participant.go @@ -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"` +} diff --git a/internal/models/problem.go b/internal/models/problem.go new file mode 100644 index 0000000..5708170 --- /dev/null +++ b/internal/models/problem.go @@ -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"` +} diff --git a/internal/models/result.go b/internal/models/result.go new file mode 100644 index 0000000..d6852b2 --- /dev/null +++ b/internal/models/result.go @@ -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 +} diff --git a/internal/models/role.go b/internal/models/role.go new file mode 100644 index 0000000..d978058 --- /dev/null +++ b/internal/models/role.go @@ -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 +} diff --git a/internal/models/solution.go b/internal/models/solution.go new file mode 100644 index 0000000..4064a5a --- /dev/null +++ b/internal/models/solution.go @@ -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"` +} diff --git a/internal/models/task.go b/internal/models/task.go new file mode 100644 index 0000000..4fb57a4 --- /dev/null +++ b/internal/models/task.go @@ -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"` +} diff --git a/internal/models/user.go b/internal/models/user.go new file mode 100644 index 0000000..357600c --- /dev/null +++ b/internal/models/user.go @@ -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"` +} diff --git a/internal/problems/delivery.go b/internal/problems/delivery.go index 0e36f9e..5a14019 100644 --- a/internal/problems/delivery.go +++ b/internal/problems/delivery.go @@ -7,7 +7,7 @@ import ( ) 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) DeleteProblem(ctx context.Context, req *problemv1.DeleteProblemRequest) (*emptypb.Empty, error) } diff --git a/internal/problems/delivery/grpc/handlers.go b/internal/problems/delivery/grpc/handlers.go index 1aef2dc..989462a 100644 --- a/internal/problems/delivery/grpc/handlers.go +++ b/internal/problems/delivery/grpc/handlers.go @@ -2,15 +2,11 @@ package grpc import ( "context" - "git.sch9.ru/new_gate/models" "git.sch9.ru/new_gate/ms-tester/internal/problems" problemv1 "git.sch9.ru/new_gate/ms-tester/pkg/go/gen/proto/problem/v1" "git.sch9.ru/new_gate/ms-tester/pkg/utils" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" + "google.golang.org/grpc" "google.golang.org/protobuf/types/known/emptypb" - "io" - "os" ) type problemHandlers struct { @@ -19,105 +15,18 @@ type problemHandlers struct { problemUC problems.ProblemUseCase } -func (h *problemHandlers) CreateProblem(server problemv1.ProblemService_CreateProblemServer) error { - ctx := server.Context() +func NewProblemHandlers(gserver *grpc.Server, problemUC problems.ProblemUseCase) { + handlers := &problemHandlers{problemUC: problemUC} - if err := h.problemUC.CanCreateProblem(ctx); err != nil { - return err - } - - req, err := server.Recv() // receive problem - if err != nil { - return utils.TransportError(err, utils.ErrBadInput, "can't receive problem") - } - problem := req.GetProblem() - 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 + problemv1.RegisterProblemServiceServer(gserver, handlers) } -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? +func (h *problemHandlers) CreateProblem(ctx context.Context, req *problemv1.CreateProblemRequest) (*problemv1.CreateProblemResponse, error) { + id, err := h.problemUC.CreateProblem(ctx, req.GetTitle()) if err != nil { - return err + return nil, 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 + return &problemv1.CreateProblemResponse{Id: id}, nil } func (h *problemHandlers) ReadProblem(ctx context.Context, req *problemv1.ReadProblemRequest) (*problemv1.ReadProblemResponse, error) { @@ -127,39 +36,19 @@ func (h *problemHandlers) ReadProblem(ctx context.Context, req *problemv1.ReadPr } return &problemv1.ReadProblemResponse{ Problem: &problemv1.ReadProblemResponse_Problem{ - Id: *problem.Id, - Name: *problem.Name, - Description: *problem.Description, - TimeLimit: *problem.TimeLimit, - MemoryLimit: *problem.MemoryLimit, - CreatedAt: utils.TimestampP(problem.CreatedAt), - UpdatedAt: utils.TimestampP(problem.UpdatedAt), + Id: *problem.Id, + Title: *problem.Title, + Content: *problem.Content, + TimeLimit: *problem.TimeLimit, + MemoryLimit: *problem.MemoryLimit, + TestingStrategy: *problem.TestingStrategy, + TestingOrder: *problem.TestingOrder, + CreatedAt: utils.TimestampP(problem.CreatedAt), + UpdatedAt: utils.TimestampP(problem.UpdatedAt), }, }, 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) { err := h.problemUC.DeleteProblem(ctx, req.GetId()) if err != nil { diff --git a/internal/problems/pg_repository.go b/internal/problems/pg_repository.go index f4e1358..7063c38 100644 --- a/internal/problems/pg_repository.go +++ b/internal/problems/pg_repository.go @@ -2,12 +2,11 @@ package problems import ( "context" - "git.sch9.ru/new_gate/models" + "git.sch9.ru/new_gate/ms-tester/internal/models" ) 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) - UpdateProblem(ctx context.Context, problem *models.Problem) error DeleteProblem(ctx context.Context, id int32) error } diff --git a/internal/problems/repository/pg_repository.go b/internal/problems/repository/pg_repository.go index c68552b..27cf3d9 100644 --- a/internal/problems/repository/pg_repository.go +++ b/internal/problems/repository/pg_repository.go @@ -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) { - tx, err := storage.db.Beginx() +const createProblemQuery = "INSERT INTO problems (title) VALUES (?) RETURNING id" + +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 { 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() var id int32 - err = rows.StructScan(&id) + rows.Next() + err = rows.Scan(&id) if err != nil { return 0, handlePgErr(err) } @@ -83,28 +43,23 @@ RETURNING id 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 - query := storage.db.Rebind("SELECT * from problems WHERE id=? LIMIT 1") - err := storage.db.GetContext(ctx, &problem, query, id) + query := r.db.Rebind(readProblemQuery) + err := r.db.GetContext(ctx, &problem, query, id) if err != nil { return nil, handlePgErr(err) } return &problem, nil } -func (storage *ProblemRepository) UpdateProblem(ctx context.Context, problem *models.Problem) error { - 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 -} +const deleteProblemQuery = "DELETE FROM problems WHERE id=?" -func (storage *ProblemRepository) DeleteProblem(ctx context.Context, id int32) error { - query := storage.db.Rebind("DELETE FROM problems WHERE id=?") - _, err := storage.db.ExecContext(ctx, query, id) +func (r *ProblemRepository) DeleteProblem(ctx context.Context, id int32) error { + query := r.db.Rebind(deleteProblemQuery) + _, err := r.db.ExecContext(ctx, query, id) if err != nil { return handlePgErr(err) } diff --git a/internal/problems/repository/pg_repository_test.go b/internal/problems/repository/pg_repository_test.go new file mode 100644 index 0000000..76d67cf --- /dev/null +++ b/internal/problems/repository/pg_repository_test.go @@ -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) + }) +} diff --git a/internal/problems/usecase.go b/internal/problems/usecase.go index 162f23b..f97a3e3 100644 --- a/internal/problems/usecase.go +++ b/internal/problems/usecase.go @@ -2,16 +2,17 @@ package problems import ( "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 - CanReadProblemById(ctx context.Context) error - CanUpdateProblem(ctx context.Context) error + CanReadProblem(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) - UpdateProblem(ctx context.Context, problem *models.Problem) error DeleteProblem(ctx context.Context, id int32) error } diff --git a/internal/problems/usecase/all.rego b/internal/problems/usecase/all.rego deleted file mode 100644 index 8525c6a..0000000 --- a/internal/problems/usecase/all.rego +++ /dev/null @@ -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 -} diff --git a/internal/problems/usecase/policy_agent.go b/internal/problems/usecase/policy_agent.go new file mode 100644 index 0000000..b993da8 --- /dev/null +++ b/internal/problems/usecase/policy_agent.go @@ -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 +//} diff --git a/internal/languages/usecase/all.rego b/internal/problems/usecase/problem.rego similarity index 100% rename from internal/languages/usecase/all.rego rename to internal/problems/usecase/problem.rego diff --git a/internal/problems/usecase/usecase.go b/internal/problems/usecase/usecase.go index 074705c..4ba9231 100644 --- a/internal/problems/usecase/usecase.go +++ b/internal/problems/usecase/usecase.go @@ -2,100 +2,34 @@ package usecase import ( "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/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 { - problemStorage ProblemStorage - pandocClient pandoc.PandocClient - permissionService IPermissionService + problemRepo problems.ProblemPostgresRepository + pandocClient pandoc.PandocClient } func NewProblemUseCase( - problemStorage ProblemStorage, + problemRepo problems.ProblemPostgresRepository, pandocClient pandoc.PandocClient, - permissionService IPermissionService, ) *ProblemUseCase { return &ProblemUseCase{ - problemStorage: problemStorage, - pandocClient: pandocClient, - permissionService: permissionService, + problemRepo: problemRepo, + pandocClient: pandocClient, } } -func extractUser(ctx context.Context) *models.User { - return ctx.Value("user").(*models.User) +func (u *ProblemUseCase) CreateProblem(ctx context.Context, title string) (int32, error) { + return u.problemRepo.CreateProblem(ctx, title) } -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 (u *ProblemUseCase) ReadProblemById(ctx context.Context, id int32) (*models.Problem, error) { + return u.problemRepo.ReadProblemById(ctx, id) } -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 -} - -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) +func (u *ProblemUseCase) DeleteProblem(ctx context.Context, id int32) error { + return u.problemRepo.DeleteProblem(ctx, id) } diff --git a/internal/tester/usecase/permission.go b/internal/tester/usecase/permission.go index ae3855f..e2c0f82 100644 --- a/internal/tester/usecase/permission.go +++ b/internal/tester/usecase/permission.go @@ -13,7 +13,7 @@ type PermissionService struct { func NewPermissionService() *PermissionService { query, err := rego.New( rego.Query("allow = data.problem.rbac.allow"), - rego.Load([]string{"./opa/all.rego"}, nil), + rego.Load([]string{"./opa/problem.rego"}, nil), ).PrepareForEval(context.TODO()) if err != nil { diff --git a/migrations/20240727123308_initial.sql b/migrations/20240727123308_initial.sql index 5d534d8..eec43d1 100644 --- a/migrations/20240727123308_initial.sql +++ b/migrations/20240727123308_initial.sql @@ -1,264 +1,125 @@ -- +goose Up -- +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 ( - id serial NOT NULL, - name VARCHAR(300) NOT NULL, - description TEXT NOT NULL, - time_limit INT NOT NULL, - memory_limit INT NOT NULL, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - - PRIMARY KEY(id) + id serial NOT NULL, + title varchar(255) NOT NULL, + content varchar(65536) NOT NULL DEFAULT '', + time_limit integer NOT NULL DEFAULT 1000, + memory_limit integer NOT NULL DEFAULT 65536, + testing_strategy integer NOT NULL DEFAULT 1, + testing_order varchar(1024) NOT NULL DEFAULT '', + created_at timestamptz NOT NULL DEFAULT now(), + 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 ( - id serial NOT NULL, - name VARCHAR(300) NOT NULL, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - - PRIMARY KEY(id) + id serial NOT NULL, + title varchar(255) NOT NULL, + created_at timestamptz NOT NULL DEFAULT now(), + updated_at timestamptz NOT NULL DEFAULT now(), + 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 ( - 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) + id serial NOT NULL, + problem_id integer NOT NULL REFERENCES problems (id), + contest_id integer NOT NULL REFERENCES contests (id), + position integer NOT NULL, + 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 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, - problem_id INT REFERENCES problems ON DELETE CASCADE, - testing_strategy INT NOT NULL, - - PRIMARY KEY (id) -); - -CREATE INDEX ON testgroups USING BTREE (id); -CREATE INDEX ON testgroups USING BTREE (problem_id); - - - - -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 TRIGGER on_tasks_update + BEFORE UPDATE + ON tasks + FOR EACH ROW +EXECUTE PROCEDURE updated_at_update(); CREATE TABLE IF NOT EXISTS participants ( - id serial NOT NULL, - user_id INT NOT NULL, - contest_id INT REFERENCES contests ON DELETE CASCADE, - name varchar(200) NOT NULL, - - UNIQUE (user_id,contest_id), - PRIMARY KEY (id) + id serial NOT NULL, + user_id integer NOT NULL, + contest_id integer NOT NULL REFERENCES contests (id), + 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), + CHECK (length(name) != 0) ); -CREATE INDEX ON participants USING BTREE (id); -CREATE INDEX ON participants USING BTREE (user_id); -CREATE INDEX ON participants USING BTREE (contest_id); - - - +CREATE TRIGGER on_participants_update + BEFORE UPDATE + ON participants + FOR EACH ROW +EXECUTE PROCEDURE updated_at_update(); CREATE TABLE IF NOT EXISTS solutions ( - id serial NOT NULL, - participant_id INT REFERENCES participants ON DELETE CASCADE, - task_id INT REFERENCES problems ON DELETE CASCADE, --- language_id INT NOT NULL REFERENCES languages ON DELETE CASCADE, - solution_hash CHAR(128) NOT NULL, - result INT NOT NULL, - score INT NOT NULL, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - + id serial NOT NULL, + task_id integer NOT NULL REFERENCES tasks (id), + participant_id integer NOT NULL REFERENCES participants (id), + solution varchar(1048576) NOT NULL, + state integer NOT NULL DEFAULT 1, + results varchar(1000) NOT NULL, + score integer NOT NULL, + 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) ); -CREATE INDEX ON solutions USING BTREE (id); --- CREATE INDEX ON solutions USING BTREE (id,participant_id,task_id,language_id); FIXME +CREATE TRIGGER on_solutions_update + BEFORE UPDATE + ON solutions + FOR EACH ROW +EXECUTE PROCEDURE updated_at_update(); - - - -CREATE TABLE IF NOT EXISTS tests +CREATE TABLE IF NOT EXISTS best_solutions ( - id serial NOT NULL, - --problem_id INT NOT NULL, - testgroup_id INT REFERENCES testgroups ON DELETE CASCADE, - - PRIMARY KEY (id) + id serial NOT NULL, + participant_id integer NOT NULL REFERENCES participants (id), + task_id integer NOT NULL REFERENCES tasks (id), + solution_id integer NOT NULL REFERENCES solutions (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 INDEX ON tests USING BTREE (testgroup_id); - - - - -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 TRIGGER on_best_solutions_update + BEFORE UPDATE + ON best_solutions + FOR EACH ROW +EXECUTE PROCEDURE updated_at_update(); CREATE FUNCTION updated_at_update() RETURNS TRIGGER LANGUAGE plpgsql AS @@ -269,31 +130,21 @@ BEGIN 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 Down -- +goose StatementBegin -DROP FUNCTION IF EXISTS updated_at_update CASCADE; -DROP FUNCTION IF EXISTS on_new_participant CASCADE; -DROP FUNCTION IF EXISTS on_new_task CASCADE; -DROP FUNCTION IF EXISTS on_new_subtask CASCADE; -DROP FUNCTION IF EXISTS on_new_solution CASCADE; -DROP FUNCTION IF EXISTS on_new_subtaskrun CASCADE; -DROP TABLE IF EXISTS tests CASCADE; -DROP TABLE IF EXISTS solutions CASCADE; ---DROP TABLE IF EXISTS languages CASCADE; -DROP TABLE IF EXISTS testgroups CASCADE; -DROP TABLE IF EXISTS testruns CASCADE; -DROP TABLE IF EXISTS subtaskruns CASCADE; -DROP TABLE IF EXISTS problems CASCADE; -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; +DROP TRIGGER IF EXISTS on_problems_update ON problems; +DROP TABLE IF EXISTS problems; +DROP TRIGGER IF EXISTS on_contests_update ON contests; +DROP TABLE IF EXISTS contests; +DROP TRIGGER IF EXISTS on_tasks_update ON tasks; +DROP TABLE IF EXISTS tasks; +DROP TRIGGER IF EXISTS on_participants_update ON participants; +DROP TABLE IF EXISTS participants; +DROP TRIGGER IF EXISTS on_solutions_update ON solutions; +DROP TABLE IF EXISTS solutions; +DROP TRIGGER IF EXISTS on_best_solutions_update ON best_solutions; +DROP TABLE IF EXISTS best_solutions; +DROP FUNCTION updated_at_update(); -- +goose StatementEnd diff --git a/pkg/external/aws/aws.go b/pkg/external/aws/client.go similarity index 100% rename from pkg/external/aws/aws.go rename to pkg/external/aws/client.go diff --git a/pkg/external/kafka/kafka.go b/pkg/external/kafka/kafka.go deleted file mode 100644 index 82b3441..0000000 --- a/pkg/external/kafka/kafka.go +++ /dev/null @@ -1 +0,0 @@ -package kafka diff --git a/pkg/external/ms-auth/client.go b/pkg/external/ms-auth/client.go deleted file mode 100644 index a8d771d..0000000 --- a/pkg/external/ms-auth/client.go +++ /dev/null @@ -1 +0,0 @@ -package ms_auth diff --git a/pkg/external/pandoc/pandoc.go b/pkg/external/pandoc/client.go similarity index 100% rename from pkg/external/pandoc/pandoc.go rename to pkg/external/pandoc/client.go diff --git a/pkg/external/postgres/postgres.go b/pkg/external/postgres/client.go similarity index 100% rename from pkg/external/postgres/postgres.go rename to pkg/external/postgres/client.go diff --git a/pkg/external/rabbitmq/client.go b/pkg/external/rabbitmq/client.go new file mode 100644 index 0000000..a4e247e --- /dev/null +++ b/pkg/external/rabbitmq/client.go @@ -0,0 +1 @@ +package rabbitmq diff --git a/pkg/external/valkey/valkey.go b/pkg/external/valkey/client.go similarity index 100% rename from pkg/external/valkey/valkey.go rename to pkg/external/valkey/client.go