948 lines
25 KiB
Go
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
|
|
}
|