diff --git a/.gitignore b/.gitignore index 1851331..12443b3 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ racing/racing *.out .idea .vscode -*/*.exe \ No newline at end of file +*/*.exe +*/**/*_testdata \ No newline at end of file diff --git a/README.md b/README.md index f582e43..1c0b363 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Please treat the services provided as if they would live in a real-world environ ### Directory Structure - `api`: A basic REST gateway, forwarding requests onto service(s). -- `racing`: A very bare-bones racing service. +- [`racing`](racing/README.md): A very bare-bones racing service. ``` entain/ diff --git a/api/go.mod b/api/go.mod index 9ff97b2..dccf52d 100644 --- a/api/go.mod +++ b/api/go.mod @@ -1,4 +1,4 @@ -module git.neds.sh/matty/entain/api +module github.com/ashleyjlive/entain/api go 1.16 diff --git a/api/main.go b/api/main.go index 5eb0368..a751648 100644 --- a/api/main.go +++ b/api/main.go @@ -5,8 +5,8 @@ import ( "flag" "net/http" + "github.com/ashleyjlive/entain/api/proto/racing" "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" - "git.neds.sh/matty/entain/api/proto/racing" log "github.com/sirupsen/logrus" "google.golang.org/grpc" ) diff --git a/api/proto/racing/racing.pb.go b/api/proto/racing/racing.pb.go index 532aa22..f47b04c 100644 --- a/api/proto/racing/racing.pb.go +++ b/api/proto/racing/racing.pb.go @@ -125,6 +125,7 @@ type ListRacesRequestFilter struct { unknownFields protoimpl.UnknownFields MeetingIds []int64 `protobuf:"varint,1,rep,packed,name=meeting_ids,json=meetingIds,proto3" json:"meeting_ids,omitempty"` + Visible *bool `protobuf:"varint,2,opt,name=visible,proto3,oneof" json:"visible,omitempty"` } func (x *ListRacesRequestFilter) Reset() { @@ -166,6 +167,13 @@ func (x *ListRacesRequestFilter) GetMeetingIds() []int64 { return nil } +func (x *ListRacesRequestFilter) GetVisible() bool { + if x != nil && x.Visible != nil { + return *x.Visible + } + return false +} + // A race resource. type Race struct { state protoimpl.MessageState @@ -277,31 +285,33 @@ var file_racing_racing_proto_rawDesc = []byte{ 0x52, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x22, 0x0a, 0x05, 0x72, 0x61, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x72, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x2e, 0x52, 0x61, 0x63, 0x65, 0x52, 0x05, 0x72, 0x61, 0x63, 0x65, - 0x73, 0x22, 0x39, 0x0a, 0x16, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, + 0x73, 0x22, 0x64, 0x0a, 0x16, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x6d, 0x65, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x03, - 0x52, 0x0a, 0x6d, 0x65, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x49, 0x64, 0x73, 0x22, 0xcb, 0x01, 0x0a, - 0x04, 0x52, 0x61, 0x63, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x65, 0x65, 0x74, 0x69, 0x6e, 0x67, - 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x6d, 0x65, 0x65, 0x74, 0x69, - 0x6e, 0x67, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x6e, 0x75, 0x6d, 0x62, - 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, - 0x12, 0x18, 0x0a, 0x07, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x07, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x12, 0x4e, 0x0a, 0x15, 0x61, 0x64, - 0x76, 0x65, 0x72, 0x74, 0x69, 0x73, 0x65, 0x64, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, - 0x69, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, - 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x13, 0x61, 0x64, 0x76, 0x65, 0x72, 0x74, 0x69, 0x73, 0x65, - 0x64, 0x53, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x32, 0x65, 0x0a, 0x06, 0x52, 0x61, - 0x63, 0x69, 0x6e, 0x67, 0x12, 0x5b, 0x0a, 0x09, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x61, 0x63, 0x65, - 0x73, 0x12, 0x18, 0x2e, 0x72, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, - 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x72, 0x61, - 0x63, 0x69, 0x6e, 0x67, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x19, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x13, 0x22, 0x0e, - 0x2f, 0x76, 0x31, 0x2f, 0x6c, 0x69, 0x73, 0x74, 0x2d, 0x72, 0x61, 0x63, 0x65, 0x73, 0x3a, 0x01, - 0x2a, 0x42, 0x09, 0x5a, 0x07, 0x2f, 0x72, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x62, 0x06, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x33, + 0x52, 0x0a, 0x6d, 0x65, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x49, 0x64, 0x73, 0x12, 0x1d, 0x0a, 0x07, + 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, + 0x07, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x88, 0x01, 0x01, 0x42, 0x0a, 0x0a, 0x08, 0x5f, + 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x22, 0xcb, 0x01, 0x0a, 0x04, 0x52, 0x61, 0x63, 0x65, + 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x02, 0x69, 0x64, + 0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x65, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x6d, 0x65, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x49, 0x64, 0x12, + 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, + 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x76, + 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x76, 0x69, + 0x73, 0x69, 0x62, 0x6c, 0x65, 0x12, 0x4e, 0x0a, 0x15, 0x61, 0x64, 0x76, 0x65, 0x72, 0x74, 0x69, + 0x73, 0x65, 0x64, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, + 0x52, 0x13, 0x61, 0x64, 0x76, 0x65, 0x72, 0x74, 0x69, 0x73, 0x65, 0x64, 0x53, 0x74, 0x61, 0x72, + 0x74, 0x54, 0x69, 0x6d, 0x65, 0x32, 0x65, 0x0a, 0x06, 0x52, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x12, + 0x5b, 0x0a, 0x09, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x61, 0x63, 0x65, 0x73, 0x12, 0x18, 0x2e, 0x72, + 0x61, 0x63, 0x69, 0x6e, 0x67, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x61, 0x63, 0x65, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x72, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x2e, + 0x4c, 0x69, 0x73, 0x74, 0x52, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x19, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x13, 0x22, 0x0e, 0x2f, 0x76, 0x31, 0x2f, 0x6c, + 0x69, 0x73, 0x74, 0x2d, 0x72, 0x61, 0x63, 0x65, 0x73, 0x3a, 0x01, 0x2a, 0x42, 0x09, 0x5a, 0x07, + 0x2f, 0x72, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -392,6 +402,7 @@ func file_racing_racing_proto_init() { } } } + file_racing_racing_proto_msgTypes[2].OneofWrappers = []interface{}{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ diff --git a/api/proto/racing/racing.proto b/api/proto/racing/racing.proto index 416faab..ab51a4e 100644 --- a/api/proto/racing/racing.proto +++ b/api/proto/racing/racing.proto @@ -28,6 +28,8 @@ message ListRacesResponse { // Filter for listing races. message ListRacesRequestFilter { repeated int64 meeting_ids = 1; + // Returns Races that have `visible` set to the requested value. + optional bool visible = 2; } /* Resources */ diff --git a/racing/README.md b/racing/README.md new file mode 100644 index 0000000..5733aa9 --- /dev/null +++ b/racing/README.md @@ -0,0 +1,30 @@ +# Racing Service +The racing service provides the ability to access racing events over a gRPC server. + +## Building +To build a executable simply call + + $ go build + +This will place a racing executable in the root directory. + +## Command Line + +You may provide optional command line arguments to the executable. +Currently, you may configure: +- `grpc-endpoint` - This is the endpoint that the front facing API server will speak to. +- `db_path` - This is the path of the database that the service will utilise. + +For example: + + $ ./racing --grpc-endpoint=localhost:8080 --db_path:/foo/bar/db.db + +## API + +Please [see](proto/README.md) the documentation for the protobuf definitions. + +## Testing + +To test individual packages: + + $ go test ./... \ No newline at end of file diff --git a/racing/db/db.go b/racing/db/db.go index 0980815..272fdf3 100644 --- a/racing/db/db.go +++ b/racing/db/db.go @@ -1,19 +1,30 @@ package db import ( + "database/sql" "time" + "github.com/ashleyjlive/entain/racing/proto/racing" + "github.com/golang/protobuf/ptypes" "syreclabs.com/go/faker" ) -func (r *racesRepo) seed() error { +func (r *racesRepo) init_tbl() error { statement, err := r.db.Prepare(`CREATE TABLE IF NOT EXISTS races (id INTEGER PRIMARY KEY, meeting_id INTEGER, name TEXT, number INTEGER, visible INTEGER, advertised_start_time DATETIME)`) if err == nil { _, err = statement.Exec() + return err + } else { + return err } +} +func (r *racesRepo) seed() error { + var ( + err error + ) for i := 1; i <= 100; i++ { - statement, err = r.db.Prepare(`INSERT OR IGNORE INTO races(id, meeting_id, name, number, visible, advertised_start_time) VALUES (?,?,?,?,?,?)`) + statement, err := r.db.Prepare(`INSERT OR IGNORE INTO races(id, meeting_id, name, number, visible, advertised_start_time) VALUES (?,?,?,?,?,?)`) if err == nil { _, err = statement.Exec( i, @@ -25,6 +36,61 @@ func (r *racesRepo) seed() error { ) } } + return err +} + +func (r *racesRepo) listAll() ([]*racing.Race, error) { + var races []*racing.Race + rows, err := + r.db.Query( + "SELECT id, meeting_id, name, number, visible, advertised_start_time FROM races") + if err != nil { + return nil, err + } + for rows.Next() { + var race racing.Race + var advertisedStart time.Time + + if err := rows.Scan(&race.Id, &race.MeetingId, &race.Name, &race.Number, &race.Visible, &advertisedStart); err != nil { + if err == sql.ErrNoRows { + return nil, nil + } + + return nil, err + } + ts, err := ptypes.TimestampProto(advertisedStart) + if err != nil { + return nil, err + } + + race.AdvertisedStartTime = ts + + races = append(races, &race) + } + return races, nil +} + +func (r *racesRepo) insert(race *racing.Race) error { + var statement *sql.Stmt + ts, err := ptypes.Timestamp(race.AdvertisedStartTime) + if err != nil { + return err + } + statement, err = r.db.Prepare(`INSERT INTO races(id, meeting_id, name, number, visible, advertised_start_time) VALUES (?,?,?,?,?,?)`) + if err == nil { + _, err = statement.Exec( + &race.Id, + &race.MeetingId, + &race.Name, + &race.Number, + &race.Visible, + ts, + ) + } + return err +} +func (r *racesRepo) clear() error { + _, err := r.db.Exec("DELETE FROM races") return err } diff --git a/racing/db/races.go b/racing/db/races.go index df14a14..81e607f 100644 --- a/racing/db/races.go +++ b/racing/db/races.go @@ -2,22 +2,25 @@ package db import ( "database/sql" - "github.com/golang/protobuf/ptypes" - _ "github.com/mattn/go-sqlite3" "strings" "sync" "time" - "git.neds.sh/matty/entain/racing/proto/racing" + "github.com/golang/protobuf/ptypes" + _ "github.com/mattn/go-sqlite3" + + "github.com/ashleyjlive/entain/racing/proto/racing" ) // RacesRepo provides repository access to races. type RacesRepo interface { // Init will initialise our races repository. - Init() error - + Init(bool) error + Clear() error + InsertRace(*racing.Race) error // List will return a list of races. List(filter *racing.ListRacesRequestFilter) ([]*racing.Race, error) + ListAll() ([]*racing.Race, error) } type racesRepo struct { @@ -31,17 +34,32 @@ func NewRacesRepo(db *sql.DB) RacesRepo { } // Init prepares the race repository dummy data. -func (r *racesRepo) Init() error { +func (r *racesRepo) Init(seed bool) error { var err error r.init.Do(func() { - // For test/example purposes, we seed the DB with some dummy races. - err = r.seed() + if seed { + if err = r.seed(); err != nil { + err = r.init_tbl() + } + } else { + err = r.init_tbl() + } }) return err } +// Clears all data in the races repository. +func (r *racesRepo) Clear() error { + return r.clear() +} + +// Allows insertions of a race into the repository. +func (r *racesRepo) InsertRace(race *racing.Race) error { + return r.insert(race) +} + func (r *racesRepo) List(filter *racing.ListRacesRequestFilter) ([]*racing.Race, error) { var ( err error @@ -61,6 +79,10 @@ func (r *racesRepo) List(filter *racing.ListRacesRequestFilter) ([]*racing.Race, return r.scanRaces(rows) } +func (r *racesRepo) ListAll() ([]*racing.Race, error) { + return r.listAll() +} + func (r *racesRepo) applyFilter(query string, filter *racing.ListRacesRequestFilter) (string, []interface{}) { var ( clauses []string @@ -71,6 +93,13 @@ func (r *racesRepo) applyFilter(query string, filter *racing.ListRacesRequestFil return query, args } + if filter.Visible != nil { + // Visibility is optional. If nil, then allow non filtering of + // visibility. + clauses = append(clauses, "visible = ?") + args = append(args, filter.Visible) + } + if len(filter.MeetingIds) > 0 { clauses = append(clauses, "meeting_id IN ("+strings.Repeat("?,", len(filter.MeetingIds)-1)+"?)") diff --git a/racing/db/races_test.go b/racing/db/races_test.go new file mode 100644 index 0000000..9292baf --- /dev/null +++ b/racing/db/races_test.go @@ -0,0 +1,218 @@ +package db_test + +import ( + "database/sql" + "os" + "path/filepath" + "strconv" + "testing" + "time" + + "github.com/ashleyjlive/entain/racing/db" + "github.com/ashleyjlive/entain/racing/proto/racing" + "github.com/golang/protobuf/ptypes" + "syreclabs.com/go/faker" +) + +func TestRepo(t *testing.T) { + racingDB, err := GetTestDB("races", "TestRepo") + if err != nil { + t.Fatalf("Unexpected error %v", err) + } + + racesRepo := db.NewRacesRepo(racingDB) + if err := racesRepo.Init(false); err != nil { + t.Fatalf("Unexpected error %v", err) + } +} + +func TestNewRepo(t *testing.T) { + racingDB, err := GetTestDB("races", "TestNewRepo") + if err != nil { + t.Fatalf("Unexpected error %v", err) + } + + racesRepo := db.NewRacesRepo(racingDB) + races, err := racesRepo.ListAll() + + if len(races) != 0 { + t.Fatal("New repo contains elements") + } +} + +func TestPopulateRepo(t *testing.T) { + racingDB, err := GetTestDB("races", "TestPopulateRepo") + if err != nil { + t.Fatalf("Unexpected error %v", err) + } + + racesRepo := db.NewRacesRepo(racingDB) + _ = racesRepo.Init(false) + err = racesRepo.Clear() + if err != nil { + t.Fatalf("Unable to clear test database %v", err) + } + + if err != nil { + t.Fatalf("Unabale to convert time.") + } + races := GetRaces() + err = racesRepo.InsertRace(races[0]) + if err != nil { + t.Fatalf("Unable to insert record into database.") + } +} + +func TestPopulateAndFetchRepo(t *testing.T) { + racingDB, err := GetTestDB("races", "TestPopulateAndFetchRepo") + if err != nil { + t.Fatalf("Unexpected error %v", err) + } + + racesRepo := db.NewRacesRepo(racingDB) + _ = racesRepo.Init(false) + err = racesRepo.Clear() + if err != nil { + t.Fatalf("Unable to clear test database %v", err) + } + + races := GetRaces() + err = racesRepo.InsertRace(races[0]) + if err != nil { + t.Fatalf("Unable to insert record into database.") + } + var a []int64 + filter := racing.ListRacesRequestFilter{MeetingIds: a} + a = append(a, races[0].MeetingId) + rsp, err := racesRepo.List(&filter) + if err != nil { + t.Fatalf("Unable to retrieve races list.") + } + if len(rsp) != 1 { + t.Fatalf("Returned incorrect amount of races.") + } + if rsp[0].MeetingId != a[0] { + t.Fatalf("Incorrect item returned.") + } +} + +func TestPopulateAndFilterVisible(t *testing.T) { + racingDB, err := GetTestDB("races", "TestPopulateAndFilterVisible") + if err != nil { + t.Fatalf("Unexpected error %v", err) + } + + racesRepo := db.NewRacesRepo(racingDB) + _ = racesRepo.Init(false) + err = racesRepo.Clear() + if err != nil { + t.Fatalf("Unable to clear test database %v", err) + } + + races := GetRaces() + err = racesRepo.InsertRace(races[0]) + if err != nil { + t.Fatalf("Unable to insert record into database.") + } + visible := true + filter := racing.ListRacesRequestFilter{Visible: &visible} + rsp, err := racesRepo.List(&filter) + if err != nil { + t.Fatalf("Unable to retrieve races list.") + } + if len(rsp) == 0 && races[0].Visible == true { + t.Fatalf("Unable to retrieve visible races list.") + } +} + +func TestFetchAllEmpty(t *testing.T) { + racingDB, err := GetTestDB("races", "TestFetchAllEmpty") + if err != nil { + t.Fatalf("Failed to open testdb %v", err) + } + racesRepo := db.NewRacesRepo(racingDB) + _ = racesRepo.Init(false) + + races, err := racesRepo.ListAll() + if err != nil { + t.Fatalf("Failed to fetch all races %v.", err) + } + if len(races) > 0 { + t.Fatalf("List all request returned invalid dataset.") + } +} + +func TestFetchAll(t *testing.T) { + racingDB, err := GetTestDB("races", "TestPopulateAndFetchRepo") + if err != nil { + t.Fatalf("Failed to open testdb %v", err) + } + racesRepo := db.NewRacesRepo(racingDB) + _ = racesRepo.Init(false) + + races, _ := racesRepo.ListAll() + if len(races) > 0 { + t.Fatalf("List all request returned invalid dataset.") + } + races = GetRaces() + err = racesRepo.InsertRace(races[0]) + if err != nil { + t.Fatalf("Failed to insert race record.") + } + + outRaces, err := racesRepo.ListAll() + if err != nil { + t.Fatalf("Failed to list all list races %v", err) + } + if len(races) > 0 { + if outRaces[0].Id != races[0].Id { + t.Fatalf("Invalid race ID returned. Got %v, expected %v", outRaces[0].Id, races[0].Id) + } + } else { + t.Fatalf("Failed to fetch inserted race.") + } +} + +// Helpers // + +func GetRaces() []*racing.Race { + var ( + races []*racing.Race + ) + for i := 1; i <= 100; i++ { + meetingId, _ := strconv.Atoi(faker.Number().Between(1, 10)) + name := faker.Team().Name() + number, _ := strconv.Atoi(faker.Number().Between(1, 12)) + visible := randBool() + tm, _ := ptypes.TimestampProto( + faker.Time().Between( + time.Now().AddDate(0, 0, -1), time.Now().AddDate(0, 0, 2))) + race := + racing.Race{Id: int64(i), MeetingId: int64(meetingId), + Name: name, Number: int64(number), + Visible: visible, AdvertisedStartTime: tm} + races = append(races, &race) + } + return races +} + +func randBool() bool { + v := faker.Number().Between(0, 1) + if v == "0" { + return false + } + return true +} + +func GetTestDB(testType string, testName string) (*sql.DB, error) { + dir := filepath.Join(testType+"_testdata", testName) + err := os.RemoveAll(dir) + if err != nil { + return nil, err + } + err = os.MkdirAll(dir, os.ModeDir) + if err != nil { + return nil, err + } + return sql.Open("sqlite3", filepath.Join(dir, "test.db")) +} diff --git a/racing/db/racing.db b/racing/db/racing.db deleted file mode 100644 index d262612..0000000 Binary files a/racing/db/racing.db and /dev/null differ diff --git a/racing/go.mod b/racing/go.mod index 0a4126f..63cca47 100644 --- a/racing/go.mod +++ b/racing/go.mod @@ -1,4 +1,4 @@ -module git.neds.sh/matty/entain/racing +module github.com/ashleyjlive/entain/racing go 1.16 diff --git a/racing/main.go b/racing/main.go index 4ff3a7d..4aa4475 100644 --- a/racing/main.go +++ b/racing/main.go @@ -3,16 +3,22 @@ package main import ( "database/sql" "flag" - "git.neds.sh/matty/entain/racing/db" - "git.neds.sh/matty/entain/racing/proto/racing" - "git.neds.sh/matty/entain/racing/service" + "net" + "os" + "path/filepath" + + "github.com/ashleyjlive/entain/racing/db" + "github.com/ashleyjlive/entain/racing/proto/racing" + "github.com/ashleyjlive/entain/racing/service" log "github.com/sirupsen/logrus" "google.golang.org/grpc" - "net" ) var ( grpcEndpoint = flag.String("grpc-endpoint", "localhost:9000", "gRPC server endpoint") + dflt_db_path = filepath.Join(homeDir(), "entain", "racing", "data.db") + db_path = flag.String("db_path", dflt_db_path, "The path of the database.") + seed = flag.Bool("seed", false, "Determines if sample data is to be inserted into the database") ) func main() { @@ -23,19 +29,31 @@ func main() { } } +func homeDir() string { + osPath, err := os.UserHomeDir() + if err != nil { + panic(err) + } + return osPath +} + func run() error { conn, err := net.Listen("tcp", ":9000") if err != nil { return err } - racingDB, err := sql.Open("sqlite3", "./db/racing.db") + err = os.MkdirAll(filepath.Dir(*db_path), os.ModeDir) + if err != nil { + panic(err) + } + racingDB, err := sql.Open("sqlite3", *db_path) if err != nil { return err } racesRepo := db.NewRacesRepo(racingDB) - if err := racesRepo.Init(); err != nil { + if err := racesRepo.Init(*seed); err != nil { return err } diff --git a/racing/proto/README.md b/racing/proto/README.md new file mode 100644 index 0000000..58b5e2b --- /dev/null +++ b/racing/proto/README.md @@ -0,0 +1,25 @@ +# racing/proto +This defines the protobuf message types for the racing API. + +## Racing API + +**RPCS** + +- `ListRaces(ListRacesRequest) ListRacesResponse` + +### ListRacesRequest +- Supports a filter parameter of type `ListRacesRequestFilter`. + +### ListRacesRequestFilter +- A list of integer IDs can be supplied to perform a bulk lookup request. (optional) +- A category filter can be supplied which finds any races which match the category (optional, case-insensitive). + +### ListRacesResponse +- Contains a list of all matching races for the given lookup. + +### Race +- The ID of the race (int64). +- The unique identifier for the races meeting (int64). +- The name of the race (string). +- The visibility of the race (bool). +- The advertised start time of the race (Timestamp). \ No newline at end of file diff --git a/racing/proto/racing/racing.pb.go b/racing/proto/racing/racing.pb.go index 7f8f979..a59fc97 100644 --- a/racing/proto/racing/racing.pb.go +++ b/racing/proto/racing/racing.pb.go @@ -123,6 +123,7 @@ type ListRacesRequestFilter struct { unknownFields protoimpl.UnknownFields MeetingIds []int64 `protobuf:"varint,1,rep,packed,name=meeting_ids,json=meetingIds,proto3" json:"meeting_ids,omitempty"` + Visible *bool `protobuf:"varint,2,opt,name=visible,proto3,oneof" json:"visible,omitempty"` } func (x *ListRacesRequestFilter) Reset() { @@ -164,6 +165,13 @@ func (x *ListRacesRequestFilter) GetMeetingIds() []int64 { return nil } +func (x *ListRacesRequestFilter) GetVisible() bool { + if x != nil && x.Visible != nil { + return *x.Visible + } + return false +} + // A race resource. type Race struct { state protoimpl.MessageState @@ -273,29 +281,32 @@ var file_racing_racing_proto_rawDesc = []byte{ 0x73, 0x74, 0x52, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x22, 0x0a, 0x05, 0x72, 0x61, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x72, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x2e, 0x52, 0x61, 0x63, 0x65, 0x52, 0x05, 0x72, 0x61, - 0x63, 0x65, 0x73, 0x22, 0x39, 0x0a, 0x16, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x61, 0x63, 0x65, 0x73, + 0x63, 0x65, 0x73, 0x22, 0x64, 0x0a, 0x16, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x6d, 0x65, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, - 0x28, 0x03, 0x52, 0x0a, 0x6d, 0x65, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x49, 0x64, 0x73, 0x22, 0xcb, - 0x01, 0x0a, 0x04, 0x52, 0x61, 0x63, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x65, 0x65, 0x74, 0x69, - 0x6e, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x6d, 0x65, 0x65, - 0x74, 0x69, 0x6e, 0x67, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x6e, 0x75, - 0x6d, 0x62, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x6e, 0x75, 0x6d, 0x62, - 0x65, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x07, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x12, 0x4e, 0x0a, 0x15, - 0x61, 0x64, 0x76, 0x65, 0x72, 0x74, 0x69, 0x73, 0x65, 0x64, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, - 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, - 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, - 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x13, 0x61, 0x64, 0x76, 0x65, 0x72, 0x74, 0x69, - 0x73, 0x65, 0x64, 0x53, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x32, 0x4c, 0x0a, 0x06, - 0x52, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x12, 0x42, 0x0a, 0x09, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x61, - 0x63, 0x65, 0x73, 0x12, 0x18, 0x2e, 0x72, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x2e, 0x4c, 0x69, 0x73, - 0x74, 0x52, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, - 0x72, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x61, 0x63, 0x65, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x09, 0x5a, 0x07, 0x2f, 0x72, - 0x61, 0x63, 0x69, 0x6e, 0x67, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x28, 0x03, 0x52, 0x0a, 0x6d, 0x65, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x49, 0x64, 0x73, 0x12, 0x1d, + 0x0a, 0x07, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x48, + 0x00, 0x52, 0x07, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x88, 0x01, 0x01, 0x42, 0x0a, 0x0a, + 0x08, 0x5f, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x22, 0xcb, 0x01, 0x0a, 0x04, 0x52, 0x61, + 0x63, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x02, + 0x69, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x65, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x69, 0x64, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x6d, 0x65, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x49, + 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x18, 0x0a, + 0x07, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, + 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x12, 0x4e, 0x0a, 0x15, 0x61, 0x64, 0x76, 0x65, 0x72, + 0x74, 0x69, 0x73, 0x65, 0x64, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, + 0x6d, 0x70, 0x52, 0x13, 0x61, 0x64, 0x76, 0x65, 0x72, 0x74, 0x69, 0x73, 0x65, 0x64, 0x53, 0x74, + 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x32, 0x4c, 0x0a, 0x06, 0x52, 0x61, 0x63, 0x69, 0x6e, + 0x67, 0x12, 0x42, 0x0a, 0x09, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x61, 0x63, 0x65, 0x73, 0x12, 0x18, + 0x2e, 0x72, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x61, 0x63, 0x65, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x72, 0x61, 0x63, 0x69, 0x6e, + 0x67, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x09, 0x5a, 0x07, 0x2f, 0x72, 0x61, 0x63, 0x69, 0x6e, 0x67, + 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -386,6 +397,7 @@ func file_racing_racing_proto_init() { } } } + file_racing_racing_proto_msgTypes[2].OneofWrappers = []interface{}{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ diff --git a/racing/proto/racing/racing.proto b/racing/proto/racing/racing.proto index 0a1f183..6167103 100644 --- a/racing/proto/racing/racing.proto +++ b/racing/proto/racing/racing.proto @@ -24,6 +24,8 @@ message ListRacesResponse { // Filter for listing races. message ListRacesRequestFilter { repeated int64 meeting_ids = 1; + // Returns Races that have `visible` set to the requested value. + optional bool visible = 2; } /* Resources */ diff --git a/racing/service/racing.go b/racing/service/racing.go index 1960430..2f83505 100644 --- a/racing/service/racing.go +++ b/racing/service/racing.go @@ -1,8 +1,8 @@ package service import ( - "git.neds.sh/matty/entain/racing/db" - "git.neds.sh/matty/entain/racing/proto/racing" + "github.com/ashleyjlive/entain/racing/db" + "github.com/ashleyjlive/entain/racing/proto/racing" "golang.org/x/net/context" )