pcgamedb/db/game.go
2024-12-23 00:28:55 +08:00

948 lines
25 KiB
Go

package db
import (
"context"
"encoding/json"
"errors"
"fmt"
"pcgamedb/utils"
"regexp"
"strings"
"time"
"pcgamedb/cache"
"pcgamedb/model"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
var (
removeNoneAlphaNumeric = regexp.MustCompile(`^[A-Za-z0-9]`)
removeRepeatingSpacesRegex = regexp.MustCompile(`\s+`)
)
func GetGameItemsByAuthor(regex string) ([]*model.GameItem, error) {
var res []*model.GameItem
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
filter := bson.D{{Key: "author", Value: primitive.Regex{Pattern: regex, Options: "i"}}}
cursor, err := GameItemCollection.Find(ctx, filter)
if err != nil {
return nil, err
}
defer func(cursor *mongo.Cursor, ctx context.Context) {
_ = cursor.Close(ctx)
}(cursor, ctx)
if cursor.Err() != nil {
return nil, cursor.Err()
}
if err = cursor.All(ctx, &res); err != nil {
return nil, err
}
return res, err
}
func GetGameItemsByAuthorPagination(regex string, page int, pageSize int) ([]*model.GameItem, int, error) {
var res []*model.GameItem
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
filter := bson.D{{Key: "author", Value: primitive.Regex{Pattern: regex, Options: "i"}}}
opts := options.Find()
opts.SetSkip(int64((page - 1) * pageSize))
opts.SetLimit(int64(pageSize))
totalCount, err := GameItemCollection.CountDocuments(ctx, filter)
if err != nil {
return nil, 0, err
}
totalPage := (totalCount + int64(pageSize) - 1) / int64(pageSize)
cursor, err := GameItemCollection.Find(ctx, filter, opts)
if err != nil {
return nil, 0, err
}
defer func(cursor *mongo.Cursor, ctx context.Context) {
_ = cursor.Close(ctx)
}(cursor, ctx)
if cursor.Err() != nil {
return nil, 0, cursor.Err()
}
if err = cursor.All(ctx, &res); err != nil {
return nil, 0, err
}
return res, int(totalPage), err
}
func IsGameCrawled(flag string, author string) bool {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
filter := bson.D{
{Key: "author", Value: primitive.Regex{Pattern: author, Options: "i"}},
{Key: "update_flag", Value: flag},
}
var game model.GameItem
err := GameItemCollection.FindOne(ctx, filter).Decode(&game)
if err != nil {
if errors.Is(err, mongo.ErrNoDocuments) {
return false
}
return false
}
return true
}
func IsGameCrawledByURL(url string) bool {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
filter := bson.D{
{Key: "url", Value: url},
}
var game model.GameItem
err := GameItemCollection.FindOne(ctx, filter).Decode(&game)
if err != nil {
if errors.Is(err, mongo.ErrNoDocuments) {
return false
}
return false
}
return true
}
func SaveGameItem(item *model.GameItem) error {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if item.ID.IsZero() {
item.ID = primitive.NewObjectID()
}
if item.CreatedAt.IsZero() {
item.CreatedAt = time.Now()
}
item.UpdatedAt = time.Now()
item.Size = strings.Replace(item.Size, "gb", "GB", -1)
item.Size = strings.Replace(item.Size, "mb", "MB", -1)
filter := bson.M{"_id": item.ID}
update := bson.M{"$set": item}
opts := options.Update().SetUpsert(true)
_, err := GameItemCollection.UpdateOne(ctx, filter, update, opts)
if err != nil {
return err
}
return nil
}
func SaveGameItems(items []*model.GameItem) error {
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
operations := make([]mongo.WriteModel, len(items))
for i, item := range items {
if item.ID.IsZero() {
item.ID = primitive.NewObjectID()
}
if item.CreatedAt.IsZero() {
item.CreatedAt = time.Now()
}
item.UpdatedAt = time.Now()
item.Size = strings.Replace(item.Size, "gb", "GB", -1)
item.Size = strings.Replace(item.Size, "mb", "MB", -1)
operations[i] = mongo.NewUpdateOneModel().
SetFilter(bson.D{{Key: "_id", Value: item.ID}}).
SetUpdate(bson.D{{Key: "$set", Value: item}}).
SetUpsert(true)
}
opts := options.BulkWrite().SetOrdered(false)
_, err := GameItemCollection.BulkWrite(ctx, operations, opts)
if err != nil {
return err
}
return nil
}
func SaveGameInfo(item *model.GameInfo) error {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if item.ID.IsZero() {
item.ID = primitive.NewObjectID()
}
if item.CreatedAt.IsZero() {
item.CreatedAt = time.Now()
}
item.UpdatedAt = time.Now()
filter := bson.M{"_id": item.ID}
update := bson.M{"$set": item}
opts := options.Update().SetUpsert(true)
_, err := GameInfoCollection.UpdateOne(ctx, filter, update, opts)
if err != nil {
return err
}
return nil
}
func SaveGameInfos(items []*model.GameInfo) error {
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
operations := make([]mongo.WriteModel, len(items))
for i, item := range items {
if item.ID.IsZero() {
item.ID = primitive.NewObjectID()
}
if item.CreatedAt.IsZero() {
item.CreatedAt = time.Now()
}
item.UpdatedAt = time.Now()
operations[i] = mongo.NewUpdateOneModel().
SetFilter(bson.D{{Key: "_id", Value: item.ID}}).
SetUpdate(bson.D{{Key: "$set", Value: item}}).
SetUpsert(true)
}
opts := options.BulkWrite().SetOrdered(false)
_, err := GameInfoCollection.BulkWrite(ctx, operations, opts)
if err != nil {
return err
}
return nil
}
func GetAllGameItems() ([]*model.GameItem, error) {
var items []*model.GameItem
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
cursor, err := GameItemCollection.Find(ctx, bson.D{})
if err != nil {
return nil, err
}
defer func(cursor *mongo.Cursor, ctx context.Context) {
_ = cursor.Close(ctx)
}(cursor, ctx)
for cursor.Next(ctx) {
var game model.GameItem
if err = cursor.Decode(&game); err != nil {
return nil, err
}
items = append(items, &game)
}
if cursor.Err() != nil {
return nil, cursor.Err()
}
return items, err
}
func GetGameItemByUrl(url string) (*model.GameItem, error) {
var item model.GameItem
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
filter := bson.M{"url": url}
err := GameItemCollection.FindOne(ctx, filter).Decode(&item)
if err != nil {
if errors.Is(err, mongo.ErrNoDocuments) {
return &model.GameItem{}, nil
}
return nil, err
}
return &item, nil
}
func GetGameItemByID(id primitive.ObjectID) (*model.GameItem, error) {
var item model.GameItem
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
filter := bson.M{"_id": id}
err := GameItemCollection.FindOne(ctx, filter).Decode(&item)
if err != nil {
return nil, err
}
return &item, nil
}
func GetGameItemsByIDs(ids []primitive.ObjectID) ([]*model.GameItem, error) {
if len(ids) == 0 {
return nil, nil
}
var items []*model.GameItem
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
cursor, err := GameItemCollection.Find(ctx, bson.M{"_id": bson.M{"$in": ids}})
if err != nil {
return nil, err
}
defer func(cursor *mongo.Cursor, ctx context.Context) {
_ = cursor.Close(ctx)
}(cursor, ctx)
for cursor.Next(ctx) {
var game model.GameItem
if err = cursor.Decode(&game); err != nil {
return nil, err
}
items = append(items, &game)
}
if cursor.Err() != nil {
return nil, cursor.Err()
}
return items, err
}
// SearchGameItems page start from 1, return (items, totalPage, error)
func SearchGameInfos(name string, page int, pageSize int) ([]*model.GameInfo, int, error) {
var items []*model.GameInfo
name = removeNoneAlphaNumeric.ReplaceAllString(name, " ")
name = removeRepeatingSpacesRegex.ReplaceAllString(name, " ")
name = strings.TrimSpace(name)
name = strings.Replace(name, " ", ".*", -1)
name = fmt.Sprintf("%s.*", name)
key := fmt.Sprintf("searchGameDetails:%s:%d:%d", name, page, pageSize)
val, exist := cache.Get(key)
if exist {
var data struct {
Items []*model.GameInfo
TotalPage int
}
err := json.Unmarshal([]byte(val), &data)
if err != nil {
return nil, 0, err
}
return data.Items, data.TotalPage, nil
}
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
filter := bson.M{"$or": []interface{}{
bson.M{"name": bson.M{"$regex": primitive.Regex{Pattern: name, Options: "i"}}},
bson.M{"aliases": bson.M{"$regex": primitive.Regex{Pattern: name, Options: "i"}}},
}}
totalCount, err := GameInfoCollection.CountDocuments(ctx, filter)
if err != nil {
return nil, 0, err
}
totalPage := (totalCount + int64(pageSize) - 1) / int64(pageSize)
findOpts := options.Find().SetSkip(int64((page - 1) * pageSize)).SetLimit(int64(pageSize)).SetSort(bson.D{{Key: "name", Value: 1}})
cursor, err := GameInfoCollection.Find(ctx, filter, findOpts)
if err != nil {
return nil, 0, err
}
defer func(cursor *mongo.Cursor, ctx context.Context) {
_ = cursor.Close(ctx)
}(cursor, ctx)
for cursor.Next(ctx) {
var game model.GameInfo
if err = cursor.Decode(&game); err != nil {
return nil, 0, err
}
game.Games, err = GetGameItemsByIDs(game.GameIDs)
if err != nil {
return nil, 0, err
}
items = append(items, &game)
}
if err := cursor.Err(); err != nil {
return nil, 0, err
}
jsonBytes, err := json.Marshal(struct {
Items []*model.GameInfo
TotalPage int
}{
Items: items,
TotalPage: int(totalPage),
})
if err == nil {
_ = cache.SetWithExpire(key, string(jsonBytes), time.Minute*5)
}
return items, int(totalPage), nil
}
func GetGameInfoByPlatformID(platform string, id int) (*model.GameInfo, error) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
var filter interface{}
switch platform {
case "steam":
filter = bson.M{"steam_id": id}
case "igdb":
filter = bson.M{"igdb_id": id}
}
var game model.GameInfo
err := GameInfoCollection.FindOne(ctx, filter).Decode(&game)
if err != nil {
return nil, err
}
return &game, nil
}
func GetGameInfosByPlatformIDs(platform string, ids []int) ([]*model.GameInfo, error) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
var filter interface{}
switch platform {
case "steam":
filter = bson.M{"steam_id": bson.M{"$in": ids}}
case "igdb":
filter = bson.M{"igdb_id": bson.M{"$in": ids}}
}
var games []*model.GameInfo
cursor, err := GameInfoCollection.Find(ctx, filter)
if err != nil {
return nil, err
}
if err = cursor.All(ctx, &games); err != nil {
return nil, err
}
return games, nil
}
func HasGameItemOrganized(id primitive.ObjectID) (bool, []*model.GameInfo) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
filter := bson.M{"games": id}
var res []*model.GameInfo
cursor, err := GameInfoCollection.Find(ctx, filter)
if err != nil {
return false, nil
}
if err = cursor.All(ctx, &res); err != nil {
return false, nil
}
if len(res) == 0 {
return false, nil
}
return true, res
}
func GetUnorganizedGameItems(num int) ([]*model.GameItem, error) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
var gamesNotInDetails []*model.GameItem
pipeline := mongo.Pipeline{
bson.D{{Key: "$lookup", Value: bson.D{
{Key: "from", Value: gameInfoCollectionName},
{Key: "localField", Value: "_id"},
{Key: "foreignField", Value: "games"},
{Key: "as", Value: "gameDetail"},
}}},
}
if num != -1 && num > 0 {
pipeline = append(pipeline, bson.D{{Key: "$limit", Value: num}})
}
pipeline = append(pipeline,
bson.D{{Key: "$match", Value: bson.D{
{Key: "gameDetail", Value: bson.D{{Key: "$size", Value: 0}}},
}}},
bson.D{{Key: "$sort", Value: bson.D{{Key: "name", Value: 1}}}},
)
cursor, err := GameItemCollection.Aggregate(ctx, pipeline)
if err != nil {
return nil, err
}
defer func(cursor *mongo.Cursor, ctx context.Context) {
_ = cursor.Close(ctx)
}(cursor, ctx)
for cursor.Next(ctx) {
var game model.GameItem
if err := cursor.Decode(&game); err != nil {
return nil, err
}
gamesNotInDetails = append(gamesNotInDetails, &game)
}
if err := cursor.Err(); err != nil {
return nil, err
}
return gamesNotInDetails, nil
}
func GetGameInfoByID(id primitive.ObjectID) (*model.GameInfo, error) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
var game model.GameInfo
err := GameInfoCollection.FindOne(ctx, bson.M{"_id": id}).Decode(&game)
if err != nil {
return nil, err
}
return &game, nil
}
func DeduplicateGameItems() ([]primitive.ObjectID, error) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
type queryRes struct {
ID struct {
RawName string `bson:"raw_name"`
Download string `bson:"download"`
} `bson:"_id"`
Count int `bson:"count"`
IDs []primitive.ObjectID `bson:"ids"`
}
var res []primitive.ObjectID
pipeline := mongo.Pipeline{
bson.D{
{Key: "$group",
Value: bson.D{
{Key: "_id",
Value: bson.D{
{Key: "raw_name", Value: "$raw_name"},
{Key: "download", Value: "$download"},
},
},
{Key: "count", Value: bson.D{{Key: "$sum", Value: 1}}},
{Key: "ids", Value: bson.D{{Key: "$push", Value: "$_id"}}},
},
},
},
bson.D{{Key: "$match", Value: bson.D{{Key: "count", Value: bson.D{{Key: "$gt", Value: 1}}}}}},
}
var qres []queryRes
cursor, err := GameItemCollection.Aggregate(ctx, pipeline)
if err != nil {
return nil, err
}
if err = cursor.All(ctx, &qres); err != nil {
return nil, err
}
for _, item := range qres {
res = append(res, item.IDs[1:]...)
}
err = DeleteGameItemsByIDs(res)
if err != nil {
return nil, err
}
_, _ = CleanOrphanGamesInGameInfos()
return res, nil
}
func CleanOrphanGamesInGameInfos() (map[primitive.ObjectID]primitive.ObjectID, error) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
pipeline := mongo.Pipeline{
bson.D{{Key: "$unwind", Value: "$games"}},
bson.D{{Key: "$lookup", Value: bson.D{
{Key: "from", Value: gameItemCollectionName},
{Key: "localField", Value: "games"},
{Key: "foreignField", Value: "_id"},
{Key: "as", Value: "gameDownloads"},
}}},
bson.D{{Key: "$match", Value: bson.D{
{Key: "gameDownloads", Value: bson.D{{Key: "$size", Value: 0}}},
}}},
bson.D{{Key: "$project", Value: bson.D{
{Key: "_id", Value: 1},
{Key: "game", Value: "$games"},
}}},
}
cursor, err := GameInfoCollection.Aggregate(ctx, pipeline)
if err != nil {
return nil, err
}
qres := make([]struct {
ID primitive.ObjectID `bson:"_id"`
Game primitive.ObjectID `bson:"game"`
}, 0)
if err := cursor.All(ctx, &qres); err != nil {
return nil, err
}
var res = make(map[primitive.ObjectID]primitive.ObjectID)
for _, item := range qres {
info, err := GetGameInfoByID(item.ID)
if err != nil {
continue
}
newGames := make([]primitive.ObjectID, 0, len(info.GameIDs))
for _, id := range info.GameIDs {
if id != item.Game {
newGames = append(newGames, id)
}
}
info.GameIDs = newGames
if err := SaveGameInfo(info); err != nil {
return nil, err
}
res[item.ID] = item.Game
}
return res, nil
}
func CleanGameInfoWithEmptyGameIDs() ([]primitive.ObjectID, error) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
filter := bson.M{"games": bson.M{"$size": 0}}
cursor, err := GameInfoCollection.Find(ctx, filter)
if err != nil {
return nil, err
}
var games []*model.GameInfo
var res []primitive.ObjectID
if err = cursor.All(ctx, &games); err != nil {
return nil, err
}
for _, item := range games {
res = append(res, item.ID)
}
_, err = GameInfoCollection.DeleteMany(ctx, filter)
if err != nil {
return nil, err
}
return res, nil
}
func GetGameInfosByName(name string) ([]*model.GameInfo, error) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
name = strings.TrimSpace(name)
name = fmt.Sprintf("^%s$", name)
filter := bson.M{"name": bson.M{"$regex": primitive.Regex{Pattern: name, Options: "i"}}}
cursor, err := GameInfoCollection.Find(ctx, filter)
if err != nil {
return nil, err
}
var games []*model.GameInfo
if err = cursor.All(ctx, &games); err != nil {
return nil, err
}
return games, nil
}
func GetGameItemByRawName(name string) ([]*model.GameItem, error) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
name = strings.TrimSpace(name)
name = fmt.Sprintf("^%s$", name)
filter := bson.M{"raw_name": bson.M{"$regex": primitive.Regex{Pattern: name, Options: "i"}}}
cursor, err := GameItemCollection.Find(ctx, filter)
if err != nil {
return nil, err
}
var game []*model.GameItem
if err = cursor.All(ctx, &game); err != nil {
return nil, err
}
return game, nil
}
func GetSameNameGameInfos() (map[string][]primitive.ObjectID, error) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
pipeline := mongo.Pipeline{
bson.D{{Key: "$group", Value: bson.D{
{Key: "_id", Value: "$name"},
{Key: "count", Value: bson.D{{Key: "$sum", Value: 1}}},
{Key: "ids", Value: bson.D{{Key: "$addToSet", Value: "$_id"}}},
}}},
bson.D{{Key: "$match", Value: bson.D{{Key: "count", Value: bson.D{{Key: "$gt", Value: 1}}}}}},
}
cursor, err := GameInfoCollection.Aggregate(ctx, pipeline)
if err != nil {
return nil, err
}
data := make([]struct {
Name string `bson:"_id"`
Count int `bson:"count"`
IDs []primitive.ObjectID `bson:"ids"`
}, 0)
if err := cursor.All(ctx, &data); err != nil {
return nil, err
}
res := make(map[string][]primitive.ObjectID)
for _, item := range data {
res[item.Name] = item.IDs
}
return res, nil
}
func DeleteGameInfosByIDs(ids []primitive.ObjectID) error {
if len(ids) == 0 {
return nil
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
_, err := GameInfoCollection.DeleteMany(ctx, bson.M{"_id": bson.M{"$in": ids}})
if err != nil {
return err
}
return nil
}
func MergeGameInfosWithSameIGDBID() error {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
pipeline := mongo.Pipeline{
bson.D{{Key: "$group", Value: bson.D{
{Key: "_id", Value: "$igdb_id"},
{Key: "count", Value: bson.D{{Key: "$sum", Value: 1}}},
{Key: "docs", Value: bson.D{{Key: "$push", Value: "$$ROOT"}}},
}}},
bson.D{{Key: "$match", Value: bson.D{{Key: "count", Value: bson.D{{Key: "$gt", Value: 1}}}}}},
}
cursor, err := GameInfoCollection.Aggregate(ctx, pipeline)
if err != nil {
return err
}
type queryRes struct {
IGDBID int `bson:"_id"`
Infos []model.GameInfo `bson:"docs"`
}
var res []queryRes
if err = cursor.All(ctx, &res); err != nil {
return err
}
for _, item := range res {
gameIDs := make([]primitive.ObjectID, 0)
deleteInfoIDs := make([]primitive.ObjectID, 0)
for _, info := range item.Infos[1:] {
gameIDs = append(gameIDs, info.GameIDs...)
deleteInfoIDs = append(deleteInfoIDs, info.ID)
}
item.Infos[0].GameIDs = utils.Unique(append(item.Infos[0].GameIDs, gameIDs...))
err = DeleteGameInfosByIDs(deleteInfoIDs)
if err != nil {
return err
}
}
return nil
}
func MergeGameInfosWithSameName() error {
games, err := GetSameNameGameInfos()
if err != nil {
return err
}
for _, ids := range games {
var IGDBItem *model.GameInfo = nil
otherPlatformItems := make([]*model.GameInfo, 0)
skip := false
for _, id := range ids {
item, err := GetGameInfoByID(id)
if err != nil {
continue
}
if item.IGDBID != 0 {
if IGDBItem == nil {
IGDBItem = item
} else {
skip = true
break
// skip if there are multiple items with IGDB ID
// not sure which item is correct
// need deal manually
}
} else {
otherPlatformItems = append(otherPlatformItems, item)
}
}
if skip {
continue
}
if IGDBItem != nil {
for _, item := range otherPlatformItems {
IGDBItem.GameIDs = append(IGDBItem.GameIDs, item.ID)
}
if err := SaveGameInfo(IGDBItem); err != nil {
continue
}
}
}
return nil
}
func GetGameInfoCount() (int64, error) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
count, err := GameInfoCollection.CountDocuments(ctx, bson.M{})
if err != nil {
return 0, err
}
return count, nil
}
func GetGameItemCount() (int64, error) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
count, err := GameItemCollection.CountDocuments(ctx, bson.M{})
if err != nil {
return 0, err
}
return count, nil
}
func GetGameInfoWithSteamID() ([]*model.GameInfo, error) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
filter := bson.M{"$and": []bson.M{
{"steam_id": bson.M{"$exists": 1}},
{"steam_id": bson.M{"$ne": 0}},
}}
cursor, err := GameInfoCollection.Find(ctx, filter)
if err != nil {
return nil, err
}
var games []*model.GameInfo
if err = cursor.All(ctx, &games); err != nil {
return nil, err
}
return games, nil
}
func DeleteGameInfoByID(id primitive.ObjectID) error {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
_, err := GameInfoCollection.DeleteOne(ctx, bson.M{"_id": id})
if err != nil {
return err
}
return nil
}
func DeleteGameItemByID(id primitive.ObjectID) error {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
_, err := GameItemCollection.DeleteOne(ctx, bson.M{"_id": id})
if err != nil {
return err
}
filter := bson.M{"games": bson.M{"$in": []primitive.ObjectID{id}}}
cursor, err := GameInfoCollection.Find(ctx, filter)
if err != nil {
return err
}
var games []*model.GameInfo
if err = cursor.All(ctx, &games); err != nil {
return err
}
for _, game := range games {
newIDs := make([]primitive.ObjectID, 0)
for _, gameID := range game.GameIDs {
if gameID != id {
newIDs = append(newIDs, gameID)
}
}
game.GameIDs = newIDs
if err := SaveGameInfo(game); err != nil {
continue
}
}
return nil
}
func DeleteGameItemsByIDs(ids []primitive.ObjectID) error {
if len(ids) == 0 {
return nil
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
_, err := GameItemCollection.DeleteMany(ctx, bson.M{"_id": bson.M{"$in": ids}})
if err != nil {
return err
}
update := bson.D{{Key: "$pull", Value: bson.D{
{Key: "games", Value: bson.D{
{Key: "$in", Value: ids},
}},
}}}
_, err = GameInfoCollection.UpdateMany(ctx, bson.M{}, update)
if err != nil {
return err
}
return nil
}
func GetAllAuthors() ([]string, error) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
pipeline := mongo.Pipeline{
bson.D{{Key: "$group", Value: bson.D{
{Key: "_id", Value: "$author"},
}}},
}
cursor, err := GameItemCollection.Aggregate(ctx, pipeline)
if err != nil {
return nil, err
}
var authors []struct {
Author string `bson:"_id"`
}
if err = cursor.All(ctx, &authors); err != nil {
return nil, err
}
var res []string
for _, author := range authors {
res = append(res, author.Author)
}
return res, nil
}
func GetAllGameInfos() ([]*model.GameInfo, error) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
cursor, err := GameInfoCollection.Find(ctx, bson.M{})
if err != nil {
return nil, err
}
var res []*model.GameInfo
if err = cursor.All(ctx, &res); err != nil {
return nil, err
}
return res, nil
}
func GetGameInfoByGameItemID(id primitive.ObjectID) (*model.GameInfo, error) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
filter := bson.M{"games": id}
cursor, err := GameInfoCollection.Find(ctx, filter)
if err != nil {
return nil, err
}
var res []*model.GameInfo
if err = cursor.All(ctx, &res); err != nil {
return nil, err
}
if len(res) == 0 {
return nil, errors.New("game info not found")
}
return res[0], nil
}
func GetOutdatedGameInfos(maxNum int) ([]*model.GameInfo, error) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
opts := options.Find().SetLimit(int64(maxNum))
filter := bson.M{
"info_updated_at": bson.M{"$lt": time.Now().Add(-24 * time.Hour * 30)},
}
cursor, err := GameInfoCollection.Find(ctx, filter, opts)
if err != nil {
return nil, err
}
var res []*model.GameInfo
if err = cursor.All(ctx, &res); err != nil {
return nil, err
}
return res, nil
}
func MergeGameInfo(oldInfo *model.GameInfo, newInfo *model.GameInfo) {
newInfo.ID = oldInfo.ID
newInfo.UpdatedAt = time.Now()
newInfo.GameIDs = oldInfo.GameIDs
newInfo.IGDBID = oldInfo.IGDBID
newInfo.SteamID = oldInfo.SteamID
newInfo.CreatedAt = oldInfo.CreatedAt
}