copy some gorm from data-service

This commit is contained in:
Quaternions 2024-11-26 13:36:40 -08:00
parent ca5779a56b
commit f0bd6ba53a
11 changed files with 631 additions and 0 deletions

132
internal/controller/maps.go Normal file
View File

@ -0,0 +1,132 @@
package controller
import (
"context"
"fmt"
"git.itzana.me/strafesnet/data-service/internal/datastore"
"git.itzana.me/strafesnet/data-service/internal/model"
"git.itzana.me/strafesnet/go-grpc/maps"
"github.com/pkg/errors"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"time"
)
type Maps struct {
*maps.UnimplementedMapsServiceServer
Store datastore.Datastore
}
func (m Maps) Get(ctx context.Context, message *maps.IdMessage) (*maps.MapResponse, error) {
item, err := m.Store.Maps().Get(ctx, message.GetID())
if err != nil {
if err == datastore.ErrNotExist {
return nil, status.Error(codes.NotFound, "map does not exit")
}
return nil, status.Error(codes.Internal, errors.Wrap(err, "failed to get map:").Error())
}
return &maps.MapResponse{
ID: item.ID,
DisplayName: item.DisplayName,
Creator: item.Creator,
GameID: item.GameID,
Date: item.Date.Unix(),
}, nil
}
func (m Maps) GetList(ctx context.Context, list *maps.IdList) (*maps.MapList, error) {
items, err := m.Store.Maps().GetList(ctx, list.ID)
if err != nil {
return nil, status.Error(codes.Internal, errors.Wrap(err, "failed to get maps").Error())
}
var resp maps.MapList
for i := 0; i < len(items); i++ {
resp.Maps = append(resp.Maps, &maps.MapResponse{
ID: items[i].ID,
DisplayName: items[i].DisplayName,
Creator: items[i].Creator,
GameID: items[i].GameID,
Date: items[i].Date.Unix(),
})
}
return &resp, nil
}
func (m Maps) Update(ctx context.Context, request *maps.MapRequest) (*maps.NullResponse, error) {
updates := datastore.Optional()
updates.AddNotNil("display_name", request.DisplayName)
updates.AddNotNil("creator", request.Creator)
updates.AddNotNil("game_id", request.GameID)
if request.Date != nil {
updates.AddNotNil("date", time.Unix(request.GetDate(), 0))
}
if err := m.Store.Maps().Update(ctx, request.GetID(), updates); err != nil {
if err == datastore.ErrNotExist {
return nil, status.Error(codes.NotFound, "map does not exit")
}
return nil, status.Error(codes.Internal, errors.Wrap(err, "failed to update map:").Error())
}
return &maps.NullResponse{}, nil
}
func (m Maps) Create(ctx context.Context, request *maps.MapRequest) (*maps.IdMessage, error) {
item, err := m.Store.Maps().Create(ctx, model.Map{
ID: request.GetID(),
DisplayName: request.GetDisplayName(),
Creator: request.GetCreator(),
GameID: request.GetGameID(),
Date: time.Unix(request.GetDate(), 0),
})
if err != nil {
return nil, status.Error(codes.Internal, errors.Wrap(err, "failed to create map:").Error())
}
return &maps.IdMessage{ID: item.ID}, nil
}
func (m Maps) Delete(ctx context.Context, message *maps.IdMessage) (*maps.NullResponse, error) {
if err := m.Store.Maps().Delete(ctx, message.GetID()); err != nil {
if err == datastore.ErrNotExist {
return nil, status.Error(codes.NotFound, "map does not exit")
}
return nil, status.Error(codes.Internal, errors.Wrap(err, "failed to delete map:").Error())
}
return &maps.NullResponse{}, nil
}
func (m Maps) List(ctx context.Context, request *maps.ListRequest) (*maps.MapList, error) {
filter := datastore.Optional()
fmt.Println(request)
if request.Filter != nil {
filter.AddNotNil("display_name", request.GetFilter().DisplayName)
filter.AddNotNil("creator", request.GetFilter().Creator)
filter.AddNotNil("game_id", request.GetFilter().GameID)
}
items, err := m.Store.Maps().List(ctx, filter, model.Page{
Number: request.GetPage().GetNumber(),
Size: request.GetPage().GetSize(),
})
if err != nil {
return nil, status.Error(codes.Internal, errors.Wrap(err, "failed to get maps:").Error())
}
var resp maps.MapList
for i := 0; i < len(items); i++ {
resp.Maps = append(resp.Maps, &maps.MapResponse{
ID: items[i].ID,
DisplayName: items[i].DisplayName,
Creator: items[i].Creator,
GameID: items[i].GameID,
Date: items[i].Date.Unix(),
})
}
return &resp, nil
}

View File

@ -0,0 +1,117 @@
package controller
import (
"context"
"git.itzana.me/strafesnet/data-service/internal/datastore"
"git.itzana.me/strafesnet/data-service/internal/model"
"git.itzana.me/strafesnet/go-grpc/users"
"github.com/pkg/errors"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
type Users struct {
*users.UnimplementedUsersServiceServer
Store datastore.Datastore
}
func (u Users) Get(ctx context.Context, request *users.IdMessage) (*users.UserResponse, error) {
ur, err := u.Store.Users().Get(ctx, request.ID)
if err != nil {
if err == datastore.ErrNotExist {
return nil, status.Error(codes.NotFound, err.Error())
}
return nil, status.Error(codes.Internal, errors.Wrap(err, "failed to get user").Error())
}
return &users.UserResponse{
ID: ur.ID,
Username: ur.Username,
StateID: ur.StateID,
}, nil
}
func (u Users) GetList(ctx context.Context, list *users.IdList) (*users.UserList, error) {
uList, err := u.Store.Users().GetList(ctx, list.ID)
if err != nil {
return nil, status.Error(codes.Internal, errors.Wrap(err, "failed to get users").Error())
}
var resp users.UserList
for i := 0; i < len(uList); i++ {
resp.Users = append(resp.Users, &users.UserResponse{
ID: uList[i].ID,
Username: uList[i].Username,
StateID: uList[i].StateID,
})
}
return &resp, nil
}
func (u Users) Update(ctx context.Context, request *users.UserRequest) (*users.NullResponse, error) {
updates := datastore.Optional()
updates.AddNotNil("state_id", request.StateID)
updates.AddNotNil("username", request.Username)
if err := u.Store.Users().Update(ctx, request.GetID(), updates); err != nil {
if err == datastore.ErrNotExist {
return nil, status.Error(codes.NotFound, err.Error())
}
return nil, status.Error(codes.Internal, errors.Wrap(err, "failed to update user").Error())
}
return &users.NullResponse{}, nil
}
func (u Users) Create(ctx context.Context, request *users.UserRequest) (*users.IdMessage, error) {
us, err := u.Store.Users().Create(ctx, model.User{
ID: request.GetID(),
Username: request.GetUsername(),
StateID: request.GetStateID(),
})
if err != nil {
return nil, status.Error(codes.Internal, errors.Wrap(err, "failed to create user").Error())
}
return &users.IdMessage{ID: us.ID}, nil
}
func (u Users) Delete(ctx context.Context, request *users.IdMessage) (*users.NullResponse, error) {
if err := u.Store.Users().Delete(ctx, request.GetID()); err != nil {
if err == datastore.ErrNotExist {
return nil, status.Error(codes.NotFound, err.Error())
}
return nil, status.Error(codes.Internal, errors.Wrap(err, "failed to delete user").Error())
}
return &users.NullResponse{}, nil
}
func (u Users) List(ctx context.Context, request *users.ListRequest) (*users.UserList, error) {
filters := datastore.Optional()
if request.Filter != nil {
filters.AddNotNil("id", request.GetFilter().ID)
filters.AddNotNil("state_id", request.GetFilter().StateID)
filters.AddNotNil("username", request.GetFilter().Username)
}
uList, err := u.Store.Users().List(ctx, filters, model.Page{
Number: request.GetPage().GetNumber(),
Size: request.GetPage().GetSize(),
})
if err != nil {
return nil, status.Error(codes.Internal, errors.Wrap(err, "failed to get filtered users").Error())
}
var uResp users.UserList
for i := 0; i < len(uList); i++ {
uResp.Users = append(uResp.Users, &users.UserResponse{
ID: uList[i].ID,
Username: uList[i].Username,
StateID: uList[i].StateID,
})
}
return &uResp, nil
}

View File

@ -0,0 +1,94 @@
package datastore
import (
"context"
"errors"
"time"
"git.itzana.me/strafesnet/data-service/internal/model"
)
var (
ErrNotExist = errors.New("resource does not exist")
)
type Datastore interface {
Times() Times
Users() Users
Bots() Bots
Maps() Maps
Events() Events
Servers() Servers
Transactions() Transactions
Ranks() Ranks
}
type Times interface {
Get(ctx context.Context, id int64) (model.Time, error)
Create(ctx context.Context, time model.Time) (model.Time, error)
Update(ctx context.Context, id int64, values OptionalMap) error
Delete(ctx context.Context, id int64) error
List(ctx context.Context, filters OptionalMap, blacklisted bool, page model.Page, sort uint32) (int64, []model.Time, error)
Rank(ctx context.Context, id int64) (int64, error)
DistinctStylePairs(ctx context.Context) ([]model.Time, error)
}
type Users interface {
Get(ctx context.Context, id int64) (model.User, error)
GetList(ctx context.Context, id []int64) ([]model.User, error)
Create(ctx context.Context, user model.User) (model.User, error)
Update(ctx context.Context, id int64, values OptionalMap) error
Delete(ctx context.Context, id int64) error
List(ctx context.Context, filters OptionalMap, page model.Page) ([]model.User, error)
}
type Bots interface {
Get(ctx context.Context, id int64) (model.Bot, error)
GetList(ctx context.Context, id []int64) ([]model.Bot, error)
Create(ctx context.Context, bot model.Bot) (model.Bot, error)
Update(ctx context.Context, id int64, values OptionalMap) error
Delete(ctx context.Context, id int64) error
List(ctx context.Context, filters OptionalMap, page model.Page) ([]model.Bot, error)
}
type Maps interface {
Get(ctx context.Context, id int64) (model.Map, error)
GetList(ctx context.Context, id []int64) ([]model.Map, error)
Create(ctx context.Context, time model.Map) (model.Map, error)
Update(ctx context.Context, id int64, values OptionalMap) error
Delete(ctx context.Context, id int64) error
List(ctx context.Context, filters OptionalMap, page model.Page) ([]model.Map, error)
}
type Events interface {
Latest(ctx context.Context, date int64, page model.Page) ([]model.Event, error)
Create(ctx context.Context, event model.Event) (model.Event, error)
Clean(ctx context.Context) error
}
type Servers interface {
Get(ctx context.Context, id string) (model.Server, error)
Create(ctx context.Context, server model.Server) (model.Server, error)
Update(ctx context.Context, id string, values OptionalMap) error
Delete(ctx context.Context, id string) error
DeleteByLastUpdated(ctx context.Context, date time.Time) error
List(ctx context.Context, filters OptionalMap, page model.Page) ([]model.Server, error)
}
type Transactions interface {
Balance(ctx context.Context, user int64) (int64, error)
Get(ctx context.Context, id string) (model.Transaction, error)
Create(ctx context.Context, transaction model.Transaction) (model.Transaction, error)
Update(ctx context.Context, id string, values OptionalMap) error
Delete(ctx context.Context, id string) error
List(ctx context.Context, filters OptionalMap, page model.Page) ([]model.Transaction, error)
}
type Ranks interface {
Delete(ctx context.Context, id int64) error
Get(ctx context.Context, user int64, style, game, mode int32, state []int32) (model.Rank, error)
List(ctx context.Context, style, game, mode int32, sort int64, state []int32, page model.Page) ([]model.Rank, error)
UpdateRankCalc(ctx context.Context) error
UpdateAll(ctx context.Context, style, game, mode int32) error
UpdateUsers(ctx context.Context, style, game, mode int32, users []int) error
}

View File

@ -0,0 +1,28 @@
package datastore
import "reflect"
type OptionalMap struct {
filter map[string]interface{}
}
func Optional() OptionalMap {
return OptionalMap{filter: map[string]interface{}{}}
}
func (q OptionalMap) Add(column string, value interface{}) OptionalMap {
q.filter[column] = value
return q
}
func (q OptionalMap) AddNotNil(column string, value interface{}) OptionalMap {
if value == nil || (reflect.ValueOf(value).Kind() == reflect.Ptr && reflect.ValueOf(value).IsNil()) {
return q
}
q.Add(column, value)
return q
}
func (q OptionalMap) Map() map[string]interface{} {
return q.filter
}

View File

@ -0,0 +1,48 @@
package gormstore
import (
"fmt"
"os"
"time"
"git.itzana.me/strafesnet/data-service/internal/datastore"
"git.itzana.me/strafesnet/data-service/internal/model"
"git.itzana.me/strafesnet/utils/logger"
"github.com/eko/gocache/lib/v4/cache"
gocache_store "github.com/eko/gocache/store/go_cache/v4"
gocache "github.com/patrickmn/go-cache"
log "github.com/sirupsen/logrus"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
func New(migrate bool) (datastore.Datastore, error) {
db, err := gorm.Open(postgres.Open(fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%s", os.Getenv("PG_HOST"), os.Getenv("PG_USER"), os.Getenv("PG_PASS"), os.Getenv("PG_DB"), os.Getenv("PG_PORT"))), &gorm.Config{
Logger: logger.New()})
if err != nil {
log.WithFields(log.Fields{
"PG_USER": os.Getenv("PG_USER"),
"PG_HOST": os.Getenv("PG_HOST"),
"PG_PORT": os.Getenv("PG_PORT"),
"PG_DB": os.Getenv("PG_DB"),
"error": err,
}).Errorln("failed to connect to database")
return nil, err
}
sqlDB, err := db.DB()
if err != nil {
return nil, err
}
sqlDB.SetMaxIdleConns(10)
sqlDB.SetMaxOpenConns(25)
if migrate {
if err := db.AutoMigrate(&model.Time{}, &model.User{}, &model.Bot{}, &model.Map{}, &model.Event{}, &model.Server{}, &model.Transaction{}, &model.Rank{}, &model.RankCalc{}); err != nil {
log.WithField("error", err).Errorln("database migration failed")
return nil, err
}
}
return &Gormstore{db, cache.New[[]byte](gocache_store.NewGoCache(gocache.New(5*time.Minute, 10*time.Minute)))}, nil
}

View File

@ -0,0 +1,44 @@
package gormstore
import (
"git.itzana.me/strafesnet/data-service/internal/datastore"
"github.com/eko/gocache/lib/v4/cache"
"gorm.io/gorm"
)
type Gormstore struct {
db *gorm.DB
cache *cache.Cache[[]byte]
}
func (g Gormstore) Times() datastore.Times {
return &Times{db: g.db}
}
func (g Gormstore) Users() datastore.Users {
return &Users{db: g.db}
}
func (g Gormstore) Bots() datastore.Bots {
return &Bots{db: g.db}
}
func (g Gormstore) Maps() datastore.Maps {
return &Maps{db: g.db}
}
func (g Gormstore) Events() datastore.Events {
return &Events{db: g.db}
}
func (g Gormstore) Servers() datastore.Servers {
return &Servers{db: g.db}
}
func (g Gormstore) Transactions() datastore.Transactions {
return &Transactions{db: g.db}
}
func (g Gormstore) Ranks() datastore.Ranks {
return &Ranks{db: g.db, cache: g.cache}
}

View File

@ -0,0 +1,72 @@
package gormstore
import (
"context"
"git.itzana.me/strafesnet/data-service/internal/datastore"
"git.itzana.me/strafesnet/data-service/internal/model"
"gorm.io/gorm"
)
type Maps struct {
db *gorm.DB
}
func (m Maps) Get(ctx context.Context, id int64) (model.Map, error) {
var smap model.Map
if err := m.db.WithContext(ctx).First(&smap, id).Error; err != nil {
if err == gorm.ErrRecordNotFound {
return smap, datastore.ErrNotExist
}
return smap, err
}
return smap, nil
}
func (m Maps) GetList(ctx context.Context, id []int64) ([]model.Map, error) {
var mapList []model.Map
if err := m.db.WithContext(ctx).Find(&mapList, "id IN ?", id).Error; err != nil {
return mapList, err
}
return mapList, nil
}
func (m Maps) Create(ctx context.Context, smap model.Map) (model.Map, error) {
if err := m.db.WithContext(ctx).Create(&smap).Error; err != nil {
return smap, err
}
return smap, nil
}
func (m Maps) Update(ctx context.Context, id int64, values datastore.OptionalMap) error {
if err := m.db.WithContext(ctx).Model(&model.Map{}).Where("id = ?", id).Updates(values.Map()).Error; err != nil {
if err == gorm.ErrRecordNotFound {
return datastore.ErrNotExist
}
return err
}
return nil
}
func (m Maps) Delete(ctx context.Context, id int64) error {
if err := m.db.WithContext(ctx).Delete(&model.Map{}, id).Error; err != nil {
if err == gorm.ErrRecordNotFound {
return datastore.ErrNotExist
}
return err
}
return nil
}
func (m Maps) List(ctx context.Context, filters datastore.OptionalMap, page model.Page) ([]model.Map, error) {
var maps []model.Map
if err := m.db.WithContext(ctx).Where(filters.Map()).Offset(int((page.Number - 1) * page.Size)).Limit(int(page.Size)).Find(&maps).Error; err != nil {
return nil, err
}
return maps, nil
}

View File

@ -0,0 +1,72 @@
package gormstore
import (
"context"
"git.itzana.me/strafesnet/data-service/internal/datastore"
"git.itzana.me/strafesnet/data-service/internal/model"
"gorm.io/gorm"
)
type Users struct {
db *gorm.DB
}
func (u Users) Get(ctx context.Context, id int64) (model.User, error) {
var user model.User
if err := u.db.WithContext(ctx).First(&user, id).Error; err != nil {
if err == gorm.ErrRecordNotFound {
return user, datastore.ErrNotExist
}
return user, err
}
return user, nil
}
func (u Users) GetList(ctx context.Context, id []int64) ([]model.User, error) {
var user []model.User
if err := u.db.WithContext(ctx).Find(&user, "id IN ?", id).Error; err != nil {
return user, err
}
return user, nil
}
func (u Users) Create(ctx context.Context, user model.User) (model.User, error) {
if err := u.db.WithContext(ctx).Create(&user).Error; err != nil {
return user, err
}
return user, nil
}
func (u Users) Update(ctx context.Context, id int64, values datastore.OptionalMap) error {
if err := u.db.WithContext(ctx).Model(&model.User{}).Where("id = ?", id).Updates(values.Map()).Error; err != nil {
if err == gorm.ErrRecordNotFound {
return datastore.ErrNotExist
}
return err
}
return nil
}
func (u Users) Delete(ctx context.Context, id int64) error {
if err := u.db.WithContext(ctx).Delete(&model.User{}, id).Error; err != nil {
if err == gorm.ErrRecordNotFound {
return datastore.ErrNotExist
}
return err
}
return nil
}
func (u Users) List(ctx context.Context, filters datastore.OptionalMap, page model.Page) ([]model.User, error) {
var users []model.User
if err := u.db.WithContext(ctx).Where(filters.Map()).Offset(int((page.Number - 1) * page.Size)).Limit(int(page.Size)).Find(&users).Error; err != nil {
return nil, err
}
return users, nil
}

11
internal/model/map.go Normal file
View File

@ -0,0 +1,11 @@
package model
import "time"
type Map struct {
ID int64
DisplayName string
Creator string
GameID int32
Date time.Time
}

6
internal/model/page.go Normal file
View File

@ -0,0 +1,6 @@
package model
type Page struct {
Number int32
Size int32
}

7
internal/model/user.go Normal file
View File

@ -0,0 +1,7 @@
package model
type User struct {
ID int64
Username string `gorm:"not null"`
StateID int32 `gorm:"not null;default:0"`
}